From 276e6cb2491411d3488a7b7249eb5d1414b772b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B2=BD=EC=A2=85?= Date: Tue, 23 Jun 2026 10:52:50 +0900 Subject: [PATCH] add gemini-harness --- Gemini/gemini-harness/.gitignore | 2 + Gemini/gemini-harness/GEMINI.md | 30 + .../skills/gemini-harness/SKILL.md | 458 ++++++++++++++ .../references/agent-design-patterns.md | 300 ++++++++++ .../references/orchestrator-template.md | 294 +++++++++ .../references/qa-agent-guide.md | 228 +++++++ .../references/skill-testing-guide.md | 307 ++++++++++ .../references/skill-writing-guide.md | 298 ++++++++++ .../references/team-examples.md | 330 +++++++++++ .../.agents/skills/document-harness/SKILL.md | 131 ---- .../references/phase-templates.md | 85 --- .../.agents/skills/document-review/SKILL.md | 45 -- Writing/Codex/.codex/agents/doc_drafter.toml | 16 - .../Codex/.codex/agents/doc_researcher.toml | 15 - Writing/Codex/.codex/agents/doc_reviewer.toml | 14 - .../Codex/.codex/agents/evidence_checker.toml | 14 - Writing/Codex/.codex/config.toml | 10 - Writing/Codex/.codex/hooks.json | 30 - Writing/Codex/.codex/hooks/pre_tool_guard.py | 67 --- Writing/Codex/.codex/hooks/stop_validate.py | 39 -- Writing/Codex/.gitignore | 14 - Writing/Codex/AGENTS.md | 57 -- Writing/Codex/README.md | 259 -------- Writing/Codex/docs/ADR.md | 48 -- Writing/Codex/docs/ARCHITECTURE.md | 74 --- Writing/Codex/docs/DraftFeedback.md | 23 - Writing/Codex/docs/FinalFeedback.md | 17 - Writing/Codex/docs/PRD.md | 68 --- Writing/Codex/docs/ResearchNote.md | 54 -- Writing/Codex/docs/UI_GUIDE.md | 54 -- Writing/Codex/drafts/.gitkeep | 1 - Writing/Codex/final/.gitkeep | 1 - Writing/Codex/scripts/execute.py | 426 ------------- Writing/Codex/scripts/test_execute.py | 560 ------------------ Writing/Codex/scripts/validate_docs.py | 196 ------ .../.agents/skills/document-harness/SKILL.md | 131 ---- .../references/phase-templates.md | 85 --- .../.agents/skills/document-review/SKILL.md | 45 -- Writing/Gemini/.gemini/agents/doc-drafter.md | 29 - .../Gemini/.gemini/agents/doc-researcher.md | 31 - Writing/Gemini/.gemini/agents/doc-reviewer.md | 25 - .../Gemini/.gemini/agents/evidence-checker.md | 25 - .../.gemini/commands/harness/draft.toml | 19 - .../.gemini/commands/harness/final.toml | 18 - .../Gemini/.gemini/commands/harness/plan.toml | 17 - .../.gemini/commands/harness/research.toml | 18 - .../.gemini/commands/harness/review.toml | 18 - .../.gemini/commands/harness/status.toml | 18 - .../Gemini/.gemini/hooks/pre_tool_guard.py | 59 -- .../hooks/validate_docs_after_agent.py | 52 -- Writing/Gemini/.gemini/settings.json | 44 -- Writing/Gemini/.gitignore | 14 - Writing/Gemini/GEMINI.md | 58 -- Writing/Gemini/README.md | 252 -------- Writing/Gemini/docs/ADR.md | 48 -- Writing/Gemini/docs/ARCHITECTURE.md | 76 --- Writing/Gemini/docs/DraftFeedback.md | 23 - Writing/Gemini/docs/FinalFeedback.md | 17 - Writing/Gemini/docs/PRD.md | 68 --- Writing/Gemini/docs/ResearchNote.md | 54 -- Writing/Gemini/docs/UI_GUIDE.md | 54 -- Writing/Gemini/drafts/.gitkeep | 1 - Writing/Gemini/final/.gitkeep | 1 - Writing/Gemini/scripts/execute.py | 427 ------------- Writing/Gemini/scripts/test_execute.py | 560 ------------------ Writing/Gemini/scripts/validate_docs.py | 234 -------- 66 files changed, 2247 insertions(+), 4839 deletions(-) create mode 100644 Gemini/gemini-harness/.gitignore create mode 100644 Gemini/gemini-harness/GEMINI.md create mode 100644 Gemini/gemini-harness/skills/gemini-harness/SKILL.md create mode 100644 Gemini/gemini-harness/skills/gemini-harness/references/agent-design-patterns.md create mode 100644 Gemini/gemini-harness/skills/gemini-harness/references/orchestrator-template.md create mode 100644 Gemini/gemini-harness/skills/gemini-harness/references/qa-agent-guide.md create mode 100644 Gemini/gemini-harness/skills/gemini-harness/references/skill-testing-guide.md create mode 100644 Gemini/gemini-harness/skills/gemini-harness/references/skill-writing-guide.md create mode 100644 Gemini/gemini-harness/skills/gemini-harness/references/team-examples.md delete mode 100644 Writing/Codex/.agents/skills/document-harness/SKILL.md delete mode 100644 Writing/Codex/.agents/skills/document-harness/references/phase-templates.md delete mode 100644 Writing/Codex/.agents/skills/document-review/SKILL.md delete mode 100644 Writing/Codex/.codex/agents/doc_drafter.toml delete mode 100644 Writing/Codex/.codex/agents/doc_researcher.toml delete mode 100644 Writing/Codex/.codex/agents/doc_reviewer.toml delete mode 100644 Writing/Codex/.codex/agents/evidence_checker.toml delete mode 100644 Writing/Codex/.codex/config.toml delete mode 100644 Writing/Codex/.codex/hooks.json delete mode 100644 Writing/Codex/.codex/hooks/pre_tool_guard.py delete mode 100644 Writing/Codex/.codex/hooks/stop_validate.py delete mode 100644 Writing/Codex/.gitignore delete mode 100644 Writing/Codex/AGENTS.md delete mode 100644 Writing/Codex/README.md delete mode 100644 Writing/Codex/docs/ADR.md delete mode 100644 Writing/Codex/docs/ARCHITECTURE.md delete mode 100644 Writing/Codex/docs/DraftFeedback.md delete mode 100644 Writing/Codex/docs/FinalFeedback.md delete mode 100644 Writing/Codex/docs/PRD.md delete mode 100644 Writing/Codex/docs/ResearchNote.md delete mode 100644 Writing/Codex/docs/UI_GUIDE.md delete mode 100644 Writing/Codex/drafts/.gitkeep delete mode 100644 Writing/Codex/final/.gitkeep delete mode 100644 Writing/Codex/scripts/execute.py delete mode 100644 Writing/Codex/scripts/test_execute.py delete mode 100644 Writing/Codex/scripts/validate_docs.py delete mode 100644 Writing/Gemini/.agents/skills/document-harness/SKILL.md delete mode 100644 Writing/Gemini/.agents/skills/document-harness/references/phase-templates.md delete mode 100644 Writing/Gemini/.agents/skills/document-review/SKILL.md delete mode 100644 Writing/Gemini/.gemini/agents/doc-drafter.md delete mode 100644 Writing/Gemini/.gemini/agents/doc-researcher.md delete mode 100644 Writing/Gemini/.gemini/agents/doc-reviewer.md delete mode 100644 Writing/Gemini/.gemini/agents/evidence-checker.md delete mode 100644 Writing/Gemini/.gemini/commands/harness/draft.toml delete mode 100644 Writing/Gemini/.gemini/commands/harness/final.toml delete mode 100644 Writing/Gemini/.gemini/commands/harness/plan.toml delete mode 100644 Writing/Gemini/.gemini/commands/harness/research.toml delete mode 100644 Writing/Gemini/.gemini/commands/harness/review.toml delete mode 100644 Writing/Gemini/.gemini/commands/harness/status.toml delete mode 100644 Writing/Gemini/.gemini/hooks/pre_tool_guard.py delete mode 100644 Writing/Gemini/.gemini/hooks/validate_docs_after_agent.py delete mode 100644 Writing/Gemini/.gemini/settings.json delete mode 100644 Writing/Gemini/.gitignore delete mode 100644 Writing/Gemini/GEMINI.md delete mode 100644 Writing/Gemini/README.md delete mode 100644 Writing/Gemini/docs/ADR.md delete mode 100644 Writing/Gemini/docs/ARCHITECTURE.md delete mode 100644 Writing/Gemini/docs/DraftFeedback.md delete mode 100644 Writing/Gemini/docs/FinalFeedback.md delete mode 100644 Writing/Gemini/docs/PRD.md delete mode 100644 Writing/Gemini/docs/ResearchNote.md delete mode 100644 Writing/Gemini/docs/UI_GUIDE.md delete mode 100644 Writing/Gemini/drafts/.gitkeep delete mode 100644 Writing/Gemini/final/.gitkeep delete mode 100644 Writing/Gemini/scripts/execute.py delete mode 100644 Writing/Gemini/scripts/test_execute.py delete mode 100644 Writing/Gemini/scripts/validate_docs.py diff --git a/Gemini/gemini-harness/.gitignore b/Gemini/gemini-harness/.gitignore new file mode 100644 index 0000000..40ddbcd --- /dev/null +++ b/Gemini/gemini-harness/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +.claude/ diff --git a/Gemini/gemini-harness/GEMINI.md b/Gemini/gemini-harness/GEMINI.md new file mode 100644 index 0000000..b1bcd11 --- /dev/null +++ b/Gemini/gemini-harness/GEMINI.md @@ -0,0 +1,30 @@ +# Gemini-Harness 프로젝트 가이드라인 + +**프로젝트 목적:** +이 프로젝트는 에이전트 팀 시스템을 활용하여 복잡한 작업을 전문 에이전트 팀으로 분해하고 조율하는 아키텍처 도구인 '하네스(Harness)'를 개발하고 유지보수하는 메타-스킬 저장소입니다. 사용자의 도메인에 맞는 에이전트 정의와 스킬을 자동으로 생성해주는 코어 프롬프트와 참조 문서들을 관리합니다. + +## 🤖 AI 에이전트(Gemini) 역할 및 행동 수칙 + +당신은 이 `gemini-harness` 메타-스킬의 핵심 개발자입니다. 이 저장소에서의 작업은 특정 도메인의 애플리케이션을 만드는 것이 아니라, **'다른 에이전트와 스킬을 생성하는 시스템 자체'를 설계하고 개선**하는 것임을 명심하십시오. + +### 1. 코어 로직 보존 및 개선 +- 핵심 로직은 `skills/gemini-harness/SKILL.md`에 정의되어 있습니다. +- 하네스의 실행 흐름(Phase 0 현황 감사 ~ Phase 7 하네스 진화)을 완벽히 이해하고, 시스템을 개선할 때 이 워크플로우의 일관성과 논리적 흐름을 깨뜨리지 않도록 주의합니다. +- 특정 도메인에 치우치지 않는 '일반화된 범용 프롬프트 메커니즘'을 유지해야 합니다. + +### 2. 참조 문서(References) 아키텍처 관리 +- 에이전트 설계 패턴, 스킬 작성 가이드, QA 방법론 등은 `skills/gemini-harness/references/` 디렉토리에서 관리됩니다. +- 새로운 협업 패턴이나 더 나은 프롬프팅 기법이 발견되면 관련 reference 문서에 반영하여 하네스의 지능을 고도화하십시오. +- `SKILL.md` 본문이 지나치게 길어지지 않도록(500줄 제한 권장) 상세 가이드는 항상 references로 분리하여 점진적 정보 공개(Progressive Disclosure) 원칙을 따릅니다. + +### 3. 하네스 설계 철학 준수 +- **에이전트 팀 우선**: 단일 거대 에이전트보다는 역할이 분리된 에이전트 팀(`invoke_subagent` 및 `send_message` 활용)의 협업을 지향하도록 설계원칙을 유지합니다. +- **파일 기반 구조화**: 모든 에이전트는 `.agents/agents/{name}.md`에, 스킬은 `.agents/skills/{name}/SKILL.md`에 독립적으로 정의되도록 템플릿과 생성 규칙을 엄격히 관리합니다. +- **진화하는 시스템**: 피드백을 기반으로 하네스가 스스로를 갱신할 수 있는 운영/유지보수 워크플로우(Phase 7) 체계를 지속적으로 강화합니다. + +### 4. 코드 작성 및 수정 지침 +- 이 레포지토리는 주로 Markdown 기반의 프롬프트 엔지니어링 파일들로 구성되어 있습니다. 코드를 수정할 때는 LLM이 정확히 이해할 수 있는 명확하고 지시적인(명령형) 어조를 유지하십시오. +- "ALWAYS/NEVER" 등의 강압적 지시보다는 'Why(왜 그렇게 해야 하는지)'를 설명하는 구조를 채택하여 LLM의 맥락적 판단력을 높이십시오. + +### 요약 +이 작업 공간 내에서의 모든 지시는 **"어떻게 하면 하네스가 더 똑똑하고 효율적으로 사용자 맞춤형 에이전트 팀을 구성할 수 있을까?"**라는 메타적 관점에서 해석되고 수행되어야 합니다. diff --git a/Gemini/gemini-harness/skills/gemini-harness/SKILL.md b/Gemini/gemini-harness/skills/gemini-harness/SKILL.md new file mode 100644 index 0000000..6257449 --- /dev/null +++ b/Gemini/gemini-harness/skills/gemini-harness/SKILL.md @@ -0,0 +1,458 @@ +--- +name: harness +description: "하네스를 구성합니다. 전문 에이전트를 정의하며, 해당 에이전트가 사용할 스킬을 생성하는 메타 스킬. (1) '하네스 구성해줘', '하네스 구축해줘' 요청 시, (2) '하네스 설계', '하네스 엔지니어링' 요청 시, (3) 새로운 도메인/프로젝트에 대한 하네스 기반 자동화 체계를 구축할 때, (4) 하네스 구성을 재구성하거나 확장할 때, (5) '하네스 점검', '하네스 감사', '하네스 현황', '에이전트/스킬 동기화' 등 기존 하네스 운영/유지보수 요청 시 사용." +--- + +# Harness — Agent Team & Skill Architect + +도메인/프로젝트에 맞는 하네스를 구성하고, 각 에이전트의 역할을 정의하며, 에이전트가 사용할 스킬을 생성하는 메타 스킬. + +**핵심 원칙:** +1. 에이전트 정의(`.agents/agents/`)와 스킬(`.agents/skills/`)을 생성한다. +2. **에이전트 팀을 기본 실행 모드로 사용한다.** +3. **AGENTS.md에 하네스 포인터를 등록한다.** — 새 세션에서 오케스트레이터 스킬이 트리거되도록 최소한의 포인터(트리거 규칙 + 변경 이력)만 기록한다. +4. **하네스는 고정물이 아니라 진화하는 시스템이다.** — 매 실행 후 피드백을 반영하고, 에이전트·스킬·AGENTS.md를 지속 갱신한다. + +## 워크플로우 + +### Phase 0: 현황 감사 + +하네스 스킬이 트리거되면 가장 먼저 기존 하네스 현황을 확인한다. + +1. `프로젝트/.agents/agents/`, `프로젝트/.agents/skills/`, `프로젝트/AGENTS.md`를 읽는다 +2. 현황에 따라 실행 모드를 분기한다: + - **신규 구축**: 에이전트/스킬 디렉토리가 없거나 비어있음 → Phase 1부터 전체 실행 + - **기존 확장**: 기존 하네스가 있고 새 에이전트/스킬 추가 요청 → 아래 Phase 선택 매트릭스에 따라 필요한 Phase만 실행 + - **운영/유지보수**: 기존 하네스의 감사·수정·동기화 요청 → Phase 7-5 운영/유지보수 워크플로우로 이동 + + **기존 확장 시 Phase 선택 매트릭스:** + | 변경 유형 | Phase 1 | Phase 2 | Phase 3 | Phase 4 | Phase 5 | Phase 6 | + |----------|---------|---------|---------|---------|---------|---------| + | 에이전트 추가 | 건너뜀 (Phase 0 결과 활용) | 배치 결정만 | 필수 (3-0 포함) | 전용 스킬 필요 시 (4-0 포함) | 오케스트레이터 수정 | 필수 | + | 스킬 추가/수정 | 건너뜀 | 건너뜀 | 건너뜀 | 필수 (4-0 포함) | 연결 변경 시 | 필수 | + | 아키텍처 변경 | 건너뜀 | 필수 | 영향받는 에이전트만 (3-0 포함) | 영향받는 스킬만 (4-0 포함) | 필수 | 필수 | +3. 기존 에이전트/스킬 목록과 AGENTS.md 기록을 대조하여 불일치(drift)를 감지한다 +4. 감사 결과를 사용자에게 요약 보고하고, 실행 계획을 확인받는다 + +### Phase 1: 도메인 분석 +1. 사용자 요청에서 도메인/프로젝트 파악 +2. 핵심 작업 유형 식별 (생성, 검증, 편집, 분석 등) +3. Phase 0 감사 결과를 기반으로 기존 에이전트/스킬과의 충돌/중복 분석 +4. 프로젝트 코드베이스 탐색 — 기술 스택, 데이터 모델, 주요 모듈 파악 +5. **사용자 숙련도 감지** — 대화의 맥락 단서(사용 용어, 질문 수준)로 기술 수준을 파악하고, 이후 커뮤니케이션 톤을 조절한다. 코딩 경험이 적은 사용자에게는 "assertion", "JSON schema" 같은 용어를 설명 없이 쓰지 않는다. + +### Phase 2: 팀 아키텍처 설계 + +#### 2-1. 실행 모드 선택 + +**에이전트 팀이 최우선 기본값이다.** 2개 이상의 에이전트가 협업할 때는 반드시 에이전트 팀을 먼저 검토한다. 팀원 간 직접 통신(send_message)과 공유 작업 목록(tasks.md 파일 생성)으로 자체 조율하며, 발견 공유·상충 토론·누락 보완이 결과 품질을 높인다. + +| 모드 | 언제 사용 | 특성 | +|------|----------|------| +| **에이전트 팀** (기본) | 2명 이상 협업, 실시간 조율·피드백 교환이 필요, 중간 산출물 상호 참조 | `invoke_subagent` + `send_message` + `tasks.md 파일 생성`로 자체 조율 | +| **서브 에이전트** (대안) | 단일 에이전트 작업, 결과만 메인에 반환하면 충분, 팀 통신 오버헤드가 과할 때 | `Agent` 도구 직접 호출, `run_in_background`로 병렬 | +| **하이브리드** | Phase마다 특성이 다를 때 — 예: 병렬 수집(서브) → 합의 기반 통합(팀) | Phase 단위로 팀/서브를 섞어 구성 | + +**의사결정 순서:** +1. 먼저 에이전트 팀으로 설계 가능한지 검토한다 — 2명 이상이면 기본값 +2. 팀 통신이 구조적으로 불필요하고(결과 전달만), 팀 오버헤드가 이득보다 클 때만 서브 에이전트 선택 +3. Phase별 특성이 확연히 다르면 하이브리드 고려 — 각 Phase의 실행 모드를 오케스트레이터에 명시 + +> 상세 비교표와 패턴별 의사결정 트리는 `references/agent-design-patterns.md`의 "실행 모드" 참조. + +#### 2-2. 아키텍처 패턴 선택 + +1. 작업을 전문 영역으로 분해 +2. 에이전트 팀 구조 결정 (아키텍처 패턴은 `references/agent-design-patterns.md` 참조) + - **파이프라인**: 순차 의존 작업 + - **팬아웃/팬인**: 병렬 독립 작업 + - **전문가 풀**: 상황별 선택 호출 + - **생성-검증**: 생성 후 품질 검수 + - **감독자**: 중앙 에이전트가 상태 관리 및 동적 분배 + - **계층적 위임**: 상위 에이전트가 하위에 재귀적 위임 + +#### 2-3. 에이전트 분리 기준 + +전문성·병렬성·컨텍스트·재사용성 4축으로 판단한다. 상세 기준표는 `references/agent-design-patterns.md`의 "에이전트 분리 기준" 참조. 기존 에이전트와의 중복·재사용 검토는 Phase 3-0에서 다룬다. + +### Phase 3: 에이전트 정의 생성 + +#### 3-0. 기존 에이전트 중복 검토 + +신규 에이전트 생성 전, `프로젝트/.agents/agents/`의 기존 에이전트와 중복 여부를 확인한다. 하네스를 반복 구축하다 보면 역할이 겹치는 에이전트가 다른 이름으로 누적되기 쉽다. + +> 중복 분류 기준과 재사용 설계는 `references/agent-design-patterns.md`의 "에이전트 재사용 설계" 참조. + +**모든 에이전트는 반드시 `프로젝트/.agents/agents/{name}.md` 파일로 정의한다.** 에이전트 정의 파일 없이 invoke_subagent 도구의 prompt에 역할을 직접 넣는 것은 금지한다. 이유: +- 에이전트 정의가 파일로 존재해야 다음 세션에서 재사용 가능 +- 팀 통신 프로토콜이 명시되어야 에이전트 간 협업 품질 보장 +- 하네스의 핵심 가치는 에이전트(누가)와 스킬(어떻게)의 분리 + +빌트인 타입(`general-purpose`, `Explore`, `Plan`)을 사용하더라도 에이전트 정의 파일은 생성한다. 빌트인 타입은 invoke_subagent 도구의 `TypeName` 파라미터로 지정하고, 에이전트 정의 파일에는 역할·원칙·프로토콜을 담는다. + +**모델 설정:** 모든 에이전트는 `model: "gemini-3.5-pro"`를 사용한다. invoke_subagent 도구 호출 시 반드시 `model: "gemini-3.5-pro"` 파라미터를 명시한다. 하네스의 품질은 에이전트의 추론 능력에 직결되며, opus가 최고 품질을 보장한다. + +**팀 재구성:** 에이전트 팀은 세션당 한 팀만 활성화할 수 있지만, Phase 간에 팀을 해체하고 새 팀을 구성할 수 있다. 파이프라인 패턴처럼 Phase별로 다른 전문가 조합이 필요하면, 이전 팀의 산출물을 파일로 저장한 뒤 팀을 정리하고 새 팀을 생성한다. + +각 에이전트를 `프로젝트/.agents/agents/{name}.md`에 정의한다. 필수 섹션: 핵심 역할, 작업 원칙, 입력/출력 프로토콜, 에러 핸들링, 협업. 에이전트 팀 모드에서는 `## 팀 통신 프로토콜` 섹션을 추가하여 메시지 수신/발신 대상과 작업 요청 범위를 명시한다. + +> 정의 템플릿과 실제 파일 전문은 `references/agent-design-patterns.md`의 "에이전트 정의 구조" + `references/team-examples.md` 참조. + +**QA 에이전트 포함 시 필수 사항:** +- QA 에이전트는 `general-purpose` 타입을 사용하라 (`Explore`는 읽기 전용이므로 검증 스크립트 실행 불가) +- QA의 핵심은 "존재 확인"이 아니라 **"경계면 교차 비교"** — API 응답과 프론트 훅을 동시에 읽고 shape을 비교 +- QA는 전체 완성 후 1회가 아니라, **각 모듈 완성 직후 점진적으로 실행** (incremental QA) +- 상세 가이드: `references/qa-agent-guide.md` 참조 + +### Phase 4: 스킬 생성 + +각 에이전트가 사용할 스킬을 `프로젝트/.agents/skills/{name}/SKILL.md`에 생성한다. 상세 작성 가이드는 `references/skill-writing-guide.md` 참조. + +#### 4-0. 기존 스킬 중복 검토 + +신규 스킬 생성 전, `프로젝트/.agents/skills/`의 기존 스킬과 중복 여부를 확인한다. 하네스를 반복 구축하다 보면 기능이 겹치는 스킬이 다른 이름으로 누적되기 쉽다. + +> 중복 분류 기준과 일반화 패턴은 `references/skill-writing-guide.md`의 "스킬 재사용 설계" 참조. + +#### 4-1. 스킬 구조 + +``` +skill-name/ +├── SKILL.md (필수) +│ ├── YAML frontmatter (name, description 필수) +│ └── Markdown 본문 +└── Bundled Resources (선택) + ├── scripts/ - 반복/결정적 작업용 실행 코드 + ├── references/ - 조건부 로딩하는 참조 문서 + └── assets/ - 출력에 사용되는 파일 (템플릿, 이미지 등) +``` + +#### 4-2. Description 작성 — 적극적 트리거 유도 + +description은 스킬의 유일한 트리거 메커니즘이다. Claude는 트리거를 보수적으로 판단하는 경향이 있으므로, description을 **적극적("pushy")**으로 작성한다. + +**나쁜 예:** `"PDF 문서를 처리하는 스킬"` +**좋은 예:** `"PDF 파일 읽기, 텍스트/테이블 추출, 병합, 분할, 회전, 워터마크, 암호화, OCR 등 모든 PDF 작업을 수행. .pdf 파일을 언급하거나 PDF 산출물을 요청하면 반드시 이 스킬을 사용할 것."` + +핵심: 스킬이 하는 일 + 구체적 트리거 상황을 모두 기술하고, 유사하지만 트리거하면 안 되는 경우와 구분되도록 작성. + +#### 4-3. 본문 작성 원칙 + +| 원칙 | 설명 | +|------|------| +| **Why를 설명하라** | "ALWAYS/NEVER" 같은 강압적 지시 대신, 왜 그렇게 해야 하는지 이유를 전달한다. LLM은 이유를 이해하면 엣지 케이스에서도 올바르게 판단한다. | +| **Lean하게 유지** | 컨텍스트 윈도우는 공공재다. SKILL.md 본문은 500줄 이내를 목표로, 무게를 벌지 않는 내용은 삭제하거나 references/로 이동한다. | +| **일반화하라** | 특정 예시에만 맞는 좁은 규칙보다, 원리를 설명하여 다양한 입력에 대응할 수 있게 한다. 오버피팅 금지. | +| **반복 코드는 번들링** | 테스트 실행에서 에이전트들이 공통으로 작성하는 스크립트가 발견되면 `scripts/`에 미리 번들링한다. | +| **명령형으로 작성** | "~한다", "~하라" 형태의 명령형/지시형 어조를 사용한다. | + +#### 4-4. Progressive Disclosure (단계적 정보 공개) + +스킬은 3단계 로딩 시스템으로 컨텍스트를 관리한다: + +| 단계 | 로딩 시점 | 크기 목표 | +|------|----------|----------| +| **Metadata** (name + description) | 항상 컨텍스트에 존재 | ~100단어 | +| **SKILL.md 본문** | 스킬 트리거 시 | <500줄 | +| **references/** | 필요할 때만 | 무제한 (스크립트는 로딩 없이 실행 가능) | + +**크기 관리 규칙:** +- SKILL.md가 500줄에 근접하면 세부 내용을 references/로 분리하고, 본문에 "언제 이 파일을 읽으라"는 포인터를 남긴다 +- 300줄 이상의 reference 파일에는 상단에 **목차(ToC)**를 포함한다 +- 도메인/프레임워크별 변형이 있으면 references/ 하위에 도메인별로 분리하여, 관련 파일만 로드한다 + +``` +cloud-deploy/ +├── SKILL.md (워크플로우 + 선택 가이드) +└── references/ + ├── aws.md ← AWS 선택 시만 로드 + ├── gcp.md + └── azure.md +``` + +#### 4-5. 스킬-에이전트 연결 원칙 + +- 에이전트 1개 ↔ 스킬 1~N개 (1:1 또는 1:다) +- 여러 에이전트가 공유하는 스킬도 가능 +- 스킬은 "어떻게 하는가"를 담고, 에이전트는 "누가 하는가"를 담는다 + +> 상세 작성 패턴, 예시, 데이터 스키마 표준은 `references/skill-writing-guide.md` 참조. + +### Phase 5: 통합 및 오케스트레이션 + +오케스트레이터는 스킬의 특수한 형태로, 개별 에이전트와 스킬을 하나의 워크플로우로 엮어 팀 전체를 조율한다. Phase 4에서 생성한 개별 스킬이 "각 에이전트가 무엇을 어떻게 하는가"를 정의한다면, 오케스트레이터는 "누가 언제 어떤 순서로 협업하는가"를 정의한다. 구체적 템플릿은 `references/orchestrator-template.md` 참조. + +**기존 확장 시 오케스트레이터 수정:** 신규 구축이 아닌 기존 확장일 때는 오케스트레이터를 새로 생성하지 않고 기존 오케스트레이터를 수정한다. 에이전트 추가 시 팀 구성·작업 할당·데이터 흐름에 새 에이전트를 반영하고, description에 새 에이전트 관련 트리거 키워드를 추가한다. + +Phase 2-1에서 선택한 실행 모드에 따라 오케스트레이터 패턴이 달라진다: + +#### 5-0. 오케스트레이터 패턴 (모드별) + +**에이전트 팀 패턴 (기본):** +오케스트레이터가 `invoke_subagent`로 팀을 구성하고, `tasks.md 파일 생성`로 작업을 할당한다. 팀원들은 `send_message`로 직접 통신하며 자체 조율한다. 리더(오케스트레이터)는 진행 상황을 모니터링하고 결과를 종합한다. + +``` +[오케스트레이터/리더] + ├── define_subagent(members) + ├── invoke_subagent(team_name, members) + ├── tasks.md 파일 생성(tasks with dependencies) + ├── 팀원들이 자체 조율 (send_message) + ├── 결과 수집 및 종합 + └── 팀 정리 +``` + +**서브 에이전트 패턴 (대안):** +오케스트레이터가 `Agent` 도구로 서브 에이전트를 직접 호출한다. 병렬 실행은 `run_in_background: true`, 결과는 메인에게만 반환된다. 팀 통신이 불필요하고 오버헤드를 줄이고 싶을 때 사용. + +``` +[오케스트레이터] + ├── Agent(agent-1, run_in_background=true) + ├── Agent(agent-2, run_in_background=true) + ├── 결과 대기 및 수집 + └── 통합 산출물 생성 +``` + +**하이브리드 패턴:** +Phase마다 다른 모드를 섞어 구성한다. 자주 쓰이는 조합: +- **병렬 수집(서브) → 합의 통합(팀)**: Phase 2에서 서브 에이전트로 독립 자료를 병렬 수집 → Phase 3에서 팀을 만들어 토론·합의 기반 통합 +- **팀 생성(팀) → 검증(서브)**: Phase 2에서 팀이 초안 생성 → Phase 3에서 단일 서브 에이전트가 독립 검증 +- **Phase 간 팀 재구성**: 각 Phase마다 `manage_subagents(kill)` 후 새 `invoke_subagent`, 사이에 서브 에이전트 호출 삽입 + +하이브리드 선택 시 오케스트레이터의 각 Phase 섹션 상단에 해당 Phase의 실행 모드를 명시한다 (예: `**실행 모드:** 에이전트 팀`). + +#### 5-1. 데이터 전달 프로토콜 + +오케스트레이터 내에 에이전트 간 데이터 전달 방식을 명시한다: + +| 전략 | 방식 | 적용 모드 | 적합한 경우 | +|------|------|----------|-----------| +| **메시지 기반** | `send_message`로 팀원 간 직접 통신 | 팀 | 실시간 조율, 피드백 교환, 가벼운 상태 전달 | +| **태스크 기반** | `tasks.md 파일 생성`/`tasks.md 업데이트`로 작업 상태 공유 | 팀 | 진행상황 추적, 의존 관계 관리, 작업 자체 요청 | +| **파일 기반** | 약속된 경로에 파일을 쓰고 읽음 | 팀 + 서브 | 대용량 데이터, 구조화된 산출물, 감사 추적 필요 | +| **반환값 기반** | `Agent` 도구의 반환 메시지 | 서브 | 서브 에이전트 결과를 메인이 직접 수집 | + +**권장 조합 (팀 모드):** 태스크 기반(조율) + 파일 기반(산출물) + 메시지 기반(실시간 소통) +**권장 조합 (서브 모드):** 반환값 기반(결과 수집) + 파일 기반(대용량 산출물) +**하이브리드:** 각 Phase의 실행 모드에 맞춰 해당 조합 적용 + +파일 기반 전달 시 규칙: +- 작업 디렉토리 하위에 `_workspace/` 폴더를 만들어 중간 산출물 저장 +- 파일명 컨벤션: `{phase}_{agent}_{artifact}.{ext}` (예: `01_analyst_requirements.md`) +- 최종 산출물만 사용자 지정 경로에 출력, 중간 파일(`_workspace/`)은 보존 (사후 검증·감사 추적용) + +#### 5-2. 에러 핸들링 + +오케스트레이터 내에 에러 처리 방침을 포함한다. 핵심 원칙: 1회 재시도 후 재실패 시 해당 결과 없이 진행(보고서에 누락 명시), 상충 데이터는 삭제하지 않고 출처 병기. + +> 에러 유형별 전략표와 구현 상세는 `references/orchestrator-template.md`의 "에러 핸들링" 참조. + +#### 5-3. 팀 크기 가이드라인 + +| 작업 규모 | 권장 팀원 수 | 팀원당 작업 수 | +|----------|------------|--------------| +| 소규모 (5~10개 작업) | 2~3명 | 3~5개 | +| 중규모 (10~20개 작업) | 3~5명 | 4~6개 | +| 대규모 (20개+ 작업) | 5~7명 | 4~5개 | + +> 팀원이 많을수록 조율 오버헤드가 커진다. 3명의 집중된 팀원이 5명의 산만한 팀원보다 낫다. + +#### 5-4. AGENTS.md 하네스 포인터 등록 + +하네스 구성 완료 후, 프로젝트의 `AGENTS.md`에 최소한의 포인터를 등록한다. AGENTS.md는 새 세션마다 로딩되므로, 하네스 존재와 트리거 규칙만 기록하면 오케스트레이터 스킬이 나머지를 처리한다. + +**AGENTS.md 템플릿:** + +````markdown +## 하네스: {도메인명} + +**목표:** {하네스의 핵심 목표 한 줄} + +**트리거:** {도메인} 관련 작업 요청 시 `{orchestrator-skill-name}` 스킬을 사용하라. 단순 질문은 직접 응답 가능. + +**변경 이력:** +| 날짜 | 변경 내용 | 대상 | 사유 | +|------|----------|------|------| +| {YYYY-MM-DD} | 초기 구성 | 전체 | - | +```` + +**AGENTS.md에 넣지 않는 것:** 에이전트 목록, 스킬 목록, 디렉토리 구조, 실행 규칙 상세. 이유: 에이전트/스킬 목록은 오케스트레이터 스킬과 `.agents/agents/`, `.agents/skills/`에서 관리하므로 중복이다. 디렉토리 구조는 파일 시스템에서 직접 확인 가능하다. AGENTS.md는 **포인터(트리거 규칙) + 변경 이력**만 담는다. + +#### 5-5. 후속 작업 지원 + +오케스트레이터는 초기 실행뿐 아니라 후속 작업도 처리해야 한다. 다음 세 가지를 보장하라: + +**1. 오케스트레이터 description에 후속 키워드 포함:** +초기 생성 키워드만으로는 후속 요청이 트리거되지 않는다. description에 반드시 포함할 후속 표현: +- "다시 실행", "재실행", "업데이트", "수정", "보완" +- "{도메인}의 {부분작업}만 다시" +- "이전 결과 기반으로", "결과 개선" + +**2. 오케스트레이터 Phase 1에 컨텍스트 확인 단계 추가:** +워크플로우 시작 시 기존 산출물 존재 여부를 확인하여 실행 모드를 결정한다: +- `_workspace/` 존재 + 사용자가 부분 수정 요청 → **부분 재실행** (해당 에이전트만 재호출) +- `_workspace/` 존재 + 사용자가 새 입력 제공 → **새 실행** (기존 _workspace를 `_workspace_prev/`로 이동) +- `_workspace/` 미존재 → **초기 실행** + +**3. 에이전트 정의에 재호출 지침 포함:** +각 에이전트 `.md` 파일에 "이전 산출물이 있을 때의 행동"을 명시한다: +- 이전 결과 파일이 존재하면 읽고 개선점을 반영 +- 사용자 피드백이 주어지면 해당 부분만 수정 + +> 오케스트레이터 템플릿의 "Phase 0: 컨텍스트 확인" 섹션 참조: `references/orchestrator-template.md` + +### Phase 6: 검증 및 테스트 + +생성된 하네스를 검증한다. 상세 테스트 방법론은 `references/skill-testing-guide.md` 참조. + +#### 6-1. 구조 검증 + +- 모든 에이전트 파일이 올바른 위치에 있는지 확인 +- 스킬의 frontmatter(name, description) 검증 +- 에이전트 간 참조 일관성 확인 +- 커맨드가 생성되지 않았는지 확인 + +#### 6-2. 실행 모드별 검증 + +- **에이전트 팀**: 팀원 간 통신 경로, 작업 의존성, 팀 크기 적정성 확인 +- **서브 에이전트**: 각 에이전트의 입출력 연결, `run_in_background` 설정, 반환값 수집 로직 확인 +- **하이브리드**: 각 Phase의 실행 모드가 오케스트레이터에 명시되었는지, Phase 경계에서 데이터 전달이 끊기지 않는지 확인 (팀 → 서브 전환 시 팀의 산출물이 서브의 입력으로 연결되는지) + +#### 6-3. 스킬 실행 테스트 + +생성된 각 스킬에 대해 실제 실행 테스트를 수행한다: + +1. **테스트 프롬프트 작성** — 각 스킬에 대해 2~3개의 현실적인 테스트 프롬프트를 작성한다. 실제 사용자가 입력할 법한 구체적이고 자연스러운 문장으로 작성한다. + +2. **With-skill vs Without-skill 비교 실행** — 가능하면 스킬 있는 실행과 없는 실행을 병렬로 수행하여 스킬의 부가가치를 확인한다. 에이전트를 두 개씩 스폰한다: + - **With-skill**: 스킬을 읽고 작업 수행 + - **Without-skill (baseline)**: 같은 프롬프트를 스킬 없이 수행 + +3. **결과 평가** — 산출물의 품질을 정성적(사용자 리뷰) + 정량적(assertion 기반) 으로 평가한다. 산출물이 객관적으로 검증 가능한 경우(파일 생성, 데이터 추출 등) assertion을 정의하고, 주관적인 경우(문체, 디자인) 사용자 피드백에 의존한다. + +4. **반복 개선 루프** — 테스트 결과에서 문제가 발견되면: + - 피드백을 **일반화**하여 스킬을 수정한다 (특정 예시에만 맞는 좁은 수정 금지) + - 수정 후 재테스트한다 + - 사용자가 만족하거나 의미 있는 개선이 더 이상 없을 때까지 반복한다 + +5. **반복 패턴 번들링** — 테스트 실행에서 에이전트들이 공통으로 작성하는 코드(예: 모든 테스트에서 동일한 헬퍼 스크립트를 생성)가 발견되면, 해당 코드를 `scripts/`에 미리 번들링한다. + +#### 6-4. 트리거 검증 + +각 스킬의 description이 올바르게 트리거되는지 검증한다: + +1. **Should-trigger 쿼리** (8~10개) — 스킬을 트리거해야 하는 다양한 표현 (공식적/캐주얼, 명시적/암시적) +2. **Should-NOT-trigger 쿼리** (8~10개) — 키워드가 유사하지만 이 스킬이 아닌 다른 도구/스킬이 적합한 "near-miss" 쿼리 + +**near-miss 작성 핵심:** "피보나치 함수 작성" 같이 명백히 무관한 쿼리는 테스트 가치가 없다. "이 엑셀 파일의 차트를 PNG로 추출해줘" (xlsx 스킬 vs 이미지 변환)처럼 **경계가 모호한 쿼리**가 좋은 테스트 케이스다. + +기존 스킬과의 트리거 충돌도 이 단계에서 확인한다. + +#### 6-5. 드라이런 테스트 + +- 오케스트레이터 스킬의 Phase 순서가 논리적인지 검토 +- 데이터 전달 경로에 빈 구간(dead link)이 없는지 확인 +- 모든 에이전트의 입력이 이전 Phase의 출력과 매칭되는지 확인 +- 에러 시나리오별 폴백 경로가 실행 가능한지 확인 + +#### 6-6. 테스트 시나리오 작성 + +- 오케스트레이터 스킬에 `## 테스트 시나리오` 섹션 추가 +- 정상 흐름 1개 + 에러 흐름 1개 이상 기술 + +### Phase 7: 하네스 진화 + +하네스는 한 번 만들고 끝나는 정적 산출물이 아니다. 사용자 피드백에 따라 계속 진화하는 시스템이다. + +#### 7-1. 실행 후 피드백 수집 + +매 하네스 실행 완료 후, 사용자에게 피드백을 요청한다: +- "결과에서 개선할 부분이 있나요?" +- "에이전트 팀 구성이나 워크플로우에 바꾸고 싶은 점이 있나요?" + +피드백이 없으면 넘어간다. 강요하지 않되, 반드시 기회를 제공한다. + +#### 7-2. 피드백 반영 경로 + +피드백 유형에 따라 수정 대상이 다르다: + +| 피드백 유형 | 수정 대상 | 예시 | +|-----------|----------|------| +| 결과물 품질 | 해당 에이전트의 스킬 | "분석이 너무 피상적" → 스킬에 깊이 기준 추가 | +| 에이전트 역할 | 에이전트 정의 `.md` | "보안 검토도 필요" → 새 에이전트 추가 | +| 워크플로우 순서 | 오케스트레이터 스킬 | "검증을 먼저 해야" → Phase 순서 변경 | +| 팀 구성 | 오케스트레이터 + 에이전트 | "이 둘은 합쳐도 될 듯" → 에이전트 병합 | +| 트리거 누락 | 스킬 description | "이 표현으로 하면 작동 안 함" → description 확장 | + +#### 7-3. 변경 이력 + +모든 변경은 AGENTS.md의 **변경 이력** 테이블에 기록한다 (Phase 5-4 템플릿의 "변경 이력" 섹션과 동일 테이블): + +```markdown +**변경 이력:** +| 날짜 | 변경 내용 | 대상 | 사유 | +|------|----------|------|------| +| 2026-04-05 | 초기 구성 | 전체 | - | +| 2026-04-07 | QA 에이전트 추가 | agents/qa.md | 산출물 품질 검증 부족 피드백 | +| 2026-04-10 | 톤 가이드 추가 | skills/content-creator | "너무 딱딱하다" 피드백 | +``` + +이 이력을 통해 하네스가 어떤 방향으로 진화했는지 추적하고, 퇴행(regression)을 방지한다. + +#### 7-4. 진화 트리거 + +사용자가 명시적으로 "하네스 수정해줘"라고 할 때만이 아니라, 다음 상황에서도 진화를 제안한다: +- 같은 유형의 피드백이 2회 이상 반복될 때 +- 에이전트가 반복적으로 실패하는 패턴이 발견될 때 +- 사용자가 오케스트레이터를 우회하여 수동으로 작업하는 것이 관찰될 때 + +#### 7-5. 운영/유지보수 워크플로우 + +기존 하네스의 점검·수정·동기화를 체계적으로 수행한다. Phase 0에서 "운영/유지보수" 분기로 진입했을 때 이 워크플로우를 따른다. + +**Step 1: 현황 감사** +- `.agents/agents/` 파일 목록과 오케스트레이터 스킬의 에이전트 구성 비교 → 불일치 목록 생성 +- `.agents/skills/` 디렉토리 목록과 오케스트레이터 스킬의 스킬 구성 비교 → 불일치 목록 생성 +- 감사 결과를 사용자에게 보고한다 + +**Step 2: 점진적 추가/수정** +- 사용자 요청에 따라 에이전트 추가/수정/삭제, 스킬 추가/수정/삭제를 수행한다 +- 변경은 한 번에 하나씩, 각 변경 후 즉시 Step 3(동기화)을 실행한다 + +**Step 3: AGENTS.md 변경 이력 갱신** +- 변경 이력 테이블에 날짜, 변경 내용, 대상, 사유를 기록한다 + +**Step 4: 변경 검증** +- 수정된 에이전트/스킬의 구조 검증 (Phase 6-1 기준) +- 수정 범위가 트리거에 영향을 주면 트리거 검증 (Phase 6-4 기준) +- 대규모 변경(아키텍처 변경, 에이전트 3개 이상 추가/삭제) 시 Phase 6-3(실행 테스트), 6-5(드라이런)까지 수행 +- AGENTS.md와 실제 파일의 일치 여부 최종 확인 + +## 산출물 체크리스트 + +생성 완료 후 확인: + +- [ ] `프로젝트/.agents/agents/` — **에이전트 정의 파일 필수 생성** (빌트인 타입이라도 파일 생성 필수) +- [ ] `프로젝트/.agents/skills/` — 스킬 파일들 (SKILL.md + references/) +- [ ] 오케스트레이터 스킬 1개 (데이터 흐름 + 에러 핸들링 + 테스트 시나리오 포함) +- [ ] 실행 모드 명시 (에이전트 팀 / 서브 에이전트 / 하이브리드 중 선택, 하이브리드면 Phase별 모드 기재) +- [ ] 모든 invoke_subagent 호출에 `model: "gemini-3.5-pro"` 파라미터 명시 +- [ ] 신규 에이전트 생성 전 기존 에이전트 중복 검토 완료 (Phase 3-0) +- [ ] 신규 스킬 생성 전 기존 스킬 중복 검토 완료 (Phase 4-0) +- [ ] `.gemini/commands/` — 아무것도 생성하지 않음 +- [ ] 기존 에이전트/스킬과 충돌 없음 +- [ ] 스킬 description이 적극적("pushy")으로 작성됨 — **후속 작업 키워드 포함** +- [ ] SKILL.md 본문이 500줄 이내, 초과 시 references/ 분리 +- [ ] 테스트 프롬프트 2~3개로 실행 검증 완료 +- [ ] 트리거 검증 (should-trigger + should-NOT-trigger) 완료 +- [ ] **AGENTS.md에 하네스 포인터 등록** (트리거 규칙 + 변경 이력) +- [ ] **AGENTS.md 변경 이력에 에이전트/스킬 추가/삭제/수정 기록** +- [ ] **오케스트레이터 Phase 1에 컨텍스트 확인 단계** (초기/후속/부분 재실행 판별) + +## 참고 + +- 하네스 패턴: `references/agent-design-patterns.md` +- 기존 하네스 예시 (실제 파일 전문 포함): `references/team-examples.md` +- 오케스트레이터 템플릿: `references/orchestrator-template.md` +- **스킬 작성 가이드**: `references/skill-writing-guide.md` — 작성 패턴, 예시, 데이터 스키마 표준 +- **스킬 테스트 가이드**: `references/skill-testing-guide.md` — 테스트/평가/반복 개선 방법론 +- **QA 에이전트 가이드**: `references/qa-agent-guide.md` — 빌드 하네스에 QA 에이전트를 포함할 때 참조. 통합 정합성 검증 방법론, 경계면 버그 패턴, QA 에이전트 정의 템플릿 포함. 실제 프로젝트에서 발견된 7개 버그 사례 기반. diff --git a/Gemini/gemini-harness/skills/gemini-harness/references/agent-design-patterns.md b/Gemini/gemini-harness/skills/gemini-harness/references/agent-design-patterns.md new file mode 100644 index 0000000..86ac94b --- /dev/null +++ b/Gemini/gemini-harness/skills/gemini-harness/references/agent-design-patterns.md @@ -0,0 +1,300 @@ +# Agent Team Design Patterns + +## 실행 모드: 에이전트 팀 vs 서브 에이전트 + +두 가지 실행 모드의 핵심 차이를 이해하고 적합한 모드를 선택한다. + +### 에이전트 팀 (Agent Teams) — 기본 모드 + +팀 리더가 `invoke_subagent`로 팀을 구성하고, 팀원들은 독립적인 Gemini 인스턴스로 실행된다. 팀원들은 `send_message`로 직접 통신하고, 공유 작업 목록(`tasks.md 파일 생성`/`tasks.md 업데이트`)으로 자체 조율한다. + +``` +[리더] ←→ [팀원A] ←→ [팀원B] + ↕ ↕ ↕ + └──── 공유 작업 목록 ────┘ +``` + +**핵심 도구:** +- `invoke_subagent`: 팀 생성 + 팀원 스폰 +- `send_message({to: name})`: 특정 팀원에게 메시지 +- `send_message({to: "all"})`: 브로드캐스트 (비용 높음, 드물게) +- `tasks.md 파일 생성`/`tasks.md 업데이트`: 공유 작업 목록 관리 + +**특징:** +- 팀원끼리 직접 대화, 도전, 검증 가능 +- 리더가 거치지 않고 팀원 간 정보 교환 +- 공유 작업 목록으로 자체 조율 (자체 작업 요청 가능) +- 팀원이 유휴 상태가 되면 자동으로 리더에게 알림 +- 계획 승인 모드로 위험한 작업 전 검토 가능 + +**제약:** +- 세션당 한 팀만 **활성화** 가능 (단, Phase 간에 팀을 해체하고 새 팀 구성은 가능) +- 중첩 팀 불가 (팀원이 자신의 팀 생성 불가) +- 리더 고정 (이전 불가) +- 토큰 비용 높음 + +**팀 재구성 패턴:** +Phase별로 다른 전문가 조합이 필요하면, 이전 팀의 산출물을 파일로 저장 → 팀 정리 → 새 팀 생성 순서로 진행한다. 이전 팀의 산출물은 `_workspace/` 에 보존되므로 새 팀이 Read로 접근 가능하다. + +### 서브 에이전트 (Sub-agents) — 경량 모드 + +메인 에이전트가 `Agent` 도구로 서브 에이전트를 생성한다. 서브 에이전트는 작업 결과를 메인에게만 반환하고 서로 통신하지 않는다. + +``` +[메인] → [서브A] → 결과 반환 + → [서브B] → 결과 반환 + → [서브C] → 결과 반환 +``` + +**핵심 도구:** +- `Agent(prompt, TypeName, run_in_background)`: 서브 에이전트 생성 + +**특징:** +- 가볍고 빠름 +- 결과가 메인 컨텍스트로 요약 반환 +- 토큰 효율적 + +**제약:** +- 서브 에이전트 간 통신 불가 +- 메인이 모든 조율 담당 +- 실시간 협업/도전 불가 + +### 모드 선택 의사결정 트리 + +``` +에이전트가 2개 이상인가? +├── Yes → 에이전트 간 통신이 필요한가? +│ ├── Yes → 에이전트 팀 (기본값) +│ │ 교차 검증·발견 공유·실시간 피드백으로 품질 향상. +│ │ +│ └── No → 서브 에이전트도 가능 +│ 결과 전달만 필요한 생성-검증, 전문가 풀 등. +│ +└── No (1개) → 서브 에이전트 + 단일 에이전트는 팀 구성 불필요. +``` + +> **핵심 원칙:** 에이전트 팀이 기본이다. 서브 에이전트를 선택할 때는 "팀원 간 통신이 정말 불필요한가?"를 자문한다. + +--- + +## 에이전트 팀 아키텍처 유형 + +### 1. 파이프라인 (Pipeline) +순차적 작업 흐름. 이전 에이전트의 출력이 다음 에이전트의 입력. + +``` +[분석] → [설계] → [구현] → [검증] +``` + +**적합한 경우:** 각 단계가 이전 단계의 산출물에 강하게 의존 +**예시:** 소설 집필 — 세계관 → 캐릭터 → 플롯 → 집필 → 편집 +**주의:** 병목이 전체 파이프라인을 지연시킴. 각 단계를 가능한 독립적으로 설계할 것. +**팀 모드 적합성:** 순차 의존이 강해 팀 모드의 이점이 제한적. 단, 파이프라인 내 병렬 구간이 있으면 팀 모드 유용. + +### 2. 팬아웃/팬인 (Fan-out/Fan-in) +병렬 처리 후 결과 통합. 독립적 작업을 동시 수행. + +``` + ┌→ [전문가A] ─┐ +[분배] → ├→ [전문가B] ─┼→ [통합] + └→ [전문가C] ─┘ +``` + +**적합한 경우:** 동일 입력에 대해 서로 다른 관점/영역의 분석이 필요 +**예시:** 종합 리서치 — 공식/미디어/커뮤니티/배경 동시 조사 → 통합 보고 +**주의:** 통합 단계의 품질이 전체 품질을 결정. +**팀 모드 적합성:** 에이전트 팀의 가장 자연스러운 패턴. **반드시 에이전트 팀으로 구성해야 한다.** 팀원들이 서로 발견을 공유하고 도전하며, 한 에이전트의 발견이 다른 에이전트의 조사 방향을 실시간으로 수정할 수 있어 단독 조사 대비 품질이 크게 향상된다. + +### 3. 전문가 풀 (Expert Pool) +상황에 따라 적절한 전문가를 선택 호출. + +``` +[라우터] → { 전문가A | 전문가B | 전문가C } +``` + +**적합한 경우:** 입력 유형에 따라 다른 처리가 필요 +**예시:** 코드 리뷰 — 보안/성능/아키텍처 전문가 중 해당 영역만 호출 +**주의:** 라우터의 분류 정확도가 핵심. +**팀 모드 적합성:** 서브 에이전트가 더 적합. 필요한 전문가만 호출하므로 상시 팀이 불필요. + +### 4. 생성-검증 (Producer-Reviewer) +생성 에이전트와 검증 에이전트가 쌍으로 동작. + +``` +[생성] → [검증] → (문제시) → [생성] 재실행 +``` + +**적합한 경우:** 산출물의 품질 보장이 중요하고 객관적 검증 기준이 존재 +**예시:** 웹툰 — artist 생성 → reviewer 검수 → 문제 패널 재생성 +**주의:** 무한 루프 방지를 위해 최대 재시도 횟수(2~3회) 설정 필수. +**팀 모드 적합성:** 에이전트 팀이 유용. send_message로 생성자↔검증자 간 실시간 피드백 교환. + +### 5. 감독자 (Supervisor) +중앙 에이전트가 작업 상태를 관리하며 하위 에이전트에 동적으로 작업을 분배. + +``` + ┌→ [워커A] +[감독자] ─┼→ [워커B] ← 감독자가 상태를 보고 동적 분배 + └→ [워커C] +``` + +**적합한 경우:** 작업량이 가변적이거나 런타임에 작업 분배를 결정해야 할 때 +**예시:** 대규모 코드 마이그레이션 — 감독자가 파일 목록을 분석하고 워커들에게 배치 할당 +**팬아웃과의 차이:** 팬아웃은 사전에 작업을 고정 분배, 감독자는 진행 상황을 보며 동적 조정 +**주의:** 감독자가 병목이 되지 않도록 위임 단위를 충분히 크게 설정. +**팀 모드 적합성:** 에이전트 팀의 공유 작업 목록이 감독자 패턴과 자연스럽게 매칭. tasks.md 파일 생성로 작업 등록, 팀원들이 자체 요청. + +### 6. 계층적 위임 (Hierarchical Delegation) +상위 에이전트가 하위 에이전트에 재귀적으로 위임. 복잡한 문제를 단계적으로 분해. + +``` +[총괄] → [팀장A] → [실무자A1] + → [실무자A2] + → [팀장B] → [실무자B1] +``` + +**적합한 경우:** 문제가 자연스럽게 계층적으로 분해되는 구조 +**예시:** 풀스택 앱 개발 — 총괄 → 프론트엔드팀장 → (UI/로직/테스트) + 백엔드팀장 → (API/DB/테스트) +**주의:** 깊이 3단계 이상은 지연과 컨텍스트 손실이 커짐. 2단계 이내 권장. +**팀 모드 적합성:** 에이전트 팀은 중첩 불가 (팀원이 팀 생성 불가). 1단계는 팀, 2단계는 서브 에이전트로 구현하거나, 평탄화하여 단일 팀으로 구성. + +## 복합 패턴 + +실전에서는 단일 패턴보다 복합 패턴이 흔하다: + +| 복합 패턴 | 구성 | 예시 | +|----------|------|------| +| **팬아웃 + 생성-검증** | 병렬 생성 후 각각 검증 | 다국어 번역 — 4개 언어 병렬 번역 → 각각 네이티브 리뷰어 검수 | +| **파이프라인 + 팬아웃** | 순차 단계 중 일부를 병렬화 | 분석(순차) → 구현(병렬) → 통합 테스트(순차) | +| **감독자 + 전문가 풀** | 감독자가 전문가를 동적 호출 | 고객 문의 처리 — 감독자가 문의 분류 후 적합한 전문가 할당 | + +### 복합 패턴에서의 실행 모드 + +**기본적으로 모든 복합 패턴에 에이전트 팀을 사용한다.** 팀원 간 활발한 커뮤니케이션이 결과 품질의 핵심 동력이다. + +| 시나리오 | 권장 모드 | 이유 | +|---------|----------|------| +| **리서치 + 분석** | 에이전트 팀 | 조사자 간 발견 공유, 상충 정보 실시간 토론 | +| **설계 + 구현 + 검증** | 에이전트 팀 | 설계자↔구현자↔검증자 간 피드백 루프 | +| **감독자 + 워커** | 에이전트 팀 | 공유 작업 목록으로 동적 할당, 워커 간 진행률 공유 | +| **생성 + 검증** | 에이전트 팀 | 생성자↔검증자 간 실시간 피드백으로 재작업 최소화 | + +> 서브 에이전트로의 혼합은 단일 에이전트가 완전히 격리된 단발성 작업을 수행할 때만 고려한다. + +## 에이전트 타입 선택 + +에이전트를 호출할 때 invoke_subagent 도구의 `TypeName` 파라미터로 타입을 지정한다. 에이전트 팀의 팀원도 커스텀 에이전트 정의를 사용할 수 있다. + +### 빌트인 타입 + +| 타입 | 도구 접근 | 적합한 용도 | +|------|----------|-----------| +| `general-purpose` | 전체 (WebSearch, WebFetch 포함) | 웹 조사, 범용 작업 | +| `Explore` | 읽기 전용 (Edit/Write 없음) | 코드베이스 탐색, 분석 | +| `Plan` | 읽기 전용 (Edit/Write 없음) | 아키텍처 설계, 계획 수립 | + +### 커스텀 타입 + +`.agents/agents/{name}.md`에 에이전트를 정의하면 `TypeName: "{name}"`으로 호출할 수 있다. 커스텀 에이전트는 전체 도구에 접근 가능. + +### 선택 기준 + +| 상황 | 권장 | 이유 | +|------|------|------| +| 역할이 복잡하고 여러 세션에서 재사용 | **커스텀 타입** (`.agents/agents/`) | 페르소나와 작업 원칙을 파일로 관리 | +| 단순 조사/수집이고 프롬프트만으로 충분 | **`general-purpose`** + 상세 프롬프트 | 에이전트 파일 불필요, 프롬프트에 지시 포함 | +| 코드 읽기만 필요 (분석/리뷰) | **`Explore`** | 실수로 파일 수정하는 것을 방지 | +| 설계/계획만 필요 | **`Plan`** | 분석에 집중, 코드 변경 방지 | +| 파일 수정이 필요한 구현 작업 | **커스텀 타입** | 전체 도구 접근 + 전문 지시 | + +**원칙:** 모든 에이전트는 반드시 `.agents/agents/{name}.md` 파일로 정의한다. 빌트인 타입이라도 에이전트 정의 파일을 생성하여 역할·원칙·프로토콜을 명시한다. 파일로 존재해야 다음 세션에서 재사용 가능하고, 팀 통신 프로토콜이 명시되어야 협업 품질이 보장된다. + +**모델:** 모든 에이전트는 `model: "gemini-3.5-pro"`를 사용한다. invoke_subagent 도구 호출 시 반드시 `model: "gemini-3.5-pro"` 파라미터를 명시한다. + +## 에이전트 정의 구조 + +```markdown +--- +name: agent-name +description: "1-2문장 역할 설명. 트리거 키워드 나열." +--- + +# Agent Name — 역할 한줄 요약 + +당신은 [도메인]의 [역할] 전문가입니다. + +## 핵심 역할 +1. 역할1 +2. 역할2 + +## 작업 원칙 +- 원칙1 +- 원칙2 + +## 입력/출력 프로토콜 +- 입력: [어디서 무엇을 받는지] +- 출력: [어디에 무엇을 쓰는지] +- 형식: [파일 포맷, 구조] + +## 팀 통신 프로토콜 (에이전트 팀 모드) +- 메시지 수신: [누구로부터 어떤 메시지를 받는지] +- 메시지 발신: [누구에게 어떤 메시지를 보내는지] +- 작업 요청: [공유 작업 목록에서 어떤 유형의 작업을 요청하는지] + +## 에러 핸들링 +- [실패 시 행동] +- [타임아웃 시 행동] + +## 협업 +- 다른 에이전트와의 관계 +``` + +## 에이전트 분리 기준 + +| 기준 | 분리 | 통합 | +|------|------|------| +| 전문성 | 영역이 다르면 분리 | 영역이 겹치면 통합 | +| 병렬성 | 독립 실행 가능하면 분리 | 순차 종속이면 통합 고려 | +| 컨텍스트 | 컨텍스트 부담이 크면 분리 | 가볍고 빠르면 통합 | +| 재사용성 | 다른 팀에서도 쓰면 분리 | 이 팀에서만 쓰면 통합 고려 | + +## 에이전트 재사용 설계 + +신규 에이전트 생성 전, 기존 에이전트와의 중복을 확인한다. 하네스를 반복 구축하다 보면 역할이 겹치는 에이전트가 다른 이름으로 누적되기 쉽다. + +| 상황 | 조치 | +|------|------| +| 기존 에이전트가 신규 역할을 완전히 포함 | 신규 생성 금지 — 기존 에이전트 재사용 | +| 기존 에이전트가 부분 포함이고 일반화 가능 | 기존 에이전트를 일반화하여 확장 | +| 도메인 특화가 의도된 부분 포함 | 신규 생성 진행 — 별개 에이전트로 유지 | +| 역할 범위가 완전히 다름 | 신규 생성 진행 | + +**원칙:** 하나의 에이전트가 하나의 역할에 집중할수록 재사용성이 높고 중복이 줄어든다. 역할이 두 가지 이상이면 분리할 수 있는지 먼저 검토한다. + +**기존 에이전트 일반화 시:** 해당 에이전트에 의존하는 오케스트레이터·팀 구성의 동작이 변경될 수 있다. 확장 전 의존성을 확인하고, 일반화 후 드라이런으로 기존 동작 유지를 확인한다. + +## 스킬 vs 에이전트 구분 + +| 구분 | 스킬 (Skill) | 에이전트 (Agent) | +|------|-------------|-----------------| +| 정의 | 절차적 지식 + 도구 번들 | 전문가 페르소나 + 행동 원칙 | +| 위치 | `.agents/skills/` | `.agents/agents/` | +| 트리거 | 사용자 요청 키워드 매칭 | invoke_subagent 도구로 명시적 호출 | +| 크기 | 작은~큰 (워크플로우) | 작은 (역할 정의) | +| 용도 | "어떻게 하는가" | "누가 하는가" | + +스킬은 에이전트가 작업을 수행할 때 참조하는 **절차적 가이드**. +에이전트는 스킬을 활용하는 **전문가 역할 정의**. + +## 스킬 ↔ 에이전트 연결 방식 + +에이전트가 스킬을 활용하는 3가지 방식: + +| 방식 | 구현 | 적합한 경우 | +|------|------|-----------| +| **Skill 도구 호출** | 에이전트 프롬프트에 `Skill 도구로 /skill-name 호출` 명시 | 스킬이 독립 워크플로우이고 사용자 호출 가능한 경우 | +| **프롬프트 내 인라인** | 에이전트 정의 내에 스킬 내용을 직접 포함 | 스킬이 짧고(50줄 이하) 이 에이전트 전용인 경우 | +| **레퍼런스 로드** | `Read`로 스킬의 references/ 파일을 필요 시 로드 | 스킬 내용이 크고 조건부로만 필요한 경우 | + +권장: 재사용성이 높으면 Skill 도구, 전용이면 인라인, 대용량이면 레퍼런스 로드. diff --git a/Gemini/gemini-harness/skills/gemini-harness/references/orchestrator-template.md b/Gemini/gemini-harness/skills/gemini-harness/references/orchestrator-template.md new file mode 100644 index 0000000..577358d --- /dev/null +++ b/Gemini/gemini-harness/skills/gemini-harness/references/orchestrator-template.md @@ -0,0 +1,294 @@ +# 오케스트레이터 스킬 템플릿 + +오케스트레이터는 팀 전체를 조율하는 상위 스킬이다. 실행 모드별로 3가지 템플릿을 제공한다: + +- **템플릿 A: 에이전트 팀 모드 (기본)** — 2명 이상 협업 시 최우선 선택 +- **템플릿 B: 서브 에이전트 모드 (대안)** — 팀 통신이 불필요한 경우 +- **템플릿 C: 하이브리드 모드** — Phase마다 모드를 섞어 구성 + +--- + +## 템플릿 A: 에이전트 팀 모드 (기본 · 최우선 선택) + +2명 이상의 에이전트가 협업할 때 **가장 먼저 검토하는 기본 모드**. `invoke_subagent`로 팀을 구성하고, 공유 작업 목록과 `send_message`로 조율한다. + +```markdown +--- +name: {domain}-orchestrator +description: "{도메인} 에이전트 팀을 조율하는 오케스트레이터. {초기 실행 키워드}. 후속 작업: {도메인} 결과 수정, 부분 재실행, 업데이트, 보완, 다시 실행, 이전 결과 개선 요청 시에도 반드시 이 스킬을 사용." +--- + +# {Domain} Orchestrator + +{도메인}의 에이전트 팀을 조율하여 {최종 산출물}을 생성하는 통합 스킬. + +## 실행 모드: 에이전트 팀 + +## 에이전트 구성 + +| 팀원 | 에이전트 타입 | 역할 | 스킬 | 출력 | +|------|-------------|------|------|------| +| {teammate-1} | {커스텀 또는 빌트인} | {역할} | {skill} | {output-file} | +| {teammate-2} | {커스텀 또는 빌트인} | {역할} | {skill} | {output-file} | +| ... | | | | | + +## 워크플로우 + +### Phase 0: 컨텍스트 확인 (후속 작업 지원) + +기존 산출물 존재 여부를 확인하여 실행 모드를 결정한다: + +1. `_workspace/` 디렉토리 존재 여부 확인 +2. 실행 모드 결정: + - **`_workspace/` 미존재** → 초기 실행. Phase 1로 진행 + - **`_workspace/` 존재 + 사용자가 부분 수정 요청** → 부분 재실행. 해당 에이전트만 재호출하고, 기존 산출물 중 수정 대상만 덮어쓴다 + - **`_workspace/` 존재 + 새 입력 제공** → 새 실행. 기존 `_workspace/`를 `_workspace_{YYYYMMDD_HHMMSS}/`로 이동한 뒤 Phase 1 진행 +3. 부분 재실행 시: 이전 산출물 경로를 에이전트 프롬프트에 포함하여, 에이전트가 기존 결과를 읽고 피드백을 반영하도록 지시 + +### Phase 1: 준비 +1. 사용자 입력 분석 — {무엇을 파악하는지} +2. 작업 디렉토리에 `_workspace/` 생성 + - **초기 실행**: 새 `_workspace/` 생성 + - **새 실행**: 기존 `_workspace/`를 `_workspace_{YYYYMMDD_HHMMSS}/`로 이동한 직후 새 `_workspace/` 재생성 +3. 입력 데이터를 `_workspace/00_input/`에 저장 + +### Phase 2: 팀 구성 + +``` + 1. 각 에이전트를 정의 (define_subagent): + {teammate-1}, {teammate-2} 등을 정의 + + 2. 팀 생성 (invoke_subagent): + invoke_subagent( + Subagents: [ + { TypeName: "{teammate-1}", Role: "{역할}", Prompt: "{초기 지시사항}" }, + { TypeName: "{teammate-2}", Role: "{역할}", Prompt: "{초기 지시사항}" }, + ... + ] + ) + ``` + +2. 작업 등록: + ``` + tasks.md 파일 생성(tasks: [ + { title: "{작업1}", description: "{상세}", assignee: "{teammate-1}" }, + { title: "{작업2}", description: "{상세}", assignee: "{teammate-2}" }, + { title: "{작업3}", description: "{상세}", depends_on: ["{작업1}"] }, + ... + ]) + ``` + + > 팀원당 5~6개 작업이 적정. 의존성이 있는 작업은 `depends_on`으로 명시. + +### Phase 3: {주요 작업 — 예: 조사/생성/분석} + +**실행 방식:** 팀원들이 자체 조율 + +팀원들은 공유 작업 목록에서 작업을 요청(claim)하고 독립적으로 수행한다. +리더는 진행 상황을 모니터링하며 필요 시 개입한다. + +**팀원 간 통신 규칙:** +- {teammate-1}은 {teammate-2}에게 {어떤 정보}를 send_message로 전달 +- {teammate-2}는 작업 완료 시 결과를 파일로 저장하고 리더에게 알림 +- 팀원이 다른 팀원의 결과가 필요하면 send_message로 요청 + +**산출물 저장:** + +| 팀원 | 출력 경로 | +|------|----------| +| {teammate-1} | `_workspace/{phase}_{teammate-1}_{artifact}.md` | +| {teammate-2} | `_workspace/{phase}_{teammate-2}_{artifact}.md` | + +**리더 모니터링:** +- 팀원이 유휴 상태가 되면 자동 알림 수신 +- 특정 팀원이 막혔을 때 send_message로 지시 또는 작업 재할당 +- 전체 진행률은 tasks.md 확인으로 확인 + +### Phase 4: {후속 작업 — 예: 검증/통합} +1. 모든 팀원의 작업 완료 대기 (tasks.md 확인으로 상태 확인) +2. 각 팀원의 산출물을 Read로 수집 +3. {통합/검증 로직} +4. 최종 산출물 생성: `{output-path}/{filename}` + +### Phase 5: 정리 +1. 팀원들에게 종료 요청 (send_message) +2. 팀 정리 (manage_subagents(kill)) +3. `_workspace/` 디렉토리 보존 (중간 산출물은 삭제하지 않음 — 사후 검증·감사 추적용) +4. 사용자에게 결과 요약 보고 + +> **팀 재구성이 필요한 경우:** Phase별로 다른 전문가 조합이 필요하면, 현재 팀을 manage_subagents(kill)로 정리한 뒤 새 invoke_subagent로 다음 Phase의 팀을 구성한다. 이전 팀의 산출물은 `_workspace/`에 보존되므로 새 팀이 Read로 접근 가능. + +## 데이터 흐름 + +``` +[리더] → define_subagent → invoke_subagent → [teammate-1] ←send_message→ [teammate-2] + │ │ + ↓ ↓ + artifact-1.md artifact-2.md + │ │ + └───────── Read ────────────┘ + ↓ + [리더: 통합] + ↓ + 최종 산출물 +``` + +## 에러 핸들링 + +| 상황 | 전략 | +|------|------| +| 팀원 1명 실패/중지 | 리더가 감지 → send_message로 상태 확인 → 재시작 또는 대체 팀원 생성 | +| 팀원 과반 실패 | 사용자에게 알리고 진행 여부 확인 | +| 타임아웃 | 현재까지 수집된 부분 결과 사용, 미완료 팀원 종료 | +| 팀원 간 데이터 충돌 | 출처 명시 후 병기, 삭제하지 않음 | +| 작업 상태 지연 | 리더가 tasks.md 확인으로 확인 후 수동으로 tasks.md 업데이트 | + +## 테스트 시나리오 + +### 정상 흐름 +1. 사용자가 {입력}을 제공 +2. Phase 1에서 {분석 결과} 도출 +3. Phase 2에서 팀 구성 ({N}명 팀원 + {M}개 작업) +4. Phase 3에서 팀원들이 자체 조율하며 작업 수행 +5. Phase 4에서 산출물 통합하여 최종 결과 생성 +6. Phase 5에서 팀 정리 +7. 예상 결과: `{output-path}/{filename}` 생성 + +### 에러 흐름 +1. Phase 3에서 {teammate-2}가 에러로 중지 +2. 리더가 유휴 알림 수신 +3. send_message로 상태 확인 → 재시작 시도 +4. 재시작 실패 시 {teammate-2} 작업을 {teammate-1}에게 재할당 +5. 나머지 결과로 Phase 4 진행 +6. 최종 보고서에 "{teammate-2} 영역 일부 미수집" 명시 +``` + +--- + +## 템플릿 B: 서브 에이전트 모드 (대안) + +팀 통신 오버헤드가 불필요한 경우. `Agent` 도구로 직접 호출하고 반환값으로 결과를 수집한다. + +```markdown +--- +name: {domain}-orchestrator +description: "{도메인} 에이전트를 조율하는 오케스트레이터. {초기 실행 키워드}. 후속 작업 키워드 포함." +--- + +## 실행 모드: 서브 에이전트 + +## 에이전트 구성 + +| 에이전트 | TypeName | 역할 | 스킬 | 출력 | +|---------|--------------|------|------|------| +| {agent-1} | {빌트인 또는 커스텀} | {역할} | {skill} | {output-file} | +| {agent-2} | ... | ... | ... | ... | + +## 워크플로우 + +### Phase 0: 컨텍스트 확인 +(Template A와 동일 — `_workspace/` 존재 여부 분기) + +### Phase 1: 준비 +1. 입력 분석 +2. `_workspace/` 생성 (초기 실행 시, 또는 새 실행에서 기존 `_workspace/`를 보관 디렉토리로 이동한 직후) + +### Phase 2: 병렬 실행 +단일 메시지에서 N개 invoke_subagent 도구를 동시 호출: + +| 에이전트 | 입력 | 출력 | model | run_in_background | +|---------|------|------|-------|-------------------| +| {agent-1} | {소스} | `_workspace/{phase}_{agent}_{artifact}.md` | opus | true | +| {agent-2} | {소스} | `_workspace/{phase}_{agent}_{artifact}.md` | opus | true | + +### Phase 3: 통합 +1. 각 에이전트의 반환값 수집 +2. 파일 기반 산출물은 Read로 수집 +3. 통합 로직 적용 → 최종 산출물 + +### Phase 4: 정리 +1. `_workspace/` 보존 +2. 결과 요약 보고 + +## 에러 핸들링 +- 에이전트 1개 실패: 1회 재시도. 재실패 시 누락 명시하고 진행 +- 과반 실패: 사용자에게 알리고 진행 여부 확인 +- 타임아웃: 현재까지 수집된 부분 결과 사용 +``` + +--- + +## 템플릿 C: 하이브리드 모드 + +Phase마다 다른 실행 모드를 사용한다. 각 Phase 상단에 `**실행 모드:** {팀 | 서브}`를 명시한다. + +```markdown +--- +name: {domain}-orchestrator +description: "{도메인} 오케스트레이터 (하이브리드). {키워드}. 후속 작업 키워드 포함." +--- + +## 실행 모드: 하이브리드 + +| Phase | 모드 | 이유 | +|-------|------|------| +| Phase 2 (병렬 수집) | 서브 에이전트 | 독립 자료 수집, 팀 통신 불필요 | +| Phase 3 (합의 통합) | 에이전트 팀 | 상충 데이터 토론·합의 필요 | +| Phase 4 (독립 검증) | 서브 에이전트 | QA 에이전트 1명이 객관 검증 | + +## 워크플로우 + +### Phase 2: 병렬 자료 수집 +**실행 모드:** 서브 에이전트 + +단일 메시지에서 invoke_subagent 도구로 N개 에이전트 병렬 호출 (`run_in_background: true`). +각 결과는 `_workspace/02_{agent}_raw.md`에 저장. + +### Phase 3: 합의 기반 통합 +**실행 모드:** 에이전트 팀 + +1. `define_subagent` 후 `invoke_subagent`로 통합 팀 구성 (editor + fact-checker + synthesizer) +2. `tasks.md 파일 생성`로 작업 분배 — 모두 Phase 2의 `_workspace/02_*` 파일을 Read +3. 팀원들이 `send_message`로 상충 데이터를 논의, 파일 기반으로 합의안 도출 +4. 최종 통합본 `_workspace/03_integrated.md` 생성 +5. `manage_subagents(kill)`로 팀 정리 + +### Phase 4: 독립 검증 +**실행 모드:** 서브 에이전트 + +단일 QA 서브 에이전트가 `_workspace/03_integrated.md`를 입력으로 받아 검증 보고서 생성. +``` + +**하이브리드 전환 규칙:** +- 팀 → 서브: 팀을 반드시 `manage_subagents(kill)`로 정리한 후 invoke_subagent 도구 호출 +- 서브 → 팀: 서브 에이전트의 파일 산출물을 팀원들에게 Read 경로로 전달 +- 팀 → 팀: 이전 팀을 정리한 후 새 `invoke_subagent` (세션당 1팀만 활성 가능) + +--- + +## 작성 원칙 + +1. **실행 모드를 먼저 명시** — 오케스트레이터 상단에 "에이전트 팀" / "서브 에이전트" / "하이브리드" 중 하나 명시. 하이브리드면 Phase별 모드 표 필수 +2. **팀 모드는 define_subagent/invoke_subagent/send_message/tasks.md 파일 생성 사용법을 구체적으로** — 팀 구성, 작업 등록, 통신 규칙 +3. **서브 모드는 invoke_subagent 도구 파라미터를 완전히 명시** — name, TypeName, prompt, run_in_background, model +4. **파일 경로는 절대적으로** — 상대 경로 금지, `_workspace/` 기준 명확한 경로 +5. **Phase 간 의존성 명시** — 어떤 Phase가 어떤 Phase의 결과에 의존하는지. 하이브리드는 모드 전환 지점을 특히 강조 +6. **에러 핸들링은 현실적으로** — "모든 것이 성공한다"고 가정하지 않음 +7. **테스트 시나리오 필수** — 정상 1 + 에러 1 이상 + +## description 작성 시 후속 작업 키워드 + +오케스트레이터 description은 초기 실행 키워드만으로는 부족하다. 다음 후속 작업 표현을 반드시 포함하라: + +- 재실행/다시 실행/업데이트/수정/보완 +- "{도메인}의 {부분}만 다시" +- "이전 결과 기반으로", "결과 개선" +- 도메인 관련 일상적 요청 (예: 런치 전략 하네스라면 "런치", "홍보", "트렌딩" 등) + +후속 키워드가 없으면 첫 실행 후 하네스가 사실상 죽은 코드가 된다. + +## 실제 오케스트레이터 참고 + +팬아웃/팬인 패턴의 오케스트레이터 기본 구조: +준비 → Phase 0(컨텍스트 확인) → define_subagent + invoke_subagent + tasks.md 파일 생성 → N개 팀원 병렬 실행 → Read + 통합 → 정리. +`references/team-examples.md`의 리서치 팀 예시를 참조. diff --git a/Gemini/gemini-harness/skills/gemini-harness/references/qa-agent-guide.md b/Gemini/gemini-harness/skills/gemini-harness/references/qa-agent-guide.md new file mode 100644 index 0000000..ef6f12d --- /dev/null +++ b/Gemini/gemini-harness/skills/gemini-harness/references/qa-agent-guide.md @@ -0,0 +1,228 @@ +# QA 에이전트 설계 가이드 + +빌드 하네스에 QA 에이전트를 포함할 때 참고하는 가이드. 실제 프로젝트(SatangSlide)에서 발견된 버그 패턴과 그 근본 원인 분석을 바탕으로, QA가 놓치기 쉬운 결함을 체계적으로 잡는 검증 방법론을 제공한다. + +--- + +## 목차 + +1. QA 에이전트가 놓치는 결함의 패턴 +2. 통합 정합성 검증 (Integration Coherence Verification) +3. QA 에이전트 설계 원칙 +4. 검증 체크리스트 템플릿 +5. QA 에이전트 정의 템플릿 + +--- + +## 1. QA 에이전트가 놓치는 결함의 패턴 + +### 1-1. 경계면 불일치 (Boundary Mismatch) + +가장 빈번한 결함. 두 컴포넌트가 각각 "올바르게" 구현되어 있지만, 연결 지점에서 계약이 어긋남. + +| 경계면 | 불일치 예시 | 놓치는 이유 | +|--------|-----------|-----------| +| API 응답 → 프론트 훅 | API가 `{ projects: [...] }` 반환, 훅이 `SlideProject[]` 기대 | 각각 개별 검증하면 정상, 교차 비교 안 함 | +| API 응답 필드명 → 타입 정의 | API가 `thumbnailUrl`(camelCase), 타입이 `thumbnail_url`(snake_case) | TypeScript 제네릭으로 캐스팅하면 컴파일러가 못 잡음 | +| 파일 경로 → 링크 href | 페이지가 `/dashboard/create`에 있는데 링크가 `/create`로 지정 | 파일 구조와 href를 교차 비교하지 않음 | +| 상태 전이 맵 → 실제 status 업데이트 | 맵에 `generating_template → template_approved` 정의, 코드에서 전환 누락 | 맵 존재 확인만 하고, 모든 업데이트 코드를 추적하지 않음 | +| API 엔드포인트 → 프론트 훅 | API 존재하지만 대응 훅 없음 (호출 안 됨) | API 목록과 훅 목록을 1:1 매핑하지 않음 | +| 즉시 응답 → 비동기 결과 | API가 즉시 `{ status }` 반환, 프론트가 `data.failedIndices` 접근 | 동기/비동기 응답 구분 없이 타입만 확인 | + +### 1-2. 왜 정적 코드 리뷰로 못 잡나 + +- **TypeScript 제네릭의 한계**: `fetchJson()` — 런타임 응답이 `{ projects: [...] }`여도 컴파일 통과 +- **`npm run build` 통과 ≠ 정상 동작**: 타입 캐스팅, `any`, 제네릭이 사용되면 빌드는 성공하지만 런타임에 실패 +- **존재 검증 vs 연결 검증의 차이**: "API가 있는가?"와 "API의 응답이 호출측의 기대와 일치하는가?"는 전혀 다른 검증 + +--- + +## 2. 통합 정합성 검증 (Integration Coherence Verification) + +QA 에이전트에 반드시 포함해야 하는 **교차 비교 검증** 영역. + +### 2-1. API 응답 ↔ 프론트 훅 타입 교차 검증 + +**방법**: 각 API route의 `NextResponse.json()` 호출부와 대응 훅의 `fetchJson` 타입 파라미터를 비교. + +``` +검증 단계: +1. API route에서 NextResponse.json()에 전달하는 객체의 shape 추출 +2. 대응 훅에서 fetchJson의 T 타입 확인 +3. shape과 T가 일치하는지 비교 +4. 래핑 여부 확인 (API가 { data: [...] }를 반환하면 훅이 .data를 꺼내는지) +``` + +**특히 주의할 패턴:** +- 페이지네이션 API: `{ items: [], total, page }` vs 프론트가 배열 기대 +- snake_case DB 필드 → camelCase API 응답 → 프론트 타입 정의 간 불일치 +- 즉시 응답 (202 Accepted) vs 최종 결과의 shape 차이 + +### 2-2. 파일 경로 ↔ 링크/라우터 경로 매핑 + +**방법**: `src/app/` 하위 page 파일의 URL 경로를 추출하고, 코드 내 모든 `href`, `router.push()`, `redirect()` 값과 대조. + +``` +검증 단계: +1. src/app/ 하위 page.tsx 파일 경로에서 URL 패턴 추출 + - (group) → URL에서 제거 + - [param] → 동적 세그먼트 +2. 코드 내 모든 href=, router.push(, redirect( 값 수집 +3. 각 링크가 실제 존재하는 page 경로와 매칭되는지 확인 +4. route group 내부 페이지의 URL 접두사 주의 (예: dashboard/ 하위) +``` + +### 2-3. 상태 전이 완전성 추적 + +**방법**: 코드에서 모든 `status:` 업데이트를 추출하여 상태 전이 맵과 대조. + +``` +검증 단계: +1. 상태 전이 맵(STATE_TRANSITIONS)에서 허용된 전이 목록 추출 +2. 모든 API route에서 .update({ status: "..." }) 패턴 검색 +3. 각 전이가 맵에 정의되어 있는지 확인 +4. 맵에 정의된 전이 중 코드에서 실행되지 않는 것 식별 (죽은 전이) +5. 특히: 중간 상태(예: generating_template)에서 최종 상태(template_approved)로의 전환이 누락되지 않았는지 +``` + +### 2-4. API 엔드포인트 ↔ 프론트 훅 1:1 매핑 + +**방법**: 모든 API route와 프론트 훅을 나열하여 짝이 맞는지 확인. + +``` +검증 단계: +1. src/app/api/ 하위 route.ts에서 HTTP 메서드별 엔드포인트 목록 추출 +2. src/hooks/ 하위 use*.ts에서 fetch 호출 URL 목록 추출 +3. API 엔드포인트 중 훅에서 호출하지 않는 것 식별 → "사용 안 됨" 플래그 +4. "사용 안 됨"이 의도적인지 (관리 API 등) 아닌지 (호출 누락) 판단 +``` + +--- + +## 3. QA 에이전트 설계 원칙 + +### 3-1. Explore 타입이 아닌 general-purpose 타입을 사용하라 + +QA 에이전트가 `Explore` 타입이면 읽기만 가능하다. 하지만 효과적인 QA는: +- Grep으로 패턴 검색 (모든 `NextResponse.json()` 추출) +- 스크립트 실행으로 자동 대조 (API shape vs 훅 타입) +- 필요 시 수정까지 가능 + +**권장**: `general-purpose` 타입으로 설정하되, 에이전트 정의에서 "검증 → 리포트 → 수정 요청" 프로토콜을 명시. + +### 3-2. 체크리스트는 "존재 확인"보다 "교차 비교"를 우선하라 + +| 약한 체크리스트 | 강한 체크리스트 | +|---------------|---------------| +| API 엔드포인트가 존재하는가? | API 엔드포인트의 응답 shape과 대응 훅의 타입이 일치하는가? | +| 상태 전이 맵이 정의되어 있는가? | 모든 status 업데이트 코드가 맵의 전이와 일치하는가? | +| 페이지 파일이 존재하는가? | 코드 내 모든 링크가 실제 존재하는 페이지를 가리키는가? | +| TypeScript strict mode인가? | 제네릭 캐스팅으로 우회된 타입 안전성이 없는가? | + +### 3-3. "양쪽을 동시에 읽어라" 원칙 + +QA가 경계면 버그를 잡으려면, 한쪽만 읽어선 안 된다. 반드시: +- API route **와** 대응 훅을 **같이** 읽고 +- 상태 전이 맵 **와** 실제 업데이트 코드를 **같이** 읽고 +- 파일 구조 **와** 링크 경로를 **같이** 읽어야 한다 + +에이전트 정의에 이 원칙을 명시적으로 기재하라. + +### 3-4. QA는 빌드 후가 아니라, 각 모듈 완성 직후에 실행하라 + +오케스트레이터에서 QA를 "Phase 4: 전체 완성 후"에만 배치하면: +- 버그가 누적되어 수정 비용이 높아짐 +- 초기 경계면 불일치가 후속 모듈에 전파됨 + +**권장 패턴**: 각 백엔드 API 완성 시 즉시 해당 API + 대응 훅의 교차 검증 수행 (incremental QA). + +--- + +## 4. 검증 체크리스트 템플릿 + +QA 에이전트 정의에 포함할 웹 애플리케이션용 통합 정합성 체크리스트. + +```markdown +### 통합 정합성 검증 (웹 앱) + +#### API ↔ 프론트엔드 연결 +- [ ] 모든 API route의 응답 shape과 대응 훅의 제네릭 타입이 일치 +- [ ] 래핑된 응답({ items: [...] })은 훅에서 unwrap하는지 확인 +- [ ] snake_case ↔ camelCase 변환이 일관되게 적용 +- [ ] 즉시 응답(202)과 최종 결과의 shape이 프론트에서 구분되는지 확인 +- [ ] 모든 API 엔드포인트에 대응하는 프론트 훅이 존재하고 실제로 호출됨 + +#### 라우팅 정합성 +- [ ] 코드 내 모든 href/router.push 값이 실제 page 파일 경로와 매칭 +- [ ] route group ((group))이 URL에서 제거되는 것을 고려한 경로 검증 +- [ ] 동적 세그먼트([id])가 올바른 파라미터로 채워지는지 확인 + +#### 상태 머신 정합성 +- [ ] 정의된 모든 상태 전이가 코드에서 실행됨 (죽은 전이 없음) +- [ ] 코드의 모든 status 업데이트가 전이 맵에 정의됨 (무단 전이 없음) +- [ ] 중간 상태에서 최종 상태로의 전환이 누락되지 않음 +- [ ] 프론트에서 상태 기반 분기(if status === "X")의 X가 실제 도달 가능 + +#### 데이터 흐름 정합성 +- [ ] DB 스키마 필드명과 API 응답 필드명의 매핑이 일관됨 +- [ ] 프론트 타입 정의와 API 응답의 필드명이 일치 +- [ ] 옵셔널 필드에 대한 null/undefined 처리가 양쪽에서 일관됨 +``` + +--- + +## 5. QA 에이전트 정의 템플릿 + +빌드 하네스의 QA 에이전트에 포함할 핵심 섹션. + +```markdown +--- +name: qa-inspector +description: "QA 검증 전문가. 스펙 준수, 통합 정합성, 디자인 품질을 검증." +--- + +# QA Inspector + +## 핵심 역할 +스펙 대비 구현 품질과 **모듈 간 통합 정합성**을 검증한다. + +## 검증 우선순위 + +1. **통합 정합성** (가장 높음) — 경계면 불일치가 런타임 에러의 주요 원인 +2. **기능 스펙 준수** — API/상태머신/데이터모델 +3. **디자인 품질** — 색상/타이포/반응형 +4. **코드 품질** — 미사용 코드, 명명 규칙 + +## 검증 방법: "양쪽 동시 읽기" + +경계면 검증은 반드시 **양쪽 코드를 동시에 열어** 비교한다: + +| 검증 대상 | 왼쪽 (생산자) | 오른쪽 (소비자) | +|----------|-------------|---------------| +| API 응답 shape | route.ts의 NextResponse.json() | hooks/의 fetchJson | +| 라우팅 | src/app/ page 파일 경로 | href, router.push 값 | +| 상태 전이 | STATE_TRANSITIONS 맵 | .update({ status }) 코드 | +| DB → API → UI | 테이블 컬럼명 | API 응답 필드 → 타입 정의 | + +## 팀 통신 프로토콜 + +- 발견 즉시 해당 에이전트에게 구체적 수정 요청 (파일:라인 + 수정 방법) +- 경계면 이슈는 양쪽 에이전트 **모두**에게 알림 +- 리더에게: 검증 리포트 (통과/실패/미검증 항목 구분) +``` + +--- + +## 실제 사례: SatangSlide에서 발견된 버그 + +이 가이드의 모든 내용은 아래 실제 버그에서 추출한 교훈이다: + +| 버그 | 경계면 | 원인 | +|------|--------|------| +| `projects?.filter is not a function` | API→훅 | API가 `{projects:[]}` 반환, 훅이 배열 기대 | +| 대시보드 모든 링크 404 | 파일경로→href | `/dashboard/` 접두사 누락 | +| 테마 이미지 안 보임 | API→컴포넌트 | `thumbnailUrl` vs `thumbnail_url` | +| 테마 선택 저장 안 됨 | API→훅 | select-theme API 존재, 훅 없음 | +| 생성 페이지 영원히 대기 | 상태전이→코드 | `template_approved` 전이 코드 누락 | +| `data.failedIndices` 크래시 | 즉시응답→프론트 | 백그라운드 결과를 즉시 응답에서 접근 | +| 완료 후 슬라이드 보기 404 | 파일경로→href | `/projects/` → `/dashboard/projects/` | diff --git a/Gemini/gemini-harness/skills/gemini-harness/references/skill-testing-guide.md b/Gemini/gemini-harness/skills/gemini-harness/references/skill-testing-guide.md new file mode 100644 index 0000000..cd65622 --- /dev/null +++ b/Gemini/gemini-harness/skills/gemini-harness/references/skill-testing-guide.md @@ -0,0 +1,307 @@ +# 스킬 테스트 & 반복 개선 가이드 + +하네스에서 생성한 스킬의 품질을 검증하고 반복적으로 개선하는 방법론. SKILL.md Phase 6의 보충 레퍼런스. + +--- + +## 목차 + +1. [테스트 프레임워크 개요](#1-테스트-프레임워크-개요) +2. [테스트 프롬프트 작성법](#2-테스트-프롬프트-작성법) +3. [실행 테스트: With-skill vs Baseline](#3-실행-테스트-with-skill-vs-baseline) +4. [정량적 평가: Assertion 기반 채점](#4-정량적-평가-assertion-기반-채점) +5. [전문 에이전트 활용](#5-전문-에이전트-활용) +6. [반복 개선 루프](#6-반복-개선-루프) +7. [Description 트리거 검증](#7-description-트리거-검증) +8. [워크스페이스 구조](#8-워크스페이스-구조) + +--- + +## 1. 테스트 프레임워크 개요 + +스킬 품질 검증은 **정성적 평가**와 **정량적 평가**의 조합이다. + +| 평가 유형 | 방법 | 적합한 스킬 | +|----------|------|-----------| +| **정성적** | 사용자가 산출물을 직접 리뷰 | 문체, 디자인, 창작물 등 주관적 품질 | +| **정량적** | assertion 기반 자동 채점 | 파일 생성, 데이터 추출, 코드 생성 등 객관적 검증 가능 | + +핵심 루프: **작성 → 테스트 실행 → 평가 → 개선 → 재테스트** + +--- + +## 2. 테스트 프롬프트 작성법 + +### 원칙 + +테스트 프롬프트는 **실제 사용자가 입력할 법한 구체적이고 자연스러운 문장**이어야 한다. 추상적이거나 인공적인 프롬프트는 테스트 가치가 낮다. + +### 나쁜 예 + +``` +"PDF를 처리하라" +"데이터를 추출하라" +"차트를 생성하라" +``` + +### 좋은 예 + +``` +"다운로드 폴더에 있는 'Q4_매출_최종_v2.xlsx'에서 C열(매출)과 D열(비용)을 +사용해서 이익률(%) 열을 추가해줘. 그리고 이익률 기준으로 내림차순 정렬." +``` + +``` +"이 PDF에서 3페이지 표를 추출해서 CSV로 변환해줘. 표 헤더가 2줄로 +되어 있어서 첫 번째 줄은 카테고리, 두 번째 줄이 실제 열 이름이야." +``` + +### 프롬프트 다양성 + +- **공식적 / 캐주얼** 톤 혼합 +- **명시적 / 암시적** 의도 혼합 (파일 형식을 직접 말하는 경우 vs 맥락으로 추론해야 하는 경우) +- **단순 / 복잡** 작업 혼합 +- 일부는 약어, 오타, 캐주얼한 표현 포함 + +### 커버리지 + +2~3개 프롬프트로 시작하되, 다음을 커버하도록 설계: +- 핵심 사용 사례 1개 +- 엣지 케이스 1개 +- (선택) 복합 작업 1개 + +--- + +## 3. 실행 테스트: With-skill vs Baseline + +### 3-1. 비교 실행 구조 + +각 테스트 프롬프트에 대해 두 개의 서브에이전트를 **동시에** 스폰한다: + +**With-skill 실행:** +``` +프롬프트: "{테스트 프롬프트}" +스킬 경로: {스킬 경로} +출력 경로: _workspace/iteration-N/eval-{id}/with_skill/outputs/ +``` + +**Baseline 실행:** +``` +프롬프트: "{테스트 프롬프트}" (동일) +스킬: 없음 +출력 경로: _workspace/iteration-N/eval-{id}/without_skill/outputs/ +``` + +### 3-2. Baseline 선택 + +| 상황 | Baseline | +|------|----------| +| 새 스킬 생성 | 스킬 없이 같은 프롬프트 실행 | +| 기존 스킬 개선 | 수정 전 스킬 버전 (스냅샷 보존) | + +### 3-3. 타이밍 데이터 캡처 + +서브에이전트 완료 알림에서 `total_tokens`와 `duration_ms`를 **즉시** 저장한다. 이 데이터는 알림 시점에만 접근 가능하고 이후 복구할 수 없다. + +```json +{ + "total_tokens": 84852, + "duration_ms": 23332, + "total_duration_seconds": 23.3 +} +``` + +--- + +## 4. 정량적 평가: Assertion 기반 채점 + +### 4-1. Assertion 작성 + +산출물이 객관적으로 검증 가능한 경우, 자동 채점을 위한 assertion을 정의한다. + +**좋은 assertion:** +- 객관적으로 참/거짓 판별 가능 +- 서술적인 이름으로 결과만 봐도 무엇을 검사하는지 명확 +- 스킬의 핵심 가치를 검증 + +**나쁜 assertion:** +- 스킬 유무와 무관하게 항상 통과하는 것 (예: "출력이 존재한다") +- 주관적 판단이 필요한 것 (예: "잘 작성되었다") + +### 4-2. 프로그래밍 가능한 검증 + +assertion이 코드로 검증 가능하면 스크립트로 작성한다. 눈으로 확인하는 것보다 빠르고 신뢰성 있으며, iteration마다 재사용 가능. + +### 4-3. Non-discriminating assertion 주의 + +"두 구성 모두에서 100% 통과"하는 assertion은 스킬의 차별적 가치를 측정하지 못한다. 이런 assertion을 발견하면 제거하거나, 더 도전적인 assertion으로 교체한다. + +### 4-4. 채점 결과 스키마 + +```json +{ + "expectations": [ + { + "text": "이익률 열이 추가됨", + "passed": true, + "evidence": "E열에 'profit_margin_pct' 열 확인" + }, + { + "text": "이익률 기준 내림차순 정렬", + "passed": false, + "evidence": "정렬 없이 원본 순서 유지됨" + } + ], + "summary": { + "passed": 1, + "failed": 1, + "total": 2, + "pass_rate": 0.50 + } +} +``` + +--- + +## 5. 전문 에이전트 활용 + +테스트/평가 과정에서 전문 역할의 에이전트를 활용하면 품질이 향상된다. + +### 5-1. Grader (채점자) + +assertion 기반 채점을 수행하고, 산출물에서 검증 가능한 주장(claim)을 추출하여 교차 검증한다. + +**역할:** +- assertion별 통과/실패 판정 + 근거 제시 +- 산출물에서 사실적 주장을 추출하고 검증 +- eval 자체의 품질에 대한 피드백 (assertion이 너무 쉽거나 모호한 경우 제안) + +### 5-2. Comparator (블라인드 비교자) + +두 산출물을 A/B로 익명화하여, 어떤 것이 스킬을 사용한 결과인지 모르는 상태에서 품질을 판정한다. + +**활용 시점:** "새 버전이 정말 더 나은가?"를 엄밀하게 확인하고 싶을 때. 일반적인 반복 개선에서는 생략 가능. + +**판정 기준:** +- 내용: 정확성, 완성도 +- 구조: 조직화, 포맷팅, 사용성 +- 종합 점수 + +### 5-3. Analyzer (분석자) + +벤치마크 데이터에서 통계적 패턴을 분석한다: +- Non-discriminating assertion (두 구성 모두 통과 → 차별력 없음) +- 고분산 eval (결과가 실행마다 크게 달라짐 → 불안정) +- 시간/토큰 트레이드오프 (스킬이 품질은 높이지만 비용도 높이는 경우) + +--- + +## 6. 반복 개선 루프 + +### 6-1. 피드백 수집 + +사용자에게 산출물을 보여주고 피드백을 받는다. 빈 피드백은 "이상 없음"으로 해석한다. + +### 6-2. 개선 원칙 + +1. **피드백을 일반화하라** — 테스트 예시에만 맞는 좁은 수정은 오버피팅이다. 원리 수준에서 수정한다. +2. **무게를 벌지 않는 것은 제거하라** — 트랜스크립트를 읽고, 스킬이 에이전트에게 비생산적인 작업을 시키고 있다면 해당 부분을 삭제한다. +3. **Why를 설명하라** — 사용자의 피드백이 간결하더라도, 왜 그것이 중요한지 이해하고 그 이해를 스킬에 반영한다. +4. **반복 작업은 번들링하라** — 모든 테스트 실행에서 동일한 헬퍼 스크립트가 생성되면, `scripts/`에 미리 포함한다. + +### 6-3. 반복 절차 + +``` +1. 스킬 수정 +2. 새 iteration-N+1/ 디렉토리에 모든 테스트 케이스 재실행 +3. 사용자에게 결과 제시 (이전 iteration과 비교) +4. 피드백 수집 +5. 다시 수정 → 반복 +``` + +**종료 조건:** +- 사용자가 만족 +- 피드백이 모두 비어 있음 (모든 산출물 이상 없음) +- 의미 있는 개선이 더 이상 없음 + +### 6-4. 초안 → 재검토 패턴 + +스킬 수정 시, 초안을 작성한 후 **새로운 시각으로 다시 읽고** 개선한다. 한 번에 완벽하게 쓰려 하지 말고, 초안-검토 사이클을 거친다. + +--- + +## 7. Description 트리거 검증 + +### 7-1. 트리거 Eval 쿼리 작성 + +20개의 eval 쿼리를 작성한다 — should-trigger 10개 + should-NOT-trigger 10개. + +**쿼리 품질 기준:** +- 실제 사용자가 입력할 법한 구체적이고 자연스러운 문장 +- 파일 경로, 개인적 맥락, 열 이름, 회사명 등 구체적 디테일 포함 +- 길이, 톤, 형식 다양하게 혼합 +- 명확한 정답보다 **경계 케이스(edge case)**에 집중 + +**Should-trigger 쿼리 (8~10개):** +- 다양한 표현의 같은 의도 (공식적/캐주얼) +- 스킬/파일 유형을 명시적으로 말하지 않지만 분명히 필요한 경우 +- 비주류 사용 사례 +- 다른 스킬과 경쟁하지만 이 스킬이 이겨야 하는 경우 + +**Should-NOT-trigger 쿼리 (8~10개):** +- **Near-miss가 핵심** — 키워드가 유사하지만 다른 도구/스킬이 적합한 쿼리 +- 명백히 무관한 쿼리("피보나치 함수 작성")는 테스트 가치 없음 +- 인접 도메인, 모호한 표현, 키워드 겹침 but 맥락이 다른 경우 + +### 7-2. 기존 스킬 충돌 검증 + +새 스킬의 description이 기존 스킬의 트리거 영역과 겹치지 않는지 확인한다: + +1. 기존 스킬 목록의 description을 수집 +2. 새 스킬의 should-trigger 쿼리가 기존 스킬을 잘못 트리거하지 않는지 확인 +3. 충돌 발견 시 description의 경계 조건을 더 명확히 기술 + +### 7-3. 자동 최적화 (선택적 고급 기능) + +description 최적화가 필요한 경우: + +1. 20개 eval 쿼리를 Train(60%) / Test(40%) split +2. 현재 description으로 트리거 정확도 측정 +3. 실패 케이스를 분석하여 개선된 description 생성 +4. Test set 기준으로 best description 선택 (Train set 기준이 아님 — 과적합 방지) +5. 최대 5회 반복 + +> 이 과정은 `claude -p`를 사용하는 자동화 스크립트로 수행한다. 토큰 비용이 높으므로 스킬이 충분히 안정화된 후 최종 단계에서 실행한다. + +--- + +## 8. 워크스페이스 구조 + +테스트/평가 결과를 체계적으로 관리하는 디렉토리 구조: + +``` +{skill-name}-workspace/ +├── iteration-1/ +│ ├── eval-descriptive-name-1/ +│ │ ├── eval_metadata.json +│ │ ├── with_skill/ +│ │ │ ├── outputs/ +│ │ │ ├── timing.json +│ │ │ └── grading.json +│ │ └── without_skill/ +│ │ ├── outputs/ +│ │ ├── timing.json +│ │ └── grading.json +│ ├── eval-descriptive-name-2/ +│ │ └── ... +│ └── benchmark.json +├── iteration-2/ +│ └── ... +└── evals/ + └── evals.json +``` + +**규칙:** +- eval 디렉토리는 숫자가 아닌 **서술적 이름** 사용 (예: `eval-multi-page-table-extraction`) +- 각 iteration은 독립 디렉토리에 보존 (이전 iteration 덮어쓰기 금지) +- `_workspace/`는 삭제하지 않음 — 사후 검증 및 감사 추적용 diff --git a/Gemini/gemini-harness/skills/gemini-harness/references/skill-writing-guide.md b/Gemini/gemini-harness/skills/gemini-harness/references/skill-writing-guide.md new file mode 100644 index 0000000..aeade33 --- /dev/null +++ b/Gemini/gemini-harness/skills/gemini-harness/references/skill-writing-guide.md @@ -0,0 +1,298 @@ +# 스킬 작성 가이드 + +하네스에서 생성하는 스킬의 품질을 높이기 위한 상세 작성 가이드. SKILL.md Phase 4의 보충 레퍼런스. + +--- + +## 목차 + +1. [Description 작성 패턴](#1-description-작성-패턴) +2. [본문 작성 스타일](#2-본문-작성-스타일) +3. [출력 형식 정의 패턴](#3-출력-형식-정의-패턴) +4. [예시 작성 패턴](#4-예시-작성-패턴) +5. [Progressive Disclosure 패턴](#5-progressive-disclosure-패턴) +6. [스크립트 번들링 판단 기준](#6-스크립트-번들링-판단-기준) +7. [데이터 스키마 표준](#7-데이터-스키마-표준) +8. [스킬에 포함하지 않을 것](#8-스킬에-포함하지-않을-것) + +--- + +## 1. Description 작성 패턴 + +Description은 스킬의 유일한 트리거 메커니즘이다. Claude는 `available_skills` 목록에서 name + description만 보고 스킬 사용 여부를 결정한다. + +### 트리거 메커니즘 이해 + +Claude는 자신의 기본 도구로 쉽게 처리할 수 있는 단순 작업에는 스킬을 호출하지 않는 경향이 있다. "이 PDF 읽어줘" 같은 단순 요청은 description이 완벽해도 트리거되지 않을 수 있다. 복잡하고 다단계이며 전문적인 작업일수록 스킬 트리거 확률이 높다. + +### 작성 원칙 + +1. **스킬이 하는 일** + **구체적 트리거 상황**을 모두 기술 +2. 유사하지만 트리거하면 안 되는 경우를 구분하는 경계 조건 명시 +3. 약간 "pushy"하게 — Claude가 트리거를 보수적으로 판단하는 경향을 보상 + +### 좋은 예시 + +```yaml +description: "PDF 파일 읽기, 텍스트/테이블 추출, 병합, 분할, 회전, 워터마크, + 암호화/복호화, OCR 등 모든 PDF 작업을 수행. .pdf 파일을 언급하거나 + PDF 산출물을 요청하면 반드시 이 스킬을 사용할 것. 단순히 PDF를 + '읽어달라'는 요청이 아닌 변환/편집/분석이 필요할 때 특히 유용." +``` + +```yaml +description: "엑셀/CSV/TSV 파일의 열 추가, 수식 계산, 서식, 차트, + 데이터 정제를 포함한 모든 스프레드시트 작업. 사용자가 스프레드시트 + 파일을 언급하면 — 심지어 캐주얼하게('다운로드 폴더의 xlsx')라고만 + 해도 — 이 스킬을 사용할 것." +``` + +### 나쁜 예시 + +- `"데이터를 처리하는 스킬"` — 너무 모호, 어떤 파일/작업인지 불분명 +- `"PDF 관련 작업"` — 구체적 동작 나열 없음, 트리거 상황 미기술 + +--- + +## 2. 본문 작성 스타일 + +### Why-First 원칙 + +LLM은 이유를 이해하면 엣지 케이스에서도 올바르게 판단한다. 강압적 규칙보다 맥락 전달이 효과적이다. + +**나쁜 예:** +```markdown +ALWAYS use pdfplumber for table extraction. NEVER use PyPDF2 for tables. +``` + +**좋은 예:** +```markdown +테이블 추출에는 pdfplumber를 사용한다. PyPDF2는 텍스트 추출에 특화되어 +있어 테이블의 행/열 구조를 보존하지 못하기 때문이다. pdfplumber는 +셀 경계를 인식하여 구조화된 데이터를 반환한다. +``` + +### 일반화 원칙 + +피드백이나 테스트 결과에서 문제가 발견되면, 특정 예시에만 맞는 좁은 수정 대신 **원리 수준에서 일반화**한다. + +**오버피팅 수정:** +```markdown +"Q4 매출" 열이 있으면 해당 열을 숫자로 변환하라. +``` + +**일반화된 수정:** +```markdown +열 이름에 "매출", "금액", "수량" 등 수치를 암시하는 키워드가 있으면 +해당 열을 숫자 타입으로 변환한다. 변환 실패 시 원본 값을 유지한다. +``` + +### 명령형 어조 + +"~합니다", "~할 수 있습니다" 대신 "~한다", "~하라" 형태를 사용한다. 스킬은 지시서이다. + +### 컨텍스트 절약 + +컨텍스트 윈도우는 공공재다. 모든 문장이 토큰 비용을 정당화하는지 자문한다: +- "Claude가 이미 알고 있는 내용인가?" → 삭제 +- "이 설명이 없으면 Claude가 실수하는가?" → 유지 +- "구체적 예시 하나가 긴 설명보다 효과적인가?" → 예시로 대체 + +--- + +## 3. 출력 형식 정의 패턴 + +산출물의 형식이 중요한 스킬에서 사용: + +```markdown +## 보고서 구조 +다음 템플릿을 정확히 따른다: + +# [제목] +## 요약 +## 핵심 발견 +## 권장 사항 +``` + +형식 정의는 간결하게, 실제 예시를 포함하면 더 효과적이다. + +--- + +## 4. 예시 작성 패턴 + +예시는 긴 설명보다 효과적이다: + +```markdown +## 커밋 메시지 형식 + +**예시 1:** +입력: JWT 토큰 기반 사용자 인증 추가 +출력: feat(auth): JWT 기반 인증 구현 + +**예시 2:** +입력: 로그인 페이지에서 비밀번호 표시 버튼이 동작하지 않는 버그 수정 +출력: fix(login): 비밀번호 표시 토글 버튼 동작 수정 +``` + +--- + +## 5. Progressive Disclosure 패턴 + +### 패턴 1: 도메인별 분리 + +``` +bigquery-skill/ +├── SKILL.md (개요 + 도메인 선택 가이드) +└── references/ + ├── finance.md (매출, 빌링 메트릭) + ├── sales.md (기회, 파이프라인) + └── product.md (API 사용량, 기능) +``` + +사용자가 매출에 대해 물으면 finance.md만 로드. + +### 패턴 2: 조건부 상세 + +```markdown +# DOCX 처리 + +## 문서 생성 +docx-js로 새 문서를 생성한다. → [DOCX-JS.md](references/docx-js.md) 참조. + +## 문서 편집 +단순 편집은 XML을 직접 수정. +**추적 변경이 필요하면**: [REDLINING.md](references/redlining.md) 참조 +``` + +### 패턴 3: 대형 레퍼런스 파일 구조 + +300줄 이상의 reference 파일은 상단에 목차를 포함한다: + +```markdown +# API 레퍼런스 + +## 목차 +1. [인증](#인증) +2. [엔드포인트 목록](#엔드포인트-목록) +3. [에러 코드](#에러-코드) +4. [레이트 리밋](#레이트-리밋) + +--- + +## 인증 +... +``` + +--- + +## 6. 스크립트 번들링 판단 기준 + +테스트 실행에서 에이전트들의 트랜스크립트를 관찰한다. 다음 패턴이 보이면 번들링 대상: + +| 신호 | 조치 | +|------|------| +| 3개 테스트 중 3개에서 동일한 헬퍼 스크립트 생성 | `scripts/`에 번들링 | +| 매번 같은 pip install/npm install 실행 | 스킬에 의존성 설치 단계 명시 | +| 동일한 다단계 접근법 반복 | 스킬 본문에 표준 절차로 기술 | +| 매번 비슷한 에러 후 같은 회피책 적용 | 스킬에 알려진 문제와 해결법 기술 | + +번들링된 스크립트는 반드시 실행 테스트를 거친다. + +--- + +## 7. 데이터 스키마 표준 + +스킬 간 데이터 교환의 일관성을 위해 표준 스키마를 사용한다. 하네스에서 생성하는 스킬의 테스트/평가에 사용할 수 있다. + +### eval_metadata.json + +각 테스트 케이스의 메타데이터: + +```json +{ + "eval_id": 0, + "eval_name": "descriptive-name-here", + "prompt": "사용자의 작업 프롬프트", + "assertions": [ + "산출물에 X가 포함되어 있다", + "Y 형식으로 파일이 생성되었다" + ] +} +``` + +### grading.json + +assertion 기반 채점 결과: + +```json +{ + "expectations": [ + { + "text": "산출물에 '서울'이 포함됨", + "passed": true, + "evidence": "3번째 단계에서 '서울 지역 데이터 추출' 확인" + } + ], + "summary": { + "passed": 2, + "failed": 1, + "total": 3, + "pass_rate": 0.67 + } +} +``` + +**필드명 주의:** `text`, `passed`, `evidence`를 정확히 사용한다 (`name`/`met`/`details` 등 변형 금지). + +### timing.json + +실행 시간/토큰 측정: + +```json +{ + "total_tokens": 84852, + "duration_ms": 23332, + "total_duration_seconds": 23.3 +} +``` + +서브에이전트 완료 알림에서 `total_tokens`와 `duration_ms`를 즉시 저장한다. 이 데이터는 알림 시점에만 접근 가능하고 이후 복구 불가. + +--- + +## 8. 스킬에 포함하지 않을 것 + +- README.md, CHANGELOG.md, INSTALLATION_GUIDE.md 등 부가 문서 +- 스킬 생성 과정의 메타 정보 (테스트 결과, 반복 이력) +- 사용자 대상 설명서 (스킬은 AI 에이전트를 위한 지시서) +- 이미 Claude가 알고 있는 일반적 지식 + +--- + +## 9. 스킬 재사용 설계 + +신규 스킬 생성 전, 기존 스킬과의 중복을 확인한다. 하네스를 반복 구축하다 보면 기능이 겹치는 스킬이 다른 이름으로 누적되기 쉽다. + +| 상황 | 조치 | +|------|------| +| 기존 스킬이 신규 기능을 완전히 포함 | 신규 생성 금지 — 기존 스킬을 에이전트에 연결 | +| 기존 스킬이 부분 포함이고 일반화 가능 | 기존 스킬을 일반화하여 확장 | +| 도메인 특화가 의도된 부분 포함 | 신규 생성 진행 — 별개 스킬로 유지 | +| 기능 범위가 완전히 다름 | 신규 생성 진행 | + +**원칙:** 하나의 스킬이 하나의 역할에 집중할수록 재사용성이 높고 중복이 줄어든다. 역할이 두 가지 이상이면 분리할 수 있는지 먼저 검토한다. + +### 어디까지 일반화할지 + +일반화는 무한히 가능하므로 **의도된 책임 범위**에서 멈춘다. 의도된 도메인 특화는 유지하고, 우연한 종속만 제거한다. + +예: "fintech 리스크 평가 PDF" 스킬 + +| 단계 | 결과 | +|------|------| +| fintech 종속 제거 | "평가 결과 PDF" — 책임 범위가 평가 리포트면 여기서 멈춤 | +| 평가 종속 제거 | "PDF 포매팅" — 이미 존재한다면 별개 스킬 생성하지 말고 재사용 | + +책임 범위가 "fintech 리스크 평가"로 의도된 특화라면 일반화하지 않고 별개 스킬로 유지한다. + +해당 스킬에 의존하는 에이전트의 동작이 변경될 수 있다. 확장 전 의존성을 확인하고, description에 확장된 사용 범위를 반영한다. diff --git a/Gemini/gemini-harness/skills/gemini-harness/references/team-examples.md b/Gemini/gemini-harness/skills/gemini-harness/references/team-examples.md new file mode 100644 index 0000000..f759c6a --- /dev/null +++ b/Gemini/gemini-harness/skills/gemini-harness/references/team-examples.md @@ -0,0 +1,330 @@ +# Agent Team Examples + +--- + +## 예시 1: 리서치 팀 (에이전트 팀 모드) + +### 팀 아키텍처: 팬아웃/팬인 +### 실행 모드: 에이전트 팀 + +``` +[리더/오케스트레이터] + ├── define_subagent (팀원 역할 정의) + ├── invoke_subagent(research-team) + ├── tasks.md 파일 생성(4개 조사 작업) + ├── 팀원들이 자체 조율 (send_message) + ├── 결과 수집 (Read) + └── 종합 보고서 생성 +``` + +### 에이전트 구성 + +| 팀원 | 에이전트 타입 | 역할 | 출력 | +|------|-------------|------|------| +| official-researcher | general-purpose | 공식 문서/블로그 | research_official.md | +| media-researcher | general-purpose | 미디어/투자 | research_media.md | +| community-researcher | general-purpose | 커뮤니티/SNS | research_community.md | +| background-researcher | general-purpose | 배경/경쟁/학술 | research_background.md | +| (리더 = 오케스트레이터) | — | 통합 보고서 | 종합보고서.md | + +> 리서치 에이전트는 `general-purpose` 빌트인 타입을 사용하되, 반드시 `.agents/agents/{name}.md` 파일로 정의한다. 파일에는 역할·조사 범위·팀 통신 프로토콜을 명시하여 재사용성과 협업 품질을 보장한다. + +### 오케스트레이터 워크플로우 (에이전트 팀) + +``` +Phase 1: 준비 + - 사용자 입력 분석 (주제, 조사 모드 파악) + - _workspace/ 생성 + +Phase 2: 팀 구성 + - 각 에이전트 define_subagent 호출 + - invoke_subagent(Subagents: [ + { TypeName: "official", Role: "공식 문서 조사", Prompt: "공식 채널 조사..." }, + { TypeName: "media", Role: "미디어 동향 조사", Prompt: "미디어/투자 동향 조사..." }, + { TypeName: "community", Role: "커뮤니티 반응 조사", Prompt: "커뮤니티 반응 조사..." }, + { TypeName: "background", Role: "배경 조사", Prompt: "배경/경쟁 환경 조사..." } + ]) + - tasks.md 파일 생성(tasks: [ + { title: "공식 채널 조사", assignee: "official" }, + { title: "미디어 동향 조사", assignee: "media" }, + { title: "커뮤니티 반응 조사", assignee: "community" }, + { title: "배경 환경 조사", assignee: "background" } + ]) + +Phase 3: 조사 수행 + - 4명의 팀원이 독립적으로 조사 + - 흥미로운 발견이 있으면 팀원 간 send_message로 공유 + (예: media가 발견한 투자 뉴스를 background에게 전달) + - 상충 정보 발견 시 팀원 간 직접 토론 + - 각 팀원은 완료 시 파일 저장 + 리더에게 알림 + +Phase 4: 통합 + - 리더가 4개 산출물 Read + - 종합 보고서 생성 + - 상충 정보는 출처 병기 + +Phase 5: 정리 + - 팀원들 종료 요청 + - 팀 정리 + - _workspace/ 보존 (사후 검증·감사 추적용) +``` + +### 팀 통신 패턴 + +``` +official ──send_message──→ background (관련 공식 발표 공유) +media ────send_message──→ background (투자/인수 정보 공유) +community ─send_message──→ media (커뮤니티 반응 중 미디어 관련 정보) +모든 팀원 ──tasks.md 업데이트──→ 공유 작업 목록 (진행률 업데이트) +리더 ←───── 유휴 알림 ──── 완료된 팀원 (자동) +``` + +--- + +## 예시 2: SF 소설 집필 팀 (에이전트 팀 모드) + +### 팀 아키텍처: 파이프라인 + 팬아웃 +### 실행 모드: 에이전트 팀 + +``` +Phase 1 (병렬 — 에이전트 팀): worldbuilder + character-designer + plot-architect + → 서로 send_message로 일관성 조율 +Phase 2 (순차): prose-stylist (집필) +Phase 3 (병렬 — 에이전트 팀): science-consultant + continuity-manager (리뷰) + → 서로 send_message로 발견 공유 +Phase 4 (순차): prose-stylist (리뷰 반영 수정) +``` + +### 에이전트 구성 + +| 팀원 | 에이전트 타입 | 역할 | 스킬 | +|------|-------------|------|------| +| worldbuilder | 커스텀 | 세계관 구축 | world-setting | +| character-designer | 커스텀 | 캐릭터 설계 | character-profile | +| plot-architect | 커스텀 | 플롯 구조 | outline | +| prose-stylist | 커스텀 | 문체 편집 + 집필 | write-scene, review-chapter | +| science-consultant | 커스텀 | 과학 검증 | science-check | +| continuity-manager | 커스텀 | 일관성 검증 | consistency-check | + +### 에이전트 파일 전문 예시: `worldbuilder.md` + +```markdown +--- +name: worldbuilder +description: "SF 소설의 세계관을 구축하는 전문가. 물리 법칙, 사회 구조, 기술 수준, 역사를 설계한다." +--- + +# Worldbuilder — SF 세계관 설계 전문가 + +당신은 SF 소설의 세계관 설계 전문가입니다. 과학적 사실에 기반하되 상상력을 확장하여, 이야기가 펼쳐질 세계의 물리적·사회적·기술적 토대를 구축합니다. + +## 핵심 역할 +1. 세계의 물리 법칙과 기술 수준 정의 +2. 사회 구조, 정치 체계, 경제 시스템 설계 +3. 역사적 맥락과 현재 갈등 구조 수립 +4. 장소별 환경과 분위기 묘사 + +## 작업 원칙 +- 내적 일관성 최우선 — 설정 간 모순이 없어야 한다 +- "만약 이 기술이 있다면?" 연쇄 질문으로 세계의 파급 효과를 추론 +- 이야기에 봉사하는 세계관 — 플롯을 방해하는 과도한 설정은 지양 + +## 입력/출력 프로토콜 +- 입력: 사용자의 세계관 컨셉, 장르 요구사항 +- 출력: `_workspace/01_worldbuilder_setting.md` +- 형식: 마크다운. 섹션별 (물리/사회/기술/역사/장소) + +## 팀 통신 프로토콜 +- character-designer에게: 사회 구조, 계급 시스템, 직업군 정보 send_message +- plot-architect에게: 세계의 주요 갈등 구조, 위기 요소 send_message +- science-consultant로부터: 과학적 오류 피드백 수신 → 설정 수정 +- 세계관 변경 시 관련 팀원 전체에 브로드캐스트 + +## 에러 핸들링 +- 컨셉이 모호하면 3가지 방향을 제안하고 선택 요청 +- 과학적 오류 발견 시 대안을 함께 제시 + +## 협업 +- character-designer에게 사회 구조 정보 제공 +- plot-architect에게 갈등 구조 정보 제공 +- science-consultant의 피드백을 반영하여 설정 수정 +``` + +### 팀 워크플로우 상세 + +``` +Phase 1: define_subagent 후 invoke_subagent(Subagents: [worldbuilder, character-designer, plot-architect]) + tasks.md 파일 생성([세계관 구축, 캐릭터 설계, 플롯 구조]) + → 팀원들이 자체 조율하며 병렬 작업 + → worldbuilder가 사회 구조 완성 시 character-designer에게 send_message + → character-designer가 주인공 설정 시 plot-architect에게 send_message + +Phase 2: Phase 1 팀 정리 → prose-stylist를 서브 에이전트로 호출 (단독 집필이므로 팀 불필요) + prose-stylist가 _workspace/의 3개 산출물을 Read하여 집필 + → 결과를 _workspace/02_prose_draft.md에 저장 + +Phase 3: 새 팀 생성 — define_subagent 후 invoke_subagent(Subagents: [science-consultant, continuity-manager]) + (세션당 한 팀만 활성이지만, Phase 1 팀을 정리했으므로 새 팀 생성 가능) + → 두 리뷰어가 draft를 검토, 서로 발견을 공유 + → science-consultant가 물리 오류 발견 시 continuity-manager에게도 알림 + → 리뷰 완료 후 팀 정리 + +Phase 4: prose-stylist를 서브 에이전트로 호출, 리뷰 결과 반영하여 최종 수정 +``` + +--- + +## 예시 3: 웹툰 제작 팀 (서브 에이전트 모드) + +### 팀 아키텍처: 생성-검증 +### 실행 모드: 서브 에이전트 + +> 생성-검증 패턴에서 에이전트가 2개뿐이고, 통신보다는 결과 전달이 핵심이므로 서브 에이전트가 적합. + +``` +Phase 1: Agent(webtoon-artist) → 패널 생성 +Phase 2: Agent(webtoon-reviewer) → 검수 +Phase 3: Agent(webtoon-artist) → 문제 패널 재생성 (최대 2회) +``` + +### 에이전트 구성 + +| 에이전트 | TypeName | 역할 | 스킬 | +|---------|--------------|------|------| +| webtoon-artist | 커스텀 | 패널 이미지 생성 | generate-webtoon | +| webtoon-reviewer | 커스텀 | 품질 검수 | review-webtoon, fix-webtoon-panel | + +### 에이전트 파일 전문 예시: `webtoon-reviewer.md` + +```markdown +--- +name: webtoon-reviewer +description: "웹툰 패널의 품질을 검수하는 전문가. 구도, 캐릭터 일관성, 텍스트 가독성, 연출을 평가한다." +--- + +# Webtoon Reviewer — 웹툰 품질 검수 전문가 + +당신은 웹툰 패널의 품질을 검수하는 전문가입니다. 시각적 완성도, 스토리 전달력, 캐릭터 일관성을 기준으로 패널을 평가합니다. + +## 핵심 역할 +1. 각 패널의 구도와 시각적 완성도 평가 +2. 캐릭터 외형의 패널 간 일관성 검증 +3. 말풍선 텍스트의 가독성과 배치 평가 +4. 전체 에피소드의 연출 흐름과 페이싱 검토 + +## 작업 원칙 +- PASS/FIX/REDO 3단계로 명확히 판정 +- FIX는 부분 수정으로 해결 가능한 경우, REDO는 전면 재생성 필요 +- 주관적 취향이 아닌 객관적 기준(일관성, 가독성, 구도)으로 판단 + +## 입력/출력 프로토콜 +- 입력: `_workspace/panels/` 디렉토리의 패널 이미지들 +- 출력: `_workspace/review_report.md` +- 형식: + ``` + ## Panel {N} + - 판정: PASS | FIX | REDO + - 사유: [구체적 이유] + - 수정 지시: [FIX/REDO인 경우 구체적 수정 방향] + ``` + +## 에러 핸들링 +- 이미지 로드 실패 시 해당 패널을 REDO로 판정 +- 2회 재생성 후에도 REDO인 패널은 경고와 함께 PASS 처리 + +## 협업 +- webtoon-artist에게 수정 지시서 전달 (결과 파일 기반) +- 재생성된 패널을 다시 검수 (최대 2회 루프) +``` + +### 에러 핸들링 + +``` +재시도 정책: +- REDO 판정 패널 → artist에게 재생성 요청 (구체적 수정 지시 포함) +- 최대 2회 루프 후 강제 PASS +- 전체 패널의 50% 이상이 REDO면 사용자에게 프롬프트 수정 제안 +``` + +--- + +## 예시 4: 코드 리뷰 팀 (에이전트 팀 모드) + +### 팀 아키텍처: 팬아웃/팬인 + 토론 +### 실행 모드: 에이전트 팀 + +> 코드 리뷰는 에이전트 팀이 빛나는 대표적 사례. 서로 다른 관점의 리뷰어들이 발견을 공유하고 도전하면서 더 깊은 리뷰가 가능. + +``` +[리더] → define_subagent → invoke_subagent(review-team) + ├── security-reviewer: 보안 취약점 점검 + ├── performance-reviewer: 성능 영향 분석 + └── test-reviewer: 테스트 커버리지 검증 + → 리뷰어들이 서로 발견 공유 (send_message) + → 리더가 결과 종합 +``` + +### 팀 통신 패턴 + +``` +security ──send_message──→ performance ("이 SQL 쿼리 주입 가능, 성능 측면에서도 확인 필요") +performance ──send_message──→ test ("N+1 쿼리 발견, 관련 테스트 있는지 확인 부탁") +test ────send_message──→ security ("인증 모듈 테스트 없음, 보안 관점에서 우선순위 의견?") +``` + +핵심: 리뷰어들이 **리더를 거치지 않고** 직접 소통하여 교차 영역 이슈를 빠르게 포착. + +--- + +## 예시 5: 감독자 패턴 — 코드 마이그레이션 팀 (에이전트 팀 모드) + +### 팀 아키텍처: 감독자 +### 실행 모드: 에이전트 팀 + +``` +[supervisor/리더] → 파일 목록 분석 → 배치 할당 + ├→ [migrator-1] (batch A) + ├→ [migrator-2] (batch B) + └→ [migrator-3] (batch C) + ← tasks.md 업데이트 수신 → 추가 배치 할당 또는 재할당 +``` + +### 에이전트 구성 + +| 팀원 | 역할 | +|------|------| +| (리더 = migration-supervisor) | 파일 분석, 배치 분배, 진행 관리 | +| migrator-1~3 | 할당된 파일 배치를 마이그레이션 | + +### 감독자의 동적 분배 로직 (에이전트 팀 활용) + +``` +1. 전체 대상 파일 목록 수집 +2. 복잡도 추정 (파일 크기, import 수, 의존성) +3. tasks.md 파일 생성로 파일 배치를 작업으로 등록 (의존성 포함) +4. 팀원들이 자체적으로 작업 요청 (claim) +5. 팀원이 tasks.md 업데이트로 완료 보고 시: + - 성공 → 다음 작업 자동 요청 + - 실패 → 리더가 send_message로 원인 확인 → 재할당 또는 다른 팀원에게 배정 +6. 모든 작업 완료 → 리더가 통합 테스트 실행 +``` + +팬아웃과의 차이: 작업이 사전 고정이 아니라 **런타임에 동적으로 할당**된다. 공유 작업 목록의 자체 요청(claim) 기능이 감독자 패턴과 자연스럽게 매칭. + +--- + +## 산출물 패턴 요약 + +### 에이전트 정의 파일 +위치: `프로젝트/.agents/agents/{agent-name}.md` +필수 섹션: 핵심 역할, 작업 원칙, 입력/출력 프로토콜, 에러 핸들링, 협업 +팀 모드 추가 섹션: **팀 통신 프로토콜** (메시지 수신/발신, 작업 요청 범위) + +### 스킬 파일 구조 +위치: `프로젝트/.agents/skills/{skill-name}/SKILL.md` (프로젝트 레벨) +또는: `~/.agents/skills/{skill-name}/SKILL.md` (글로벌 레벨) + +### 통합 스킬 (오케스트레이터) +팀 전체를 조율하는 상위 스킬. 시나리오별 에이전트 구성과 워크플로우를 정의. +템플릿: `references/orchestrator-template.md` 참조. +**실행 모드를 반드시 명시** — 에이전트 팀(기본) 또는 서브 에이전트. diff --git a/Writing/Codex/.agents/skills/document-harness/SKILL.md b/Writing/Codex/.agents/skills/document-harness/SKILL.md deleted file mode 100644 index 1e153e8..0000000 --- a/Writing/Codex/.agents/skills/document-harness/SKILL.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -name: "document-harness" -description: "Use when creating, running, or updating the staged Markdown document-writing Harness from docs/PRD.md through research notes, drafts, feedback gates, and final documents." ---- - -# Document Harness Skill - -Use this skill to turn `docs/PRD.md` into researched, reviewable, and feedback-driven Markdown documents. - -## Operating Rules - -1. Read `AGENTS.md`, `docs/PRD.md`, `docs/ARCHITECTURE.md`, `docs/ADR.md`, and `docs/UI_GUIDE.md` before planning document work. -2. Treat `docs/PRD.md` as the single source of requirements. -3. If PRD purpose, target reader, final deliverables, scope, or key questions are materially empty, stop and ask the user to complete PRD first. -4. Use `docs/ResearchNote.md` as the evidence ledger before drafting externally factual content. -5. Store review drafts under `drafts/` and final deliverables under `final/`. -6. Preserve `docs/DraftFeedback.md` and `docs/FinalFeedback.md`; never delete user feedback. -7. Run `python scripts/validate_docs.py` before reporting completion. - -## Staged Workflow - -### 1. PRD Intake - -Read `docs/PRD.md` and identify: - -- document purpose -- target readers -- final deliverables -- required outline -- important keywords -- key questions -- scope boundaries -- tone and style constraints -- research requirements - -### 2. Rule Synthesis - -Update only the relevant project-specific guidance in `AGENTS.md`. - -Include: - -- document purpose and target readers -- final deliverables -- tone and style rules -- citation and verification standards -- draft and final feedback process - -Keep the generic Codex configuration and repository workflow concise. - -### 3. Research Note - -Research PRD keywords and key questions. Prefer official, academic, government, institutional, or other primary sources. - -Write `docs/ResearchNote.md` with: - -- search date -- search terms -- source URLs -- source quality notes -- core findings -- conflicting claims -- unresolved questions -- intended document usage - -Use `doc_researcher` or `evidence_checker` agents when the user or current phase explicitly asks for subagent work. - -### 4. Draft Documents - -Create all PRD deliverables under `drafts/`. - -Drafts must: - -- answer the PRD key questions -- stay inside PRD scope -- use the requested tone -- link factual claims to `docs/ResearchNote.md` -- mark weak or missing evidence - -After drafting, request user review in `docs/DraftFeedback.md`. - -### 5. Draft Feedback Gate - -If `docs/DraftFeedback.md` has no actionable user feedback or approval, mark the phase step as `blocked` with a clear `blocked_reason`. - -If feedback exists, summarize it before revising. - -### 6. Final Documents - -Create final deliverables under `final/`. Do not overwrite `drafts/`. - -Final documents must reflect: - -- PRD requirements -- ResearchNote evidence -- DraftFeedback requests -- UI guide style rules - -After finalizing, request user review or approval in `docs/FinalFeedback.md`. - -### 7. Final Feedback Gate - -If `docs/FinalFeedback.md` does not contain approval or actionable next feedback, mark the phase step as `blocked`. - -If approval exists, mark the phase completed. - -## Phase Files - -When creating a new phase, use `references/phase-templates.md`. - -Each step must include: - -- files to read -- exact task -- acceptance criteria -- validation procedure -- status update instructions -- concrete forbidden actions - -## Validation - -Always run: - -```bash -python scripts/validate_docs.py -``` - -For executor changes, also run: - -```bash -python -m pytest scripts/test_execute.py -``` diff --git a/Writing/Codex/.agents/skills/document-harness/references/phase-templates.md b/Writing/Codex/.agents/skills/document-harness/references/phase-templates.md deleted file mode 100644 index 3e849e5..0000000 --- a/Writing/Codex/.agents/skills/document-harness/references/phase-templates.md +++ /dev/null @@ -1,85 +0,0 @@ -# Document Harness Phase Templates - -## Top-Level Phase Index - -Create or update `phases/index.json`. - -```json -{ - "phases": [ - { - "dir": "0-document", - "status": "pending" - } - ] -} -``` - -## Task Index - -Create `phases/{task-name}/index.json`. - -```json -{ - "project": "<문서 프로젝트명>", - "phase": "", - "steps": [ - { "step": 0, "name": "rule-synthesis", "status": "pending" }, - { "step": 1, "name": "research-note", "status": "pending" }, - { "step": 2, "name": "draft-documents", "status": "pending" }, - { "step": 3, "name": "draft-feedback-gate", "status": "pending" }, - { "step": 4, "name": "final-documents", "status": "pending" }, - { "step": 5, "name": "final-feedback-gate", "status": "pending" } - ] -} -``` - -## Step File - -Create `phases/{task-name}/step{N}.md`. - -```markdown -# Step {N}: {이름} - -## 읽어야 할 파일 - -먼저 아래 파일들을 읽고 문서 목적과 작성 기준을 파악하라: - -- `/AGENTS.md` -- `/docs/PRD.md` -- `/docs/ARCHITECTURE.md` -- `/docs/ADR.md` -- `/docs/UI_GUIDE.md` -- {이전 step에서 생성/수정된 파일 경로} - -## 작업 - -{구체적인 문서 작성 또는 검토 지시. 파일 경로, 산출물 이름, 반영해야 할 PRD 항목, 출처 기준을 포함한다.} - -## Acceptance Criteria - -```bash -python scripts/validate_docs.py -``` - -## 검증 절차 - -1. 위 AC 커맨드를 실행한다. -2. 문서 체크리스트를 확인한다: - - `docs/PRD.md`의 목적, 독자, 범위를 벗어나지 않았는가? - - 외부 사실은 `docs/ResearchNote.md`의 출처와 연결되는가? - - 초안은 `drafts/`, 최종본은 `final/`에 분리되었는가? - - 사용자 피드백 파일을 삭제하거나 덮어쓰지 않았는가? -3. 결과에 따라 `phases/{task-name}/index.json`의 해당 step을 업데이트한다: - - 성공 -> `"status": "completed"`, `"summary": "산출물 한 줄 요약"` - - 수정 3회 시도 후에도 실패 -> `"status": "error"`, `"error_message": "구체적 에러 내용"` - - 사용자 개입 필요 -> `"status": "blocked"`, `"blocked_reason": "구체적 요청 사항"` 후 즉시 중단 - -## 금지사항 - -- PRD에 없는 문서 목표를 추가하지 마라. 이유: 사용자 의도가 흐려진다. -- 출처 없는 외부 사실을 최종 문서에 단정하지 마라. 이유: 검증 가능성이 사라진다. -- 초안 파일을 최종본으로 덮어쓰지 마라. 이유: 피드백 전후 변경 추적이 어렵다. -- 사용자 피드백 파일을 삭제하지 마라. 이유: 의사결정 기록이 사라진다. -- 직접 `git commit`하지 마라. 이유: `scripts/execute.py`가 step 완료 후 커밋을 관리한다. -``` diff --git a/Writing/Codex/.agents/skills/document-review/SKILL.md b/Writing/Codex/.agents/skills/document-review/SKILL.md deleted file mode 100644 index c581c94..0000000 --- a/Writing/Codex/.agents/skills/document-review/SKILL.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: "document-review" -description: "Use when reviewing Markdown document changes for PRD alignment, source traceability, feedback coverage, structure, and final delivery readiness." ---- - -# Document Review Skill - -Use this skill to review changed Markdown files in the Codex Markdown Document Harness. - -## Read First - -- `AGENTS.md` -- `docs/PRD.md` -- `docs/ResearchNote.md` -- `docs/DraftFeedback.md` -- `docs/FinalFeedback.md` -- `docs/ARCHITECTURE.md` -- `docs/ADR.md` -- `docs/UI_GUIDE.md` - -## Review Checklist - -1. PRD alignment: purpose, target reader, deliverables, scope, and tone match `docs/PRD.md`. -2. Source traceability: external facts, dates, statistics, claims, and quotations connect to `docs/ResearchNote.md`. -3. Structure: heading hierarchy, section order, and file names match the intended deliverables. -4. Feedback coverage: `docs/DraftFeedback.md` or `docs/FinalFeedback.md` requests are addressed. -5. Draft/final separation: drafts live under `drafts/`; final deliverables live under `final/`. -6. Style quality: avoid generic AI prose, unsupported superlatives, repetition, and vague claims. -7. Validation: `python scripts/validate_docs.py` passes. - -## Output Format - -Lead with findings. Use this table when a full checklist result is useful: - -| 항목 | 결과 | 비고 | -|------|------|------| -| PRD 정합성 | PASS/FAIL | {상세} | -| 출처 추적 | PASS/FAIL | {상세} | -| 문서 구조 | PASS/FAIL | {상세} | -| 피드백 반영 | PASS/FAIL | {상세} | -| 초안/최종본 분리 | PASS/FAIL | {상세} | -| 문체 품질 | PASS/FAIL | {상세} | -| 검증 가능성 | PASS/FAIL | {상세} | - -If there are issues, include concrete file paths and suggested fixes. diff --git a/Writing/Codex/.codex/agents/doc_drafter.toml b/Writing/Codex/.codex/agents/doc_drafter.toml deleted file mode 100644 index 9e6bf2e..0000000 --- a/Writing/Codex/.codex/agents/doc_drafter.toml +++ /dev/null @@ -1,16 +0,0 @@ -name = "doc_drafter" -description = "Turns PRD requirements and ResearchNote evidence into reviewable Markdown drafts." -nickname_candidates = ["Drafter", "Draft"] -model_reasoning_effort = "high" - -developer_instructions = """ -You are the drafting specialist for the Codex Markdown Document Harness. - -Responsibilities: -- Read AGENTS.md, docs/PRD.md, docs/ResearchNote.md, and docs/UI_GUIDE.md before drafting. -- Create draft documents only under drafts/. -- Keep the document goal, audience, scope, and tone aligned with docs/PRD.md. -- Tie external claims to docs/ResearchNote.md sources. -- Preserve user feedback files and do not overwrite final/ documents. -- If a PRD requirement is ambiguous, mark the ambiguity in the draft or report it to the parent agent. -""" diff --git a/Writing/Codex/.codex/agents/doc_researcher.toml b/Writing/Codex/.codex/agents/doc_researcher.toml deleted file mode 100644 index 19bbd46..0000000 --- a/Writing/Codex/.codex/agents/doc_researcher.toml +++ /dev/null @@ -1,15 +0,0 @@ -name = "doc_researcher" -description = "Researches PRD keywords, gathers trustworthy sources, and maintains docs/ResearchNote.md." -nickname_candidates = ["Researcher", "Research"] -model_reasoning_effort = "high" - -developer_instructions = """ -You are the research specialist for the Codex Markdown Document Harness. - -Responsibilities: -- Read AGENTS.md, docs/PRD.md, and docs/UI_GUIDE.md before researching. -- Prefer primary sources: official documentation, government or institutional publications, academic papers, and original company materials. -- Record search date, search terms, source URLs, core claims, conflicts, and where each source should be reflected in docs/ResearchNote.md. -- Mark uncertain claims as 확인 필요 instead of presenting them as facts. -- Do not write final prose in final/. Your primary output is docs/ResearchNote.md and concise research notes for the parent agent. -""" diff --git a/Writing/Codex/.codex/agents/doc_reviewer.toml b/Writing/Codex/.codex/agents/doc_reviewer.toml deleted file mode 100644 index 67d48dd..0000000 --- a/Writing/Codex/.codex/agents/doc_reviewer.toml +++ /dev/null @@ -1,14 +0,0 @@ -name = "doc_reviewer" -description = "Reviews Markdown documents for PRD alignment, evidence quality, structure, and feedback coverage." -nickname_candidates = ["Reviewer", "Review"] -model_reasoning_effort = "medium" - -developer_instructions = """ -You are the review specialist for the Codex Markdown Document Harness. - -Responsibilities: -- Review changed Markdown files against AGENTS.md, docs/PRD.md, docs/ResearchNote.md, docs/DraftFeedback.md, docs/FinalFeedback.md, and docs/UI_GUIDE.md. -- Lead with concrete issues, ordered by severity, with file paths and line references when possible. -- Check PRD alignment, source traceability, draft/final separation, feedback preservation, and Markdown structure. -- Do not rewrite documents unless the parent agent explicitly asks you to make edits. -""" diff --git a/Writing/Codex/.codex/agents/evidence_checker.toml b/Writing/Codex/.codex/agents/evidence_checker.toml deleted file mode 100644 index 513a1e3..0000000 --- a/Writing/Codex/.codex/agents/evidence_checker.toml +++ /dev/null @@ -1,14 +0,0 @@ -name = "evidence_checker" -description = "Checks whether factual claims in drafts and final documents are supported by ResearchNote sources." -nickname_candidates = ["Evidence", "Checker"] -model_reasoning_effort = "medium" - -developer_instructions = """ -You are the evidence checking specialist for the Codex Markdown Document Harness. - -Responsibilities: -- Compare drafts/ and final/ documents with docs/ResearchNote.md. -- Identify unsupported statistics, dates, legal or policy claims, product/version claims, and quotations. -- Report missing, weak, stale, or conflicting evidence. -- Prefer concise claim-to-source mapping over broad style feedback. -""" diff --git a/Writing/Codex/.codex/config.toml b/Writing/Codex/.codex/config.toml deleted file mode 100644 index a6c03df..0000000 --- a/Writing/Codex/.codex/config.toml +++ /dev/null @@ -1,10 +0,0 @@ -web_search = "live" - -[features] -codex_hooks = true -multi_agent = true - -[agents] -max_threads = 4 -max_depth = 1 -job_max_runtime_seconds = 1800 diff --git a/Writing/Codex/.codex/hooks.json b/Writing/Codex/.codex/hooks.json deleted file mode 100644 index e9489fb..0000000 --- a/Writing/Codex/.codex/hooks.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "hooks": { - "PreToolUse": [ - { - "matcher": "Bash|shell_command", - "hooks": [ - { - "type": "command", - "command": "python .codex/hooks/pre_tool_guard.py", - "timeout": 10, - "statusMessage": "Checking shell command safety" - } - ] - } - ], - "Stop": [ - { - "matcher": "", - "hooks": [ - { - "type": "command", - "command": "python .codex/hooks/stop_validate.py", - "timeout": 60, - "statusMessage": "Validating document harness files" - } - ] - } - ] - } -} diff --git a/Writing/Codex/.codex/hooks/pre_tool_guard.py b/Writing/Codex/.codex/hooks/pre_tool_guard.py deleted file mode 100644 index e2a139a..0000000 --- a/Writing/Codex/.codex/hooks/pre_tool_guard.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 -"""Codex PreToolUse guard for obviously destructive shell commands.""" - -import json -import re -import sys -from typing import Any - - -DANGEROUS_PATTERNS = [ - (r"\brm\s+-rf\b", "Recursive force deletion is blocked by the document harness."), - ( - r"\bRemove-Item\b(?=.*\b-Recurse\b|\s-r\b)(?=.*\b-Force\b|\s-f\b)", - "PowerShell recursive force deletion is blocked by the document harness.", - ), - (r"\bgit\s+reset\s+--hard\b", "Hard reset is blocked because it can discard user work."), - (r"\bgit\s+push\b.*\s--force(?:-with-lease)?\b", "Force push is blocked by the document harness."), - (r"\bDROP\s+TABLE\b", "Destructive database commands are blocked by the document harness."), -] - - -def iter_strings(value: Any): - if isinstance(value, str): - yield value - elif isinstance(value, dict): - for key, item in value.items(): - yield str(key) - yield from iter_strings(item) - elif isinstance(value, list): - for item in value: - yield from iter_strings(item) - - -def deny(reason: str) -> None: - payload = { - "hookSpecificOutput": { - "permissionDecision": "deny", - "permissionDecisionReason": reason, - }, - "decision": "block", - "reason": reason, - } - print(json.dumps(payload, ensure_ascii=False)) - - -def main() -> int: - raw = sys.stdin.read() - haystack = raw - - try: - data = json.loads(raw) if raw.strip() else {} - except json.JSONDecodeError: - data = {} - - if data: - haystack += "\n" + "\n".join(iter_strings(data)) - - for pattern, reason in DANGEROUS_PATTERNS: - if re.search(pattern, haystack, flags=re.IGNORECASE | re.DOTALL): - deny(reason) - return 0 - - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Writing/Codex/.codex/hooks/stop_validate.py b/Writing/Codex/.codex/hooks/stop_validate.py deleted file mode 100644 index 8719875..0000000 --- a/Writing/Codex/.codex/hooks/stop_validate.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -"""Codex Stop hook that asks the agent to continue when template validation fails.""" - -import json -import subprocess -import sys -from pathlib import Path - - -ROOT = Path(__file__).resolve().parents[2] - - -def main() -> int: - result = subprocess.run( - [sys.executable, "scripts/validate_docs.py"], - cwd=ROOT, - capture_output=True, - text=True, - encoding="utf-8", - errors="replace", - ) - - if result.returncode == 0: - return 0 - - details = "\n".join(part for part in [result.stdout.strip(), result.stderr.strip()] if part) - payload = { - "decision": "block", - "reason": ( - "Document harness validation failed. Continue the turn, fix the listed " - f"issues, and run `python scripts/validate_docs.py` again.\n\n{details}" - ), - } - print(json.dumps(payload, ensure_ascii=False)) - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Writing/Codex/.gitignore b/Writing/Codex/.gitignore deleted file mode 100644 index ad8994f..0000000 --- a/Writing/Codex/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -node_modules/ -.next/ -out/ -next-env.d.ts -tsconfig.tsbuildinfo - -# Python/test cache -__pycache__/ -*.pyc -.pytest_cache/ - -# phase execution outputs -phases/**/phase*-output.json -phases/**/step*-output.json diff --git a/Writing/Codex/AGENTS.md b/Writing/Codex/AGENTS.md deleted file mode 100644 index 31f01c5..0000000 --- a/Writing/Codex/AGENTS.md +++ /dev/null @@ -1,57 +0,0 @@ -# 프로젝트: Codex Markdown Document Harness Template - -## 목적 -이 템플릿은 Codex와 Harness Engineering 방식을 이용해 사용자의 목표에 맞는 Markdown 문서를 단계적으로 작성하기 위한 작업 환경이다. - -사용자는 `docs/PRD.md`에 문서의 목적, 개요, 대상 독자, 중요 키워드, 참고 자료를 입력한다. 이후 Codex는 이 정보를 기준으로 작성 규칙을 구체화하고, 조사 노트, 초안, 피드백 반영본, 최종 문서를 순차적으로 만든다. - -## Codex 구성 -- `AGENTS.md`: Codex가 항상 참고하는 프로젝트 규칙. -- `.agents/skills/document-harness/`: 문서 작성 Harness의 단계별 실행 절차. -- `.agents/skills/document-review/`: 문서 변경 사항 리뷰 절차. -- `.codex/agents/`: 조사, 초안, 리뷰에 특화된 Codex custom agents. -- `.codex/hooks.json`: 문서 검증과 위험 명령 방지를 위한 lifecycle hooks. -- `.codex/config.toml`: hooks, multi-agent, live web search 등 이 템플릿에서 권장하는 Codex 기능 설정. - -## 기본 산출물 -- `docs/PRD.md`: 사용자가 작성하는 문서 요구사항의 원천. -- `docs/ResearchNote.md`: 웹 조사 결과, 출처, 쟁점, 문서 반영 메모. -- `drafts/`: 사용자 검토를 위한 초안 문서. -- `final/`: 피드백을 반영한 최종 문서. -- `docs/DraftFeedback.md`: 초안 검토 후 사용자가 남기는 피드백. -- `docs/FinalFeedback.md`: 최종 문서 검토 후 사용자가 남기는 피드백. -- `phases/`: Harness step 실행 계획과 상태 파일. - -## 문서 작성 규칙 -- CRITICAL: `docs/PRD.md`를 단일 요구사항 원천으로 삼는다. PRD에 없는 목표, 독자, 범위, 톤을 임의로 추가하지 마라. -- CRITICAL: 외부 사실, 통계, 최신 정보, 인용, 법/제도/가격/제품 정보는 `docs/ResearchNote.md`의 출처에 근거해야 한다. -- CRITICAL: 출처가 불명확한 주장을 최종 문서에 단정적으로 쓰지 마라. 필요한 경우 "확인 필요" 또는 "출처 필요"로 표시한다. -- CRITICAL: 초안 작성 후와 최종 문서 작성 후에는 사용자 피드백을 받아야 한다. 피드백이 필요한 step은 `blocked` 상태와 구체적인 `blocked_reason`을 기록한다. -- CRITICAL: 최종 문서는 초안과 분리해 `final/` 아래에 작성한다. 초안 파일을 최종본처럼 덮어쓰지 마라. -- 조사 노트에는 검색 일시, 검색어, 출처 URL, 핵심 요지, 문서 반영 여부를 남긴다. -- 문서 구조는 제목 계층을 유지한다. `#`는 문서 제목에만 사용하고, 본문 구조는 `##`, `###`를 사용한다. -- 사용자의 피드백은 삭제하지 말고 별도 피드백 문서에 보존한다. - -## Codex 작업 규칙 -- 반복 가능한 절차는 `.agents/skills/`의 Skill에 둔다. `AGENTS.md`에는 지속적으로 적용할 짧은 규칙만 유지한다. -- 문서 작성 Harness를 실행하거나 설계할 때는 `$document-harness` Skill을 우선 사용한다. -- 문서 변경 사항을 검토할 때는 `$document-review` Skill을 사용한다. -- 병렬 조사나 독립 리뷰가 필요한 경우 `.codex/agents/`의 custom agents를 명시적으로 선택한다. -- `scripts/execute.py`가 step 실행 후 git commit을 처리하므로, step을 수행하는 Codex 세션은 직접 commit하지 않는다. - -## 권장 워크플로우 -1. 사용자가 `docs/PRD.md`를 채운다. -2. Codex가 PRD를 읽고 `AGENTS.md`의 프로젝트별 작성 규칙을 구체화한다. -3. Codex 또는 Codex subagents가 웹 검색을 수행하고 `docs/ResearchNote.md`를 작성한다. -4. Codex가 `drafts/`에 초안을 만들고 사용자 검토를 요청한다. -5. 사용자가 `docs/DraftFeedback.md`에 피드백을 남긴다. -6. Codex가 피드백을 반영해 `final/`에 최종 문서를 작성한다. -7. 사용자가 `docs/FinalFeedback.md`에 최종 피드백 또는 승인 여부를 남긴다. - -## 명령어 -```bash -python scripts/validate_docs.py -python scripts/execute.py -python scripts/execute.py --push -python -m pytest scripts/test_execute.py -``` diff --git a/Writing/Codex/README.md b/Writing/Codex/README.md deleted file mode 100644 index 00c7159..0000000 --- a/Writing/Codex/README.md +++ /dev/null @@ -1,259 +0,0 @@ -# Codex Markdown Document Harness Template - -Codex 환경에서 Harness Engineering 방식으로 Markdown 문서를 단계적으로 작성하기 위한 템플릿입니다. - -사용자는 `docs/PRD.md`에 만들고 싶은 문서의 목적, 대상 독자, 개요, 중요 키워드, 조사 요구사항을 작성합니다. 이후 Codex는 PRD를 기준으로 작성 규칙을 구체화하고, 웹 조사, 조사 노트, 초안, 사용자 피드백, 최종 문서를 순서대로 만들어 갑니다. - -## 핵심 아이디어 - -이 템플릿은 한 번에 최종 문서를 쓰는 방식이 아니라, 다음 흐름을 강제합니다. - -```text -PRD 작성 - -> 작성 규칙 구체화 - -> 웹 조사 및 ResearchNote 작성 - -> drafts/ 초안 작성 - -> 사용자 초안 피드백 - -> final/ 최종 문서 작성 - -> 사용자 최종 피드백 또는 승인 -``` - -목표는 빠른 초안 작성보다 사용자의 의도, 출처, 피드백, 최종 산출물을 분리해 관리하는 것입니다. - -## Codex 구성 - -```text -. -├── AGENTS.md # Codex가 읽는 프로젝트 기본 규칙 -├── .agents/ -│ └── skills/ -│ ├── document-harness/ # 단계적 문서 작성 Skill -│ └── document-review/ # 문서 리뷰 Skill -├── .codex/ -│ ├── config.toml # hooks, multi-agent, live web search 설정 -│ ├── hooks.json # Stop/PreToolUse hook 연결 -│ ├── hooks/ # hook 실행 스크립트 -│ └── agents/ # 조사, 초안, 리뷰, 근거 점검 custom agents -├── docs/ -│ ├── PRD.md # 사용자가 채우는 문서 요구사항 -│ ├── ResearchNote.md # 조사 결과와 출처 장부 -│ ├── DraftFeedback.md # 초안 피드백 -│ ├── FinalFeedback.md # 최종 문서 피드백 -│ ├── ARCHITECTURE.md # 템플릿 구조 설명 -│ ├── ADR.md # 주요 설계 결정 -│ └── UI_GUIDE.md # Markdown 문서 스타일 가이드 -├── drafts/ # 초안 문서 -├── final/ # 최종 문서 -├── phases/ # 단계 실행 계획과 상태 파일 -└── scripts/ - ├── execute.py # codex exec 기반 step 실행기 - ├── validate_docs.py # 템플릿 구조 검증 - └── test_execute.py # 실행기 테스트 -``` - -## 빠른 시작 - -1. 템플릿을 git 저장소로 준비합니다. - -```bash -git init -``` - -`scripts/execute.py`는 브랜치 생성과 커밋을 수행하므로 자동 실행을 쓰려면 git 저장소가 필요합니다. - -2. `docs/PRD.md`를 채웁니다. - -최소한 아래 항목은 구체적으로 작성하는 것이 좋습니다. - -- 문서 목적 -- 대상 독자 -- 최종 산출물 -- 문서 개요 -- 중요 키워드 -- 핵심 질문 -- 포함할 범위와 제외할 범위 -- 톤과 스타일 -- 조사 요구사항 -- 승인 기준 - -3. Codex에서 Harness Skill을 사용합니다. - -예시 프롬프트: - -```text -$document-harness를 사용해서 docs/PRD.md를 읽고 문서 작성 phase를 설계해 주세요. -``` - -또는 바로 다음처럼 요청할 수 있습니다. - -```text -$document-harness를 사용해서 docs/PRD.md 기준으로 작성 규칙 구체화, ResearchNote 작성, 초안 작성 단계까지 진행해 주세요. -``` - -4. 생성된 초안을 검토합니다. - -초안은 `drafts/` 아래에 생성됩니다. 검토 후 `docs/DraftFeedback.md`에 피드백을 작성합니다. - -5. 최종본을 검토합니다. - -최종 문서는 `final/` 아래에 생성됩니다. 검토 후 `docs/FinalFeedback.md`에 승인 또는 추가 수정 요청을 작성합니다. - -## 자동 실행 방식 - -Codex가 `phases/{task-name}/` 아래에 step 파일을 만든 뒤, 실행기는 각 step을 `codex exec`로 순차 실행합니다. - -```bash -python scripts/execute.py -``` - -원격 저장소에 push까지 하려면 다음 명령을 사용합니다. - -```bash -python scripts/execute.py --push -``` - -실행기가 처리하는 일: - -- `feat-{task-name}` 브랜치 생성 또는 checkout -- `AGENTS.md`와 `docs/*.md`를 매 step 프롬프트에 주입 -- 완료된 step의 `summary`를 다음 step에 전달 -- 실패 시 최대 3회 재시도 -- step 상태를 `completed`, `blocked`, `error`로 관리 -- step 완료 후 문서 변경과 메타데이터를 커밋 - -## 피드백 게이트 - -사용자 검토가 필요한 단계에서는 step이 `blocked` 상태로 멈출 수 있습니다. - -초안 피드백: - -```text -docs/DraftFeedback.md -``` - -최종 피드백: - -```text -docs/FinalFeedback.md -``` - -피드백을 작성한 뒤 해당 step의 상태를 `pending`으로 되돌리고 다시 실행하면 다음 단계가 진행됩니다. - -## Codex Skills - -이 템플릿은 repo 공유 Skill을 사용합니다. - -`document-harness`: - -- PRD intake -- 작성 규칙 구체화 -- ResearchNote 작성 -- 초안 작성 -- 피드백 게이트 -- 최종 문서 작성 - -`document-review`: - -- PRD 정합성 검토 -- 출처 추적 검토 -- 초안/최종본 분리 확인 -- 피드백 반영 확인 -- 문체와 Markdown 구조 검토 - -Codex에서 명시적으로 호출할 수 있습니다. - -```text -$document-harness -$document-review -``` - -## Codex Custom Agents - -`.codex/agents/`에는 문서 작성에 특화된 역할이 정의되어 있습니다. - -| Agent | 역할 | -|-------|------| -| `doc_researcher` | PRD 키워드 조사, 출처 수집, `docs/ResearchNote.md` 작성 | -| `doc_drafter` | ResearchNote와 PRD를 바탕으로 `drafts/` 초안 작성 | -| `doc_reviewer` | PRD 정합성, 구조, 피드백 반영 여부 리뷰 | -| `evidence_checker` | 문서 주장과 ResearchNote 출처 연결 확인 | - -Codex는 subagent를 항상 자동으로 생성하지 않습니다. 병렬 조사나 독립 리뷰가 필요하면 프롬프트에서 명시적으로 요청하세요. - -예시: - -```text -doc_researcher와 evidence_checker 역할을 사용해 핵심 키워드를 병렬 조사하고 docs/ResearchNote.md를 정리해 주세요. -``` - -## Hooks - -`.codex/hooks.json`은 두 가지 기본 hook을 연결합니다. - -- `PreToolUse`: 위험한 shell 명령을 차단합니다. -- `Stop`: 응답 종료 시 `python scripts/validate_docs.py`를 실행해 템플릿 구조를 검증합니다. - -검증 실패 시 Codex가 문제를 고치도록 이어서 작업하게 만드는 용도입니다. - -## 검증 - -템플릿 구조를 확인합니다. - -```bash -python scripts/validate_docs.py -``` - -실행기 테스트를 실행합니다. - -```bash -python -m pytest scripts/test_execute.py -``` - -현재 기대 결과: - -```text -Document harness validation passed. -51 passed -``` - -## 문서 작성 규칙 - -- `docs/PRD.md`를 단일 요구사항 원천으로 사용합니다. -- 외부 사실, 통계, 최신 정보, 법/제도/가격/제품 정보는 `docs/ResearchNote.md`의 출처에 근거해야 합니다. -- 출처가 불명확한 내용은 최종 문서에 단정적으로 쓰지 않습니다. -- 초안은 `drafts/`, 최종 문서는 `final/`에 분리합니다. -- 사용자 피드백 파일은 삭제하거나 덮어쓰지 않습니다. -- 문서 제목은 `#`, 주요 섹션은 `##`, 하위 섹션은 `###`를 사용합니다. - -## 추천 사용 프롬프트 - -PRD 검토: - -```text -$document-harness를 사용해 docs/PRD.md가 문서 작성을 시작하기에 충분한지 검토해 주세요. -``` - -조사 노트 작성: - -```text -$document-harness를 사용해 docs/PRD.md의 중요 키워드를 웹 조사하고 docs/ResearchNote.md를 작성해 주세요. -``` - -초안 작성: - -```text -$document-harness를 사용해 docs/PRD.md와 docs/ResearchNote.md를 바탕으로 drafts/에 초안을 작성해 주세요. -``` - -문서 리뷰: - -```text -$document-review를 사용해 drafts/와 final/의 변경 사항을 검토해 주세요. -``` - -## 주의사항 - -- 자동 실행기는 git 저장소를 전제로 합니다. -- 최신 정보가 중요한 문서는 `docs/ResearchNote.md`에 조사 일시와 기준일을 남겨야 합니다. -- `AGENTS.md`에는 지속적으로 적용할 규칙만 두고, 긴 절차는 Skill에 둡니다. -- Claude Code용 `.claude/` 구조는 사용하지 않습니다. 이 템플릿은 Codex의 `AGENTS.md`, Skill, hook, custom agent 구조를 기준으로 합니다. diff --git a/Writing/Codex/docs/ADR.md b/Writing/Codex/docs/ADR.md deleted file mode 100644 index b9e580f..0000000 --- a/Writing/Codex/docs/ADR.md +++ /dev/null @@ -1,48 +0,0 @@ -# Architecture Decision Records - -## 철학 -이 템플릿의 핵심 가치는 사용자의 의도를 보존하면서도, 조사와 피드백을 통해 Markdown 문서 품질을 단계적으로 높이는 것이다. 빠르게 초안을 만들되, 근거 없는 최종본을 만들지 않는다. - ---- - -### ADR-001: Markdown-first 문서 산출 -**결정**: 모든 중간 산출물과 최종 산출물은 Markdown으로 작성한다. - -**이유**: Markdown은 버전 관리, 리뷰, 재사용, 자동 변환에 적합하고 AI Agent가 구조를 안정적으로 다루기 쉽다. - -**트레이드오프**: PDF, DOCX, 슬라이드 같은 최종 배포 형식은 별도 변환 단계가 필요하다. - -### ADR-002: PRD를 단일 요구사항 원천으로 사용 -**결정**: `docs/PRD.md`를 문서 목적, 독자, 범위, 톤, 키워드의 기준으로 삼는다. - -**이유**: 단계가 길어질수록 AI Agent가 임의로 목표를 확장할 위험이 있다. 단일 원천을 두면 초안과 최종본을 같은 기준으로 평가할 수 있다. - -**트레이드오프**: PRD가 빈약하면 후속 산출물도 흐려진다. 필요한 경우 PRD 보강을 먼저 요청해야 한다. - -### ADR-003: ResearchNote를 출처 장부로 사용 -**결정**: 웹 조사 결과와 출처 검증은 `docs/ResearchNote.md`에 먼저 정리한 뒤 문서에 반영한다. - -**이유**: 최종 문서에서 어떤 주장에 어떤 근거가 사용되었는지 추적할 수 있다. - -**트레이드오프**: 짧은 문서라도 조사 단계가 하나 추가된다. 대신 사실 오류와 출처 누락 위험을 줄인다. - -### ADR-004: 피드백 지점은 blocked 상태로 표현 -**결정**: 사용자 검토가 필요한 step은 `blocked` 상태와 구체적인 `blocked_reason`을 기록한다. - -**이유**: Harness 실행기가 사용자 개입이 필요한 지점을 명확히 멈출 수 있다. - -**트레이드오프**: 사용자가 피드백을 작성한 뒤 상태를 `pending`으로 되돌려 재실행해야 한다. - -### ADR-005: 초안과 최종본 분리 -**결정**: 초안은 `drafts/`, 최종본은 `final/`에 저장한다. - -**이유**: 사용자 검토 흔적과 최종 납품물을 명확히 분리할 수 있다. - -**트레이드오프**: 파일 수가 늘어난다. 대신 리뷰와 회귀 확인이 쉬워진다. - -### ADR-006: Codex의 AGENTS/Skill/Hook 구조로 이전 -**결정**: Claude 전용 `CLAUDE.md`, `.claude/commands`, `.claude/settings.json` 구조를 Codex의 `AGENTS.md`, `.agents/skills`, `.codex/hooks.json`, `.codex/agents` 구조로 이전한다. - -**이유**: Codex는 프로젝트 지침을 `AGENTS.md`로 읽고, 재사용 가능한 워크플로우를 Skill로 관리하며, lifecycle hook과 custom agent를 별도 디렉토리에서 구성한다. 템플릿의 의도를 Codex의 네이티브 구조에 맞추면 실행 맥락과 재사용성이 좋아진다. - -**트레이드오프**: Claude Code와의 직접 호환성은 낮아진다. 대신 Codex CLI, Skill, custom agent, hook을 기준으로 한 문서 작성 자동화가 명확해진다. diff --git a/Writing/Codex/docs/ARCHITECTURE.md b/Writing/Codex/docs/ARCHITECTURE.md deleted file mode 100644 index 3f86a87..0000000 --- a/Writing/Codex/docs/ARCHITECTURE.md +++ /dev/null @@ -1,74 +0,0 @@ -# 문서 작성 하네스 아키텍처 - -## 디렉토리 구조 -```text -. -├── AGENTS.md # Codex가 읽는 프로젝트별 문서 작성 규칙 -├── .agents/ -│ └── skills/ -│ ├── document-harness/ # 단계적 문서 작성 Skill -│ └── document-review/ # 문서 리뷰 Skill -├── .codex/ -│ ├── config.toml # Codex 기능, live web search, agent 한도 설정 -│ ├── hooks.json # Stop/PreToolUse hook 설정 -│ ├── hooks/ # hook 실행 스크립트 -│ └── agents/ # 조사/초안/리뷰 custom agents -├── docs/ -│ ├── PRD.md # 사용자 요구사항 원천 -│ ├── ResearchNote.md # 조사 노트와 출처 장부 -│ ├── DraftFeedback.md # 초안 피드백 -│ ├── FinalFeedback.md # 최종 문서 피드백 -│ ├── ARCHITECTURE.md # 하네스 구조 -│ ├── ADR.md # 문서 작성 의사결정 -│ └── UI_GUIDE.md # Markdown 스타일 가이드 -├── drafts/ # 검토용 초안 산출물 -├── final/ # 피드백 반영 최종 산출물 -├── phases/ # Harness task/step 계획과 상태 -└── scripts/ - ├── execute.py # codex exec 기반 step 순차 실행기 - ├── validate_docs.py # 문서 템플릿 기본 검증 - └── test_execute.py # execute.py 안전망 테스트 -``` - -## 데이터 흐름 -```text -사용자 입력 - -> docs/PRD.md - -> AGENTS.md 작성 규칙 구체화 - -> Codex/custom agents 웹 검색 및 출처 검증 - -> docs/ResearchNote.md - -> drafts/ 초안 작성 - -> docs/DraftFeedback.md 사용자 피드백 - -> final/ 최종 문서 작성 - -> docs/FinalFeedback.md 최종 피드백 또는 승인 -``` - -## Step 설계 패턴 -권장 phase는 아래 순서를 따른다. - -1. `rule-synthesis`: `docs/PRD.md`를 읽고 `AGENTS.md`의 문서 작성 규칙을 프로젝트에 맞게 구체화한다. -2. `research-note`: 웹 검색과 사용자가 제공한 자료를 바탕으로 `docs/ResearchNote.md`를 작성한다. 필요하면 `doc_researcher` agent를 사용한다. -3. `draft-documents`: `drafts/`에 사용자 검토용 초안을 작성한다. -4. `draft-feedback-gate`: `docs/DraftFeedback.md`가 비어 있으면 `blocked`로 멈추고 사용자 검토를 요청한다. -5. `final-documents`: 피드백을 반영해 `final/`에 최종 문서를 작성한다. -6. `final-feedback-gate`: `docs/FinalFeedback.md`에 승인 또는 추가 수정 요청이 없으면 `blocked`로 멈춘다. - -## Codex 구성 책임 -- `AGENTS.md`는 Codex가 매 작업에서 읽는 짧고 지속적인 규칙을 담는다. -- `.agents/skills/document-harness/`는 phase 생성, research, draft, feedback gate, final 작성 절차를 담는다. -- `.agents/skills/document-review/`는 변경된 Markdown 문서의 리뷰 체크리스트를 담는다. -- `.codex/agents/`는 조사, 초안 작성, 리뷰, 근거 점검 역할을 분리한다. -- `.codex/hooks.json`은 위험 명령 차단과 Stop 시점 문서 검증을 연결한다. - -## 상태 관리 -- `pending`: 아직 실행되지 않은 step. -- `completed`: step 산출물이 생성되었고 검증이 끝난 상태. -- `blocked`: 사용자 피드백, 자료 제공, 승인 등 외부 입력이 필요한 상태. -- `error`: 자동 수정 3회 후에도 실패한 상태. - -## 파일 책임 -- `docs/PRD.md`는 사용자의 의도와 요구사항을 보존한다. Codex가 임의로 요구사항을 바꾸지 않는다. -- `docs/ResearchNote.md`는 사실 검증의 근거 장부다. 최종 문서의 외부 주장은 이 파일의 출처와 연결되어야 한다. -- `drafts/`는 논의용이다. 문장이 거칠 수 있지만 구조와 근거는 검토 가능해야 한다. -- `final/`은 납품용이다. 사용자 피드백, 출처, 스타일 기준을 반영해야 한다. -- `phases/`의 `index.json`과 `stepN.md`는 독립 실행 가능한 작업 지시서다. diff --git a/Writing/Codex/docs/DraftFeedback.md b/Writing/Codex/docs/DraftFeedback.md deleted file mode 100644 index 06c8544..0000000 --- a/Writing/Codex/docs/DraftFeedback.md +++ /dev/null @@ -1,23 +0,0 @@ -# Draft Feedback - -초안 검토 후 사용자가 피드백을 남기는 파일이다. AI Agent는 이 파일을 읽고 `final/` 문서에 반영한다. - -## 검토 대상 -- `drafts/{파일명}` - -## 전체 판단 -- {예: 방향 승인 / 구조 수정 필요 / 추가 조사 필요 / 톤 변경 필요} - -## 수정 요청 -| 위치 | 요청 | 이유 | -|------|------|------| -| {섹션 또는 파일명} | {수정 요청} | {왜 필요한지} | - -## 추가로 포함할 내용 -- {추가 내용} - -## 제외하거나 줄일 내용 -- {삭제/축소할 내용} - -## 승인 여부 -{예: 초안 방향 승인 / 아직 승인하지 않음} diff --git a/Writing/Codex/docs/FinalFeedback.md b/Writing/Codex/docs/FinalFeedback.md deleted file mode 100644 index 95c388c..0000000 --- a/Writing/Codex/docs/FinalFeedback.md +++ /dev/null @@ -1,17 +0,0 @@ -# Final Feedback - -최종 문서 검토 후 사용자가 승인 또는 추가 수정 요청을 남기는 파일이다. - -## 검토 대상 -- `final/{파일명}` - -## 승인 여부 -{예: 승인 / 수정 후 승인 / 승인하지 않음} - -## 최종 수정 요청 -| 위치 | 요청 | 우선순위 | -|------|------|----------| -| {섹션 또는 파일명} | {수정 요청} | {높음/중간/낮음} | - -## 비고 -- {추가 의견} diff --git a/Writing/Codex/docs/PRD.md b/Writing/Codex/docs/PRD.md deleted file mode 100644 index 09fe5ee..0000000 --- a/Writing/Codex/docs/PRD.md +++ /dev/null @@ -1,68 +0,0 @@ -# PRD: {문서 프로젝트명} - -이 파일은 Codex 문서 작성 Harness의 출발점이다. 사용자는 아래 항목을 가능한 한 구체적으로 채운다. Codex는 이 문서를 기준으로 작성 규칙, 조사 계획, 초안, 최종 문서를 만든다. - -## 문서 목적 -{이 문서가 해결하려는 문제, 설득하려는 주장, 설명하려는 주제, 또는 독자가 얻어야 할 결과를 한 문단으로 작성} - -## 대상 독자 -- 주요 독자: {예: 경영진, 개발자, 학생, 고객, 정책 담당자} -- 독자의 배경지식: {초급/중급/전문가, 알고 있다고 가정해도 되는 것} -- 독자가 문서를 읽은 뒤 해야 할 행동: {결정, 학습, 실행, 검토, 공유 등} - -## 최종 산출물 -| 문서명 | 목적 | 예상 분량 | 필수 포함 요소 | -|--------|------|-----------|----------------| -| {예: executive-summary.md} | {요약/설득/보고} | {예: 2쪽} | {핵심 메시지, 근거, 권고안} | -| {예: full-report.md} | {상세 설명} | {예: 10쪽} | {배경, 분석, 결론, 참고문헌} | - -## 문서 개요 -{원하는 목차, 포함해야 할 흐름, 반드시 다뤄야 할 섹션을 작성} - -## 중요 키워드 -- {키워드 1} -- {키워드 2} -- {키워드 3} - -## 핵심 질문 -- {문서가 반드시 답해야 하는 질문 1} -- {문서가 반드시 답해야 하는 질문 2} -- {문서가 반드시 답해야 하는 질문 3} - -## 범위 -### 포함할 것 -- {포함 범위 1} -- {포함 범위 2} - -### 제외할 것 -- {제외 범위 1} -- {제외 범위 2} - -## 톤과 스타일 -- 톤: {예: 전문적, 차분한 보고서, 친근한 설명문, 강한 설득형} -- 언어: {예: 한국어, 영어, 한영 병기} -- 문체: {예: 간결한 문장, 긴 분석형 문단, bullet 중심} -- 금지 표현: {예: 과장 광고 문구, "혁신적인", "압도적인" 같은 근거 없는 표현} - -## 참고 자료 -사용자가 이미 가진 자료나 반드시 참고해야 할 링크를 적는다. - -| 제목 | URL 또는 파일 경로 | 참고 이유 | -|------|-------------------|-----------| -| {자료명} | {URL/path} | {왜 중요한지} | - -## 조사 요구사항 -- 검색해야 할 주제: {예: 시장 규모, 기술 동향, 경쟁 사례, 법적 요건} -- 선호 출처: {예: 공식 문서, 학술 논문, 정부/기관 자료, 기업 보고서} -- 피해야 할 출처: {예: 출처 불명 블로그, 홍보성 기사} -- 최신성 기준: {예: 최근 2년, 2025년 이후, 최신 버전} - -## 품질 기준 -- {예: 모든 핵심 주장에 출처를 달 것} -- {예: 결론 전에 대안과 반론을 함께 검토할 것} -- {예: 초안은 빠르게, 최종본은 문장 품질과 일관성을 엄격히 볼 것} - -## 사용자 피드백 방식 -- 초안 피드백 위치: `docs/DraftFeedback.md` -- 최종 피드백 위치: `docs/FinalFeedback.md` -- 승인 기준: {예: 사용자가 명시적으로 "승인"이라고 남기면 완료} diff --git a/Writing/Codex/docs/ResearchNote.md b/Writing/Codex/docs/ResearchNote.md deleted file mode 100644 index d04849a..0000000 --- a/Writing/Codex/docs/ResearchNote.md +++ /dev/null @@ -1,54 +0,0 @@ -# Research Note: {문서 프로젝트명} - -이 파일은 조사 내용과 출처를 보존하는 장부다. 최종 문서에 들어가는 외부 사실, 통계, 인용, 사례는 가능한 한 이 파일의 항목과 연결되어야 한다. - -## 조사 범위 -- 기준 PRD: `docs/PRD.md` -- 조사 주제: {조사할 주제} -- 제외 주제: {조사하지 않을 주제} -- 최신성 기준: {예: 최근 2년, 2025년 이후, 최신 공식 문서} - -## 조사 일시 -- 시작: {YYYY-MM-DD HH:mm, timezone} -- 종료: {YYYY-MM-DD HH:mm, timezone} -- 조사자: AI Agent - -## 검색어 -| 검색어 | 목적 | 결과 메모 | -|--------|------|-----------| -| {검색어} | {무엇을 확인하려 했는지} | {핵심 결과} | - -## 핵심 결론 -1. {조사에서 확인한 핵심 결론} -2. {조사에서 확인한 핵심 결론} -3. {조사에서 확인한 핵심 결론} - -## 출처 목록 -| ID | 제목 | URL | 게시일/확인일 | 신뢰도 | 관련 키워드 | -|----|------|-----|---------------|--------|-------------| -| S1 | {출처 제목} | {URL} | {날짜} | {공식/학술/언론/블로그 등} | {키워드} | - -## 출처별 메모 -### S1: {출처 제목} -- URL: {URL} -- 요지: {핵심 내용 요약} -- 문서에 쓸 수 있는 내용: {반영할 사실/사례/근거} -- 주의사항: {한계, 편향, 오래된 정보, 상충 자료} - -## 키워드별 정리 -### {키워드} -- 확인된 사실: {내용} -- 관련 출처: {S1, S2} -- 문서 반영 위치: {draft/final의 예상 섹션} - -## 쟁점과 상반된 주장 -| 쟁점 | 주장 A | 주장 B | 판단/처리 | -|------|--------|--------|-----------| -| {쟁점} | {내용과 출처} | {내용과 출처} | {문서에서 어떻게 다룰지} | - -## 확인 필요 -- {추가 확인이 필요한 사실} -- {사용자에게 물어봐야 할 내용} - -## 문서 반영 메모 -- {어떤 결론을 어떤 문서/섹션에 반영할지} diff --git a/Writing/Codex/docs/UI_GUIDE.md b/Writing/Codex/docs/UI_GUIDE.md deleted file mode 100644 index d535648..0000000 --- a/Writing/Codex/docs/UI_GUIDE.md +++ /dev/null @@ -1,54 +0,0 @@ -# Markdown 문서 스타일 가이드 - -## 원칙 -1. 독자의 다음 행동이 분명해야 한다. 설명문이라면 이해, 보고서라면 판단, 가이드라면 실행이 가능해야 한다. -2. 근거와 의견을 섞지 않는다. 사실, 해석, 권고를 구분해 쓴다. -3. AI가 쓴 듯한 일반론보다 사용자의 목적과 키워드에 맞춘 구체성을 우선한다. - -## AI 문서 안티패턴 -| 금지 사항 | 이유 | -|-----------|------| -| "오늘날 빠르게 변화하는 시대에" 같은 상투적 도입 | 정보 밀도가 낮고 AI 생성문처럼 보인다 | -| 근거 없는 최상급 표현 | 신뢰를 떨어뜨린다 | -| 출처 없는 통계와 수치 | 검증할 수 없다 | -| 같은 의미의 문장을 반복해 분량 늘리기 | 독자의 시간을 낭비한다 | -| 목차와 본문 제목 불일치 | 리뷰와 유지보수가 어려워진다 | -| PRD에 없는 독자나 목표 추가 | 사용자 의도를 벗어난다 | -| ResearchNote에 없는 외부 주장 단정 | 출처 추적이 끊긴다 | -| AGENTS.md와 Skill 지침 불일치 | Codex 실행 맥락이 흔들린다 | - -## 구조 -- 문서 제목은 `#` 하나만 사용한다. -- 주요 섹션은 `##`, 하위 섹션은 `###`를 사용한다. -- 한 섹션에는 하나의 중심 메시지만 둔다. -- 긴 목록은 표로 바꿀 수 있는지 검토한다. -- 결론 문서라면 "요약 -> 근거 -> 판단/권고 -> 한계" 순서를 우선 고려한다. -- 설명 문서라면 "맥락 -> 핵심 개념 -> 절차/예시 -> 주의사항" 순서를 우선 고려한다. - -## 문체 -- 문장은 가능한 한 짧게 쓴다. -- 모호한 주어를 피한다. -- "중요하다", "효과적이다"처럼 평가를 쓸 때는 이유나 근거를 바로 붙인다. -- 불확실한 정보는 확률적 표현 또는 확인 필요 표시를 사용한다. -- 한국어 문서에서는 불필요한 영어 약어를 피하고, 처음 등장할 때 풀어쓴다. - -## 출처 표기 -- 외부 사실은 문장 끝이나 문단 끝에 출처 링크를 붙인다. -- 긴 직접 인용보다 요약과 해석을 우선한다. -- 같은 출처를 반복해서 사용할 때도 어떤 주장에 연결되는지 분명히 한다. -- 출처가 상충하면 `docs/ResearchNote.md`의 "쟁점/상반된 주장"에 기록한다. - -## 표와 목록 -- 비교, 분류, 의사결정 기준은 표를 우선 검토한다. -- 순서가 중요한 절차는 번호 목록을 사용한다. -- 단순 나열은 bullet을 사용한다. -- 표는 너무 넓어지면 섹션을 나누거나 요약 표와 상세 설명을 분리한다. - -## 최종 점검 -- PRD의 목적과 대상 독자에 맞는가? -- 모든 핵심 질문에 답했는가? -- 외부 주장에 출처가 있는가? -- 초안 피드백이 반영되었는가? -- 문서 제목, 섹션 제목, 파일명이 산출물 목적과 맞는가? -- 최종 문서는 `final/` 아래에 있는가? -- Codex Skill, agent, hook 지침과 충돌하지 않는가? diff --git a/Writing/Codex/drafts/.gitkeep b/Writing/Codex/drafts/.gitkeep deleted file mode 100644 index 8b13789..0000000 --- a/Writing/Codex/drafts/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Writing/Codex/final/.gitkeep b/Writing/Codex/final/.gitkeep deleted file mode 100644 index 8b13789..0000000 --- a/Writing/Codex/final/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Writing/Codex/scripts/execute.py b/Writing/Codex/scripts/execute.py deleted file mode 100644 index 15410e3..0000000 --- a/Writing/Codex/scripts/execute.py +++ /dev/null @@ -1,426 +0,0 @@ -#!/usr/bin/env python3 -""" -Codex Harness Step Executor — phase 내 step을 순차 실행하고 자가 교정한다. - -Usage: - python3 scripts/execute.py [--push] -""" - -import argparse -import contextlib -import json -import os -import subprocess -import sys -import threading -import time -import types -from datetime import datetime, timezone, timedelta -from pathlib import Path -from typing import Optional - -ROOT = Path(__file__).resolve().parent.parent - - -@contextlib.contextmanager -def progress_indicator(label: str): - """터미널 진행 표시기. with 문으로 사용하며 .elapsed 로 경과 시간을 읽는다.""" - frames = "◐◓◑◒" - stop = threading.Event() - t0 = time.monotonic() - - def _animate(): - idx = 0 - while not stop.wait(0.12): - sec = int(time.monotonic() - t0) - sys.stderr.write(f"\r{frames[idx % len(frames)]} {label} [{sec}s]") - sys.stderr.flush() - idx += 1 - sys.stderr.write("\r" + " " * (len(label) + 20) + "\r") - sys.stderr.flush() - - th = threading.Thread(target=_animate, daemon=True) - th.start() - info = types.SimpleNamespace(elapsed=0.0) - try: - yield info - finally: - stop.set() - th.join() - info.elapsed = time.monotonic() - t0 - - -class StepExecutor: - """Phase 디렉토리 안의 step들을 Codex로 순차 실행하는 하네스.""" - - MAX_RETRIES = 3 - FEAT_MSG = "feat({phase}): step {num} — {name}" - CHORE_MSG = "chore({phase}): step {num} output" - TZ = timezone(timedelta(hours=9)) - - def __init__(self, phase_dir_name: str, *, auto_push: bool = False): - self._root = str(ROOT) - self._phases_dir = ROOT / "phases" - self._phase_dir = self._phases_dir / phase_dir_name - self._phase_dir_name = phase_dir_name - self._top_index_file = self._phases_dir / "index.json" - self._auto_push = auto_push - - if not self._phase_dir.is_dir(): - print(f"ERROR: {self._phase_dir} not found") - sys.exit(1) - - self._index_file = self._phase_dir / "index.json" - if not self._index_file.exists(): - print(f"ERROR: {self._index_file} not found") - sys.exit(1) - - idx = self._read_json(self._index_file) - self._project = idx.get("project", "project") - self._phase_name = idx.get("phase", phase_dir_name) - self._total = len(idx["steps"]) - - def run(self): - self._print_header() - self._check_blockers() - self._checkout_branch() - guardrails = self._load_guardrails() - self._ensure_created_at() - self._execute_all_steps(guardrails) - self._finalize() - - # --- timestamps --- - - def _stamp(self) -> str: - return datetime.now(self.TZ).strftime("%Y-%m-%dT%H:%M:%S%z") - - # --- JSON I/O --- - - @staticmethod - def _read_json(p: Path) -> dict: - return json.loads(p.read_text(encoding="utf-8")) - - @staticmethod - def _write_json(p: Path, data: dict): - p.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8") - - # --- git --- - - def _run_git(self, *args) -> subprocess.CompletedProcess: - cmd = ["git"] + list(args) - return subprocess.run(cmd, cwd=self._root, capture_output=True, text=True) - - def _checkout_branch(self): - branch = f"feat-{self._phase_name}" - - r = self._run_git("rev-parse", "--abbrev-ref", "HEAD") - if r.returncode != 0: - print(f" ERROR: git을 사용할 수 없거나 git repo가 아닙니다.") - print(f" {r.stderr.strip()}") - sys.exit(1) - - if r.stdout.strip() == branch: - return - - r = self._run_git("rev-parse", "--verify", branch) - r = self._run_git("checkout", branch) if r.returncode == 0 else self._run_git("checkout", "-b", branch) - - if r.returncode != 0: - print(f" ERROR: 브랜치 '{branch}' checkout 실패.") - print(f" {r.stderr.strip()}") - print(f" Hint: 변경사항을 stash하거나 commit한 후 다시 시도하세요.") - sys.exit(1) - - print(f" Branch: {branch}") - - def _commit_step(self, step_num: int, step_name: str): - output_rel = f"phases/{self._phase_dir_name}/step{step_num}-output.json" - index_rel = f"phases/{self._phase_dir_name}/index.json" - - self._run_git("add", "-A") - self._run_git("reset", "HEAD", "--", output_rel) - self._run_git("reset", "HEAD", "--", index_rel) - - if self._run_git("diff", "--cached", "--quiet").returncode != 0: - msg = self.FEAT_MSG.format(phase=self._phase_name, num=step_num, name=step_name) - r = self._run_git("commit", "-m", msg) - if r.returncode == 0: - print(f" Commit: {msg}") - else: - print(f" WARN: 코드 커밋 실패: {r.stderr.strip()}") - - self._run_git("add", "-A") - if self._run_git("diff", "--cached", "--quiet").returncode != 0: - msg = self.CHORE_MSG.format(phase=self._phase_name, num=step_num) - r = self._run_git("commit", "-m", msg) - if r.returncode != 0: - print(f" WARN: housekeeping 커밋 실패: {r.stderr.strip()}") - - # --- top-level index --- - - def _update_top_index(self, status: str): - if not self._top_index_file.exists(): - return - top = self._read_json(self._top_index_file) - ts = self._stamp() - for phase in top.get("phases", []): - if phase.get("dir") == self._phase_dir_name: - phase["status"] = status - ts_key = {"completed": "completed_at", "error": "failed_at", "blocked": "blocked_at"}.get(status) - if ts_key: - phase[ts_key] = ts - break - self._write_json(self._top_index_file, top) - - # --- guardrails & context --- - - def _load_guardrails(self) -> str: - sections = [] - agents_md = ROOT / "AGENTS.md" - if agents_md.exists(): - sections.append(f"## 프로젝트 규칙 (AGENTS.md)\n\n{agents_md.read_text(encoding='utf-8')}") - docs_dir = ROOT / "docs" - if docs_dir.is_dir(): - for doc in sorted(docs_dir.glob("*.md")): - sections.append(f"## {doc.stem}\n\n{doc.read_text(encoding='utf-8')}") - return "\n\n---\n\n".join(sections) if sections else "" - - @staticmethod - def _build_step_context(index: dict) -> str: - lines = [ - f"- Step {s['step']} ({s['name']}): {s['summary']}" - for s in index["steps"] - if s["status"] == "completed" and s.get("summary") - ] - if not lines: - return "" - return "## 이전 Step 산출물\n\n" + "\n".join(lines) + "\n\n" - - def _build_preamble(self, guardrails: str, step_context: str, - prev_error: Optional[str] = None) -> str: - retry_section = "" - if prev_error: - retry_section = ( - f"\n## ⚠ 이전 시도 실패 — 아래 에러를 반드시 참고하여 수정하라\n\n" - f"{prev_error}\n\n---\n\n" - ) - return ( - f"당신은 {self._project} 프로젝트의 Codex 문서 작성 에이전트입니다. 아래 step을 수행하세요.\n\n" - f"{guardrails}\n\n---\n\n" - f"{step_context}{retry_section}" - f"## 작업 규칙\n\n" - f"1. 이전 step에서 작성된 문서와 메모를 확인하고 일관성을 유지하라.\n" - f"2. 이 step에 명시된 작업만 수행하라. 추가 산출물이나 임의 요구사항을 만들지 마라.\n" - f"3. 기존 문서 구조와 피드백 기록을 깨뜨리지 마라.\n" - f"4. AC(Acceptance Criteria) 검증을 직접 실행하라.\n" - f"5. /phases/{self._phase_dir_name}/index.json의 해당 step status를 업데이트하라:\n" - f" - AC 통과 → \"completed\" + \"summary\" 필드에 이 step의 산출물을 한 줄로 요약\n" - f" - {self.MAX_RETRIES}회 수정 시도 후에도 실패 → \"error\" + \"error_message\" 기록\n" - f" - 사용자 개입이 필요한 경우 (API 키, 인증, 수동 설정 등) → \"blocked\" + \"blocked_reason\" 기록 후 즉시 중단\n" - f"6. 직접 git commit하지 마라. commit은 scripts/execute.py가 step 완료 후 수행한다.\n" - f"7. 병렬 조사나 독립 리뷰가 필요하고 step에서 허용했다면 .codex/agents의 custom agent 역할을 활용하라.\n\n---\n\n" - ) - - # --- Codex 호출 --- - - def _invoke_codex(self, step: dict, preamble: str) -> dict: - step_num, step_name = step["step"], step["name"] - step_file = self._phase_dir / f"step{step_num}.md" - - if not step_file.exists(): - print(f" ERROR: {step_file} not found") - sys.exit(1) - - prompt = preamble + step_file.read_text(encoding="utf-8") - cmd = ["codex", "exec", "--skip-git-repo-check", "--full-auto", "--json", "-"] - - try: - result = subprocess.run( - cmd, - cwd=self._root, - input=prompt, - capture_output=True, - text=True, - encoding="utf-8", - errors="replace", - timeout=1800, - ) - except FileNotFoundError: - print("\n ERROR: Codex CLI를 찾을 수 없습니다. `codex --version`이 실행되는지 확인하세요.") - sys.exit(1) - - if result.returncode != 0: - print(f"\n WARN: Codex가 비정상 종료됨 (code {result.returncode})") - if result.stderr: - print(f" stderr: {result.stderr[:500]}") - - output = { - "step": step_num, "name": step_name, - "exitCode": result.returncode, - "stdout": result.stdout, "stderr": result.stderr, - } - out_path = self._phase_dir / f"step{step_num}-output.json" - with open(out_path, "w", encoding="utf-8") as f: - json.dump(output, f, indent=2, ensure_ascii=False) - - return output - - # --- 헤더 & 검증 --- - - def _print_header(self): - print(f"\n{'='*60}") - print(f" Codex Harness Step Executor") - print(f" Phase: {self._phase_name} | Steps: {self._total}") - if self._auto_push: - print(f" Auto-push: enabled") - print(f"{'='*60}") - - def _check_blockers(self): - index = self._read_json(self._index_file) - for s in reversed(index["steps"]): - if s["status"] == "error": - print(f"\n ✗ Step {s['step']} ({s['name']}) failed.") - print(f" Error: {s.get('error_message', 'unknown')}") - print(f" Fix and reset status to 'pending' to retry.") - sys.exit(1) - if s["status"] == "blocked": - print(f"\n ⏸ Step {s['step']} ({s['name']}) blocked.") - print(f" Reason: {s.get('blocked_reason', 'unknown')}") - print(f" Resolve and reset status to 'pending' to retry.") - sys.exit(2) - if s["status"] != "pending": - break - - def _ensure_created_at(self): - index = self._read_json(self._index_file) - if "created_at" not in index: - index["created_at"] = self._stamp() - self._write_json(self._index_file, index) - - # --- 실행 루프 --- - - def _execute_single_step(self, step: dict, guardrails: str) -> bool: - """단일 step 실행 (재시도 포함). 완료되면 True, 실패/차단이면 False.""" - step_num, step_name = step["step"], step["name"] - done = sum(1 for s in self._read_json(self._index_file)["steps"] if s["status"] == "completed") - prev_error = None - - for attempt in range(1, self.MAX_RETRIES + 1): - index = self._read_json(self._index_file) - step_context = self._build_step_context(index) - preamble = self._build_preamble(guardrails, step_context, prev_error) - - tag = f"Step {step_num}/{self._total - 1} ({done} done): {step_name}" - if attempt > 1: - tag += f" [retry {attempt}/{self.MAX_RETRIES}]" - - with progress_indicator(tag) as pi: - self._invoke_codex(step, preamble) - elapsed = int(pi.elapsed) - - index = self._read_json(self._index_file) - status = next((s.get("status", "pending") for s in index["steps"] if s["step"] == step_num), "pending") - ts = self._stamp() - - if status == "completed": - for s in index["steps"]: - if s["step"] == step_num: - s["completed_at"] = ts - self._write_json(self._index_file, index) - self._commit_step(step_num, step_name) - print(f" ✓ Step {step_num}: {step_name} [{elapsed}s]") - return True - - if status == "blocked": - for s in index["steps"]: - if s["step"] == step_num: - s["blocked_at"] = ts - self._write_json(self._index_file, index) - reason = next((s.get("blocked_reason", "") for s in index["steps"] if s["step"] == step_num), "") - print(f" ⏸ Step {step_num}: {step_name} blocked [{elapsed}s]") - print(f" Reason: {reason}") - self._update_top_index("blocked") - sys.exit(2) - - err_msg = next( - (s.get("error_message", "Step did not update status") for s in index["steps"] if s["step"] == step_num), - "Step did not update status", - ) - - if attempt < self.MAX_RETRIES: - for s in index["steps"]: - if s["step"] == step_num: - s["status"] = "pending" - s.pop("error_message", None) - self._write_json(self._index_file, index) - prev_error = err_msg - print(f" ↻ Step {step_num}: retry {attempt}/{self.MAX_RETRIES} — {err_msg}") - else: - for s in index["steps"]: - if s["step"] == step_num: - s["status"] = "error" - s["error_message"] = f"[{self.MAX_RETRIES}회 시도 후 실패] {err_msg}" - s["failed_at"] = ts - self._write_json(self._index_file, index) - self._commit_step(step_num, step_name) - print(f" ✗ Step {step_num}: {step_name} failed after {self.MAX_RETRIES} attempts [{elapsed}s]") - print(f" Error: {err_msg}") - self._update_top_index("error") - sys.exit(1) - - return False # unreachable - - def _execute_all_steps(self, guardrails: str): - while True: - index = self._read_json(self._index_file) - pending = next((s for s in index["steps"] if s["status"] == "pending"), None) - if pending is None: - print("\n All steps completed!") - return - - step_num = pending["step"] - for s in index["steps"]: - if s["step"] == step_num and "started_at" not in s: - s["started_at"] = self._stamp() - self._write_json(self._index_file, index) - break - - self._execute_single_step(pending, guardrails) - - def _finalize(self): - index = self._read_json(self._index_file) - index["completed_at"] = self._stamp() - self._write_json(self._index_file, index) - self._update_top_index("completed") - - self._run_git("add", "-A") - if self._run_git("diff", "--cached", "--quiet").returncode != 0: - msg = f"chore({self._phase_name}): mark phase completed" - r = self._run_git("commit", "-m", msg) - if r.returncode == 0: - print(f" ✓ {msg}") - - if self._auto_push: - branch = f"feat-{self._phase_name}" - r = self._run_git("push", "-u", "origin", branch) - if r.returncode != 0: - print(f"\n ERROR: git push 실패: {r.stderr.strip()}") - sys.exit(1) - print(f" ✓ Pushed to origin/{branch}") - - print(f"\n{'='*60}") - print(f" Phase '{self._phase_name}' completed!") - print(f"{'='*60}") - - -def main(): - parser = argparse.ArgumentParser(description="Codex Harness Step Executor") - parser.add_argument("phase_dir", help="Phase directory name (e.g. 0-mvp)") - parser.add_argument("--push", action="store_true", help="Push branch after completion") - args = parser.parse_args() - - StepExecutor(args.phase_dir, auto_push=args.push).run() - - -if __name__ == "__main__": - main() diff --git a/Writing/Codex/scripts/test_execute.py b/Writing/Codex/scripts/test_execute.py deleted file mode 100644 index ecb0f2d..0000000 --- a/Writing/Codex/scripts/test_execute.py +++ /dev/null @@ -1,560 +0,0 @@ -""" -execute.py 리팩터링 안전망 테스트. -리팩터링 전후 동작이 동일한지 검증한다. -""" - -import json -import os -import subprocess -import sys -import textwrap -from datetime import datetime, timezone, timedelta -from pathlib import Path -from unittest.mock import patch, MagicMock - -import pytest - -sys.path.insert(0, str(Path(__file__).parent)) -import execute as ex - - -# --------------------------------------------------------------------------- -# Fixtures -# --------------------------------------------------------------------------- - -@pytest.fixture -def tmp_project(tmp_path): - """phases/, AGENTS.md, docs/ 를 갖춘 임시 프로젝트 구조.""" - phases_dir = tmp_path / "phases" - phases_dir.mkdir() - - agents_md = tmp_path / "AGENTS.md" - agents_md.write_text("# Rules\n- rule one\n- rule two", encoding="utf-8") - - docs_dir = tmp_path / "docs" - docs_dir.mkdir() - (docs_dir / "arch.md").write_text("# Architecture\nSome content", encoding="utf-8") - (docs_dir / "guide.md").write_text("# Guide\nAnother doc", encoding="utf-8") - - return tmp_path - - -@pytest.fixture -def phase_dir(tmp_project): - """step 3개를 가진 phase 디렉토리.""" - d = tmp_project / "phases" / "0-mvp" - d.mkdir() - - index = { - "project": "TestProject", - "phase": "mvp", - "steps": [ - {"step": 0, "name": "setup", "status": "completed", "summary": "프로젝트 초기화 완료"}, - {"step": 1, "name": "core", "status": "completed", "summary": "핵심 로직 구현"}, - {"step": 2, "name": "ui", "status": "pending"}, - ], - } - (d / "index.json").write_text(json.dumps(index, indent=2, ensure_ascii=False), encoding="utf-8") - (d / "step2.md").write_text("# Step 2: UI\n\nUI를 구현하세요.", encoding="utf-8") - - return d - - -@pytest.fixture -def top_index(tmp_project): - """phases/index.json (top-level).""" - top = { - "phases": [ - {"dir": "0-mvp", "status": "pending"}, - {"dir": "1-polish", "status": "pending"}, - ] - } - p = tmp_project / "phases" / "index.json" - p.write_text(json.dumps(top, indent=2), encoding="utf-8") - return p - - -@pytest.fixture -def executor(tmp_project, phase_dir): - """테스트용 StepExecutor 인스턴스. git 호출은 별도 mock 필요.""" - with patch.object(ex, "ROOT", tmp_project): - inst = ex.StepExecutor("0-mvp") - # 내부 경로를 tmp_project 기준으로 재설정 - inst._root = str(tmp_project) - inst._phases_dir = tmp_project / "phases" - inst._phase_dir = phase_dir - inst._phase_dir_name = "0-mvp" - inst._index_file = phase_dir / "index.json" - inst._top_index_file = tmp_project / "phases" / "index.json" - return inst - - -# --------------------------------------------------------------------------- -# _stamp (= 이전 now_iso) -# --------------------------------------------------------------------------- - -class TestStamp: - def test_returns_kst_timestamp(self, executor): - result = executor._stamp() - assert "+0900" in result - - def test_format_is_iso(self, executor): - result = executor._stamp() - dt = datetime.strptime(result, "%Y-%m-%dT%H:%M:%S%z") - assert dt.tzinfo is not None - - def test_is_current_time(self, executor): - before = datetime.now(ex.StepExecutor.TZ).replace(microsecond=0) - result = executor._stamp() - after = datetime.now(ex.StepExecutor.TZ).replace(microsecond=0) + timedelta(seconds=1) - parsed = datetime.strptime(result, "%Y-%m-%dT%H:%M:%S%z") - assert before <= parsed <= after - - -# --------------------------------------------------------------------------- -# _read_json / _write_json -# --------------------------------------------------------------------------- - -class TestJsonHelpers: - def test_roundtrip(self, tmp_path): - data = {"key": "값", "nested": [1, 2, 3]} - p = tmp_path / "test.json" - ex.StepExecutor._write_json(p, data) - loaded = ex.StepExecutor._read_json(p) - assert loaded == data - - def test_save_ensures_ascii_false(self, tmp_path): - p = tmp_path / "test.json" - ex.StepExecutor._write_json(p, {"한글": "테스트"}) - raw = p.read_text(encoding="utf-8") - assert "한글" in raw - assert "\\u" not in raw - - def test_save_indented(self, tmp_path): - p = tmp_path / "test.json" - ex.StepExecutor._write_json(p, {"a": 1}) - raw = p.read_text(encoding="utf-8") - assert "\n" in raw - - def test_load_nonexistent_raises(self, tmp_path): - with pytest.raises(FileNotFoundError): - ex.StepExecutor._read_json(tmp_path / "nope.json") - - -# --------------------------------------------------------------------------- -# _load_guardrails -# --------------------------------------------------------------------------- - -class TestLoadGuardrails: - def test_loads_agents_md_and_docs(self, executor, tmp_project): - with patch.object(ex, "ROOT", tmp_project): - result = executor._load_guardrails() - assert "# Rules" in result - assert "rule one" in result - assert "# Architecture" in result - assert "# Guide" in result - - def test_sections_separated_by_divider(self, executor, tmp_project): - with patch.object(ex, "ROOT", tmp_project): - result = executor._load_guardrails() - assert "---" in result - - def test_docs_sorted_alphabetically(self, executor, tmp_project): - with patch.object(ex, "ROOT", tmp_project): - result = executor._load_guardrails() - arch_pos = result.index("arch") - guide_pos = result.index("guide") - assert arch_pos < guide_pos - - def test_no_agents_md(self, executor, tmp_project): - (tmp_project / "AGENTS.md").unlink() - with patch.object(ex, "ROOT", tmp_project): - result = executor._load_guardrails() - assert "AGENTS.md" not in result - assert "Architecture" in result - - def test_no_docs_dir(self, executor, tmp_project): - import shutil - shutil.rmtree(tmp_project / "docs") - with patch.object(ex, "ROOT", tmp_project): - result = executor._load_guardrails() - assert "Rules" in result - assert "Architecture" not in result - - def test_empty_project(self, tmp_path): - with patch.object(ex, "ROOT", tmp_path): - # executor가 필요 없는 static-like 동작이므로 임시 인스턴스 - phases_dir = tmp_path / "phases" / "dummy" - phases_dir.mkdir(parents=True) - idx = {"project": "T", "phase": "t", "steps": []} - (phases_dir / "index.json").write_text(json.dumps(idx), encoding="utf-8") - inst = ex.StepExecutor.__new__(ex.StepExecutor) - result = inst._load_guardrails() - assert result == "" - - -# --------------------------------------------------------------------------- -# _build_step_context -# --------------------------------------------------------------------------- - -class TestBuildStepContext: - def test_includes_completed_with_summary(self, phase_dir): - index = json.loads((phase_dir / "index.json").read_text(encoding="utf-8")) - result = ex.StepExecutor._build_step_context(index) - assert "Step 0 (setup): 프로젝트 초기화 완료" in result - assert "Step 1 (core): 핵심 로직 구현" in result - - def test_excludes_pending(self, phase_dir): - index = json.loads((phase_dir / "index.json").read_text(encoding="utf-8")) - result = ex.StepExecutor._build_step_context(index) - assert "ui" not in result - - def test_excludes_completed_without_summary(self, phase_dir): - index = json.loads((phase_dir / "index.json").read_text(encoding="utf-8")) - del index["steps"][0]["summary"] - result = ex.StepExecutor._build_step_context(index) - assert "setup" not in result - assert "core" in result - - def test_empty_when_no_completed(self): - index = {"steps": [{"step": 0, "name": "a", "status": "pending"}]} - result = ex.StepExecutor._build_step_context(index) - assert result == "" - - def test_has_header(self, phase_dir): - index = json.loads((phase_dir / "index.json").read_text(encoding="utf-8")) - result = ex.StepExecutor._build_step_context(index) - assert result.startswith("## 이전 Step 산출물") - - -# --------------------------------------------------------------------------- -# _build_preamble -# --------------------------------------------------------------------------- - -class TestBuildPreamble: - def test_includes_project_name(self, executor): - result = executor._build_preamble("", "") - assert "TestProject" in result - - def test_includes_guardrails(self, executor): - result = executor._build_preamble("GUARD_CONTENT", "") - assert "GUARD_CONTENT" in result - - def test_includes_step_context(self, executor): - ctx = "## 이전 Step 산출물\n\n- Step 0: done" - result = executor._build_preamble("", ctx) - assert "이전 Step 산출물" in result - - def test_tells_agent_not_to_commit_directly(self, executor): - result = executor._build_preamble("", "") - assert "직접 git commit하지 마라" in result - - def test_includes_rules(self, executor): - result = executor._build_preamble("", "") - assert "작업 규칙" in result - assert "AC" in result - - def test_no_retry_section_by_default(self, executor): - result = executor._build_preamble("", "") - assert "이전 시도 실패" not in result - - def test_retry_section_with_prev_error(self, executor): - result = executor._build_preamble("", "", prev_error="타입 에러 발생") - assert "이전 시도 실패" in result - assert "타입 에러 발생" in result - - def test_includes_max_retries(self, executor): - result = executor._build_preamble("", "") - assert str(ex.StepExecutor.MAX_RETRIES) in result - - def test_includes_index_path(self, executor): - result = executor._build_preamble("", "") - assert "/phases/0-mvp/index.json" in result - - -# --------------------------------------------------------------------------- -# _update_top_index -# --------------------------------------------------------------------------- - -class TestUpdateTopIndex: - def test_completed(self, executor, top_index): - executor._top_index_file = top_index - executor._update_top_index("completed") - data = json.loads(top_index.read_text(encoding="utf-8")) - mvp = next(p for p in data["phases"] if p["dir"] == "0-mvp") - assert mvp["status"] == "completed" - assert "completed_at" in mvp - - def test_error(self, executor, top_index): - executor._top_index_file = top_index - executor._update_top_index("error") - data = json.loads(top_index.read_text(encoding="utf-8")) - mvp = next(p for p in data["phases"] if p["dir"] == "0-mvp") - assert mvp["status"] == "error" - assert "failed_at" in mvp - - def test_blocked(self, executor, top_index): - executor._top_index_file = top_index - executor._update_top_index("blocked") - data = json.loads(top_index.read_text(encoding="utf-8")) - mvp = next(p for p in data["phases"] if p["dir"] == "0-mvp") - assert mvp["status"] == "blocked" - assert "blocked_at" in mvp - - def test_other_phases_unchanged(self, executor, top_index): - executor._top_index_file = top_index - executor._update_top_index("completed") - data = json.loads(top_index.read_text(encoding="utf-8")) - polish = next(p for p in data["phases"] if p["dir"] == "1-polish") - assert polish["status"] == "pending" - - def test_nonexistent_dir_is_noop(self, executor, top_index): - executor._top_index_file = top_index - executor._phase_dir_name = "no-such-dir" - original = json.loads(top_index.read_text(encoding="utf-8")) - executor._update_top_index("completed") - after = json.loads(top_index.read_text(encoding="utf-8")) - for p_before, p_after in zip(original["phases"], after["phases"]): - assert p_before["status"] == p_after["status"] - - def test_no_top_index_file(self, executor, tmp_path): - executor._top_index_file = tmp_path / "nonexistent.json" - executor._update_top_index("completed") # should not raise - - -# --------------------------------------------------------------------------- -# _checkout_branch (mocked) -# --------------------------------------------------------------------------- - -class TestCheckoutBranch: - def _mock_git(self, executor, responses): - call_idx = {"i": 0} - def fake_git(*args): - idx = call_idx["i"] - call_idx["i"] += 1 - if idx < len(responses): - return responses[idx] - return MagicMock(returncode=0, stdout="", stderr="") - executor._run_git = fake_git - - def test_already_on_branch(self, executor): - self._mock_git(executor, [ - MagicMock(returncode=0, stdout="feat-mvp\n", stderr=""), - ]) - executor._checkout_branch() # should return without checkout - - def test_branch_exists_checkout(self, executor): - self._mock_git(executor, [ - MagicMock(returncode=0, stdout="main\n", stderr=""), - MagicMock(returncode=0, stdout="", stderr=""), - MagicMock(returncode=0, stdout="", stderr=""), - ]) - executor._checkout_branch() - - def test_branch_not_exists_create(self, executor): - self._mock_git(executor, [ - MagicMock(returncode=0, stdout="main\n", stderr=""), - MagicMock(returncode=1, stdout="", stderr="not found"), - MagicMock(returncode=0, stdout="", stderr=""), - ]) - executor._checkout_branch() - - def test_checkout_fails_exits(self, executor): - self._mock_git(executor, [ - MagicMock(returncode=0, stdout="main\n", stderr=""), - MagicMock(returncode=1, stdout="", stderr=""), - MagicMock(returncode=1, stdout="", stderr="dirty tree"), - ]) - with pytest.raises(SystemExit) as exc_info: - executor._checkout_branch() - assert exc_info.value.code == 1 - - def test_no_git_exits(self, executor): - self._mock_git(executor, [ - MagicMock(returncode=1, stdout="", stderr="not a git repo"), - ]) - with pytest.raises(SystemExit) as exc_info: - executor._checkout_branch() - assert exc_info.value.code == 1 - - -# --------------------------------------------------------------------------- -# _commit_step (mocked) -# --------------------------------------------------------------------------- - -class TestCommitStep: - def test_two_phase_commit(self, executor): - calls = [] - def fake_git(*args): - calls.append(args) - if args[:2] == ("diff", "--cached"): - return MagicMock(returncode=1) - return MagicMock(returncode=0, stdout="", stderr="") - executor._run_git = fake_git - - executor._commit_step(2, "ui") - - commit_calls = [c for c in calls if c[0] == "commit"] - assert len(commit_calls) == 2 - assert "feat(mvp):" in commit_calls[0][2] - assert "chore(mvp):" in commit_calls[1][2] - - def test_no_code_changes_skips_feat_commit(self, executor): - call_count = {"diff": 0} - calls = [] - def fake_git(*args): - calls.append(args) - if args[:2] == ("diff", "--cached"): - call_count["diff"] += 1 - if call_count["diff"] == 1: - return MagicMock(returncode=0) - return MagicMock(returncode=1) - return MagicMock(returncode=0, stdout="", stderr="") - executor._run_git = fake_git - - executor._commit_step(2, "ui") - - commit_msgs = [c[2] for c in calls if c[0] == "commit"] - assert len(commit_msgs) == 1 - assert "chore" in commit_msgs[0] - - -# --------------------------------------------------------------------------- -# _invoke_codex (mocked) -# --------------------------------------------------------------------------- - -class TestInvokeCodex: - def test_invokes_codex_with_correct_args(self, executor): - mock_result = MagicMock(returncode=0, stdout='{"result": "ok"}', stderr="") - step = {"step": 2, "name": "ui"} - preamble = "PREAMBLE\n" - - with patch("subprocess.run", return_value=mock_result) as mock_run: - output = executor._invoke_codex(step, preamble) - - cmd = mock_run.call_args[0][0] - assert cmd[:2] == ["codex", "exec"] - assert "--skip-git-repo-check" in cmd - assert "--full-auto" in cmd - assert "--json" in cmd - assert cmd[-1] == "-" - assert "PREAMBLE" in mock_run.call_args[1]["input"] - assert "UI를 구현하세요" in mock_run.call_args[1]["input"] - - def test_saves_output_json(self, executor): - mock_result = MagicMock(returncode=0, stdout='{"ok": true}', stderr="") - step = {"step": 2, "name": "ui"} - - with patch("subprocess.run", return_value=mock_result): - executor._invoke_codex(step, "preamble") - - output_file = executor._phase_dir / "step2-output.json" - assert output_file.exists() - data = json.loads(output_file.read_text(encoding="utf-8")) - assert data["step"] == 2 - assert data["name"] == "ui" - assert data["exitCode"] == 0 - - def test_nonexistent_step_file_exits(self, executor): - step = {"step": 99, "name": "nonexistent"} - with pytest.raises(SystemExit) as exc_info: - executor._invoke_codex(step, "preamble") - assert exc_info.value.code == 1 - - def test_timeout_is_1800(self, executor): - mock_result = MagicMock(returncode=0, stdout="{}", stderr="") - step = {"step": 2, "name": "ui"} - - with patch("subprocess.run", return_value=mock_result) as mock_run: - executor._invoke_codex(step, "preamble") - - assert mock_run.call_args[1]["timeout"] == 1800 - - -# --------------------------------------------------------------------------- -# progress_indicator (= 이전 Spinner) -# --------------------------------------------------------------------------- - -class TestProgressIndicator: - def test_context_manager(self): - import time - with ex.progress_indicator("test") as pi: - time.sleep(0.15) - assert pi.elapsed >= 0.1 - - def test_elapsed_increases(self): - import time - with ex.progress_indicator("test") as pi: - time.sleep(0.2) - assert pi.elapsed > 0 - - -# --------------------------------------------------------------------------- -# main() CLI 파싱 (mocked) -# --------------------------------------------------------------------------- - -class TestMainCli: - def test_no_args_exits(self): - with patch("sys.argv", ["execute.py"]): - with pytest.raises(SystemExit) as exc_info: - ex.main() - assert exc_info.value.code == 2 # argparse exits with 2 - - def test_invalid_phase_dir_exits(self): - with patch("sys.argv", ["execute.py", "nonexistent"]): - with patch.object(ex, "ROOT", Path("/tmp/fake_nonexistent")): - with pytest.raises(SystemExit) as exc_info: - ex.main() - assert exc_info.value.code == 1 - - def test_missing_index_exits(self, tmp_project): - (tmp_project / "phases" / "empty").mkdir() - with patch("sys.argv", ["execute.py", "empty"]): - with patch.object(ex, "ROOT", tmp_project): - with pytest.raises(SystemExit) as exc_info: - ex.main() - assert exc_info.value.code == 1 - - -# --------------------------------------------------------------------------- -# _check_blockers (= 이전 main() error/blocked 체크) -# --------------------------------------------------------------------------- - -class TestCheckBlockers: - def _make_executor_with_steps(self, tmp_project, steps): - d = tmp_project / "phases" / "test-phase" - d.mkdir(exist_ok=True) - index = {"project": "T", "phase": "test", "steps": steps} - (d / "index.json").write_text(json.dumps(index), encoding="utf-8") - - with patch.object(ex, "ROOT", tmp_project): - inst = ex.StepExecutor.__new__(ex.StepExecutor) - inst._root = str(tmp_project) - inst._phases_dir = tmp_project / "phases" - inst._phase_dir = d - inst._phase_dir_name = "test-phase" - inst._index_file = d / "index.json" - inst._top_index_file = tmp_project / "phases" / "index.json" - inst._phase_name = "test" - inst._total = len(steps) - return inst - - def test_error_step_exits_1(self, tmp_project): - steps = [ - {"step": 0, "name": "ok", "status": "completed"}, - {"step": 1, "name": "bad", "status": "error", "error_message": "fail"}, - ] - inst = self._make_executor_with_steps(tmp_project, steps) - with pytest.raises(SystemExit) as exc_info: - inst._check_blockers() - assert exc_info.value.code == 1 - - def test_blocked_step_exits_2(self, tmp_project): - steps = [ - {"step": 0, "name": "ok", "status": "completed"}, - {"step": 1, "name": "stuck", "status": "blocked", "blocked_reason": "API key"}, - ] - inst = self._make_executor_with_steps(tmp_project, steps) - with pytest.raises(SystemExit) as exc_info: - inst._check_blockers() - assert exc_info.value.code == 2 diff --git a/Writing/Codex/scripts/validate_docs.py b/Writing/Codex/scripts/validate_docs.py deleted file mode 100644 index 2bdfba1..0000000 --- a/Writing/Codex/scripts/validate_docs.py +++ /dev/null @@ -1,196 +0,0 @@ -#!/usr/bin/env python3 -""" -Basic validation for the Markdown document harness template. - -This check is intentionally lightweight: it verifies that the template files -exist and keep the sections that later Harness steps depend on. -""" - -import json -from pathlib import Path -import sys - -try: - import tomllib -except ModuleNotFoundError: # pragma: no cover - Python < 3.11 compatibility - tomllib = None - - -ROOT = Path(__file__).resolve().parent.parent - -REQUIRED_FILES = [ - "README.md", - "AGENTS.md", - "docs/PRD.md", - "docs/ResearchNote.md", - "docs/DraftFeedback.md", - "docs/FinalFeedback.md", - "docs/ARCHITECTURE.md", - "docs/ADR.md", - "docs/UI_GUIDE.md", - ".agents/skills/document-harness/SKILL.md", - ".agents/skills/document-harness/references/phase-templates.md", - ".agents/skills/document-review/SKILL.md", - ".codex/config.toml", - ".codex/hooks.json", - ".codex/hooks/pre_tool_guard.py", - ".codex/hooks/stop_validate.py", - ".codex/agents/doc_researcher.toml", - ".codex/agents/doc_drafter.toml", - ".codex/agents/doc_reviewer.toml", - ".codex/agents/evidence_checker.toml", -] - -REQUIRED_DIRS = [ - "docs", - "scripts", - ".agents", - ".agents/skills", - ".agents/skills/document-harness", - ".agents/skills/document-review", - ".codex", - ".codex/hooks", - ".codex/agents", -] - -REQUIRED_SECTIONS = { - "README.md": [ - "## 핵심 아이디어", - "## Codex 구성", - "## 빠른 시작", - "## 자동 실행 방식", - "## 피드백 게이트", - "## 검증", - ], - "docs/PRD.md": [ - "## 문서 목적", - "## 대상 독자", - "## 최종 산출물", - "## 문서 개요", - "## 중요 키워드", - "## 핵심 질문", - "## 범위", - "## 톤과 스타일", - "## 조사 요구사항", - "## 사용자 피드백 방식", - ], - "docs/ResearchNote.md": [ - "## 조사 범위", - "## 조사 일시", - "## 검색어", - "## 핵심 결론", - "## 출처 목록", - "## 쟁점과 상반된 주장", - "## 확인 필요", - ], - "AGENTS.md": [ - "## 목적", - "## Codex 구성", - "## 기본 산출물", - "## 문서 작성 규칙", - "## Codex 작업 규칙", - "## 권장 워크플로우", - "## 명령어", - ], - ".agents/skills/document-harness/SKILL.md": [ - "# Document Harness Skill", - "## Operating Rules", - "## Staged Workflow", - "## Validation", - ], - ".agents/skills/document-review/SKILL.md": [ - "# Document Review Skill", - "## Read First", - "## Review Checklist", - "## Output Format", - ], -} - -REQUIRED_JSON_FILES = [ - ".codex/hooks.json", -] - -REQUIRED_TOML_FILES = [ - ".codex/config.toml", - ".codex/agents/doc_researcher.toml", - ".codex/agents/doc_drafter.toml", - ".codex/agents/doc_reviewer.toml", - ".codex/agents/evidence_checker.toml", -] - - -def first_nonempty_line(path: Path) -> str: - for line in path.read_text(encoding="utf-8").splitlines(): - if line.strip(): - return line.strip() - return "" - - -def markdown_file_has_valid_start(path: Path) -> bool: - first = first_nonempty_line(path) - if first.startswith("# "): - return True - if first == "---" and path.name == "SKILL.md": - return True - return False - - -def main() -> int: - errors: list[str] = [] - - for rel in REQUIRED_DIRS: - path = ROOT / rel - if not path.is_dir(): - errors.append(f"missing directory: {rel}") - - for rel in REQUIRED_FILES: - path = ROOT / rel - if not path.is_file(): - errors.append(f"missing file: {rel}") - continue - - if path.suffix == ".md": - if not markdown_file_has_valid_start(path): - errors.append(f"markdown file must start with a level-1 heading or Skill frontmatter: {rel}") - - for rel, sections in REQUIRED_SECTIONS.items(): - path = ROOT / rel - if not path.is_file(): - continue - - text = path.read_text(encoding="utf-8") - for section in sections: - if section not in text: - errors.append(f"missing section in {rel}: {section}") - - for rel in REQUIRED_JSON_FILES: - path = ROOT / rel - if not path.is_file(): - continue - try: - json.loads(path.read_text(encoding="utf-8")) - except json.JSONDecodeError as exc: - errors.append(f"invalid JSON in {rel}: {exc}") - - if tomllib is not None: - for rel in REQUIRED_TOML_FILES: - path = ROOT / rel - if not path.is_file(): - continue - try: - tomllib.loads(path.read_text(encoding="utf-8")) - except tomllib.TOMLDecodeError as exc: - errors.append(f"invalid TOML in {rel}: {exc}") - - if errors: - print("Document harness validation failed:") - for error in errors: - print(f"- {error}") - return 1 - - print("Document harness validation passed.") - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Writing/Gemini/.agents/skills/document-harness/SKILL.md b/Writing/Gemini/.agents/skills/document-harness/SKILL.md deleted file mode 100644 index 704f7a5..0000000 --- a/Writing/Gemini/.agents/skills/document-harness/SKILL.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -name: "document-harness" -description: "Use when creating, running, or updating the staged Markdown document-writing Harness from docs/PRD.md through research notes, drafts, feedback gates, and final documents." ---- - -# Document Harness Skill - -Use this skill to turn `docs/PRD.md` into researched, reviewable, and feedback-driven Markdown documents. - -## Operating Rules - -1. Read `GEMINI.md`, `docs/PRD.md`, `docs/ARCHITECTURE.md`, `docs/ADR.md`, and `docs/UI_GUIDE.md` before planning document work. -2. Treat `docs/PRD.md` as the single source of requirements. -3. If PRD purpose, target reader, final deliverables, scope, or key questions are materially empty, stop and ask the user to complete PRD first. -4. Use `docs/ResearchNote.md` as the evidence ledger before drafting externally factual content. -5. Store review drafts under `drafts/` and final deliverables under `final/`. -6. Preserve `docs/DraftFeedback.md` and `docs/FinalFeedback.md`; never delete user feedback. -7. Run `python scripts/validate_docs.py` before reporting completion. - -## Staged Workflow - -### 1. PRD Intake - -Read `docs/PRD.md` and identify: - -- document purpose -- target readers -- final deliverables -- required outline -- important keywords -- key questions -- scope boundaries -- tone and style constraints -- research requirements - -### 2. Rule Synthesis - -Update only the relevant project-specific guidance in `GEMINI.md`. - -Include: - -- document purpose and target readers -- final deliverables -- tone and style rules -- citation and verification standards -- draft and final feedback process - -Keep the generic Gemini CLI configuration and repository workflow concise. - -### 3. Research Note - -Research PRD keywords and key questions. Prefer official, academic, government, institutional, or other primary sources. - -Write `docs/ResearchNote.md` with: - -- search date -- search terms -- source URLs -- source quality notes -- core findings -- conflicting claims -- unresolved questions -- intended document usage - -Use `doc-researcher` or `evidence-checker` subagents when the user or current phase explicitly asks for subagent work. - -### 4. Draft Documents - -Create all PRD deliverables under `drafts/`. - -Drafts must: - -- answer the PRD key questions -- stay inside PRD scope -- use the requested tone -- link factual claims to `docs/ResearchNote.md` -- mark weak or missing evidence - -After drafting, request user review in `docs/DraftFeedback.md`. - -### 5. Draft Feedback Gate - -If `docs/DraftFeedback.md` has no actionable user feedback or approval, mark the phase step as `blocked` with a clear `blocked_reason`. - -If feedback exists, summarize it before revising. - -### 6. Final Documents - -Create final deliverables under `final/`. Do not overwrite `drafts/`. - -Final documents must reflect: - -- PRD requirements -- ResearchNote evidence -- DraftFeedback requests -- UI guide style rules - -After finalizing, request user review or approval in `docs/FinalFeedback.md`. - -### 7. Final Feedback Gate - -If `docs/FinalFeedback.md` does not contain approval or actionable next feedback, mark the phase step as `blocked`. - -If approval exists, mark the phase completed. - -## Phase Files - -When creating a new phase, use `references/phase-templates.md`. - -Each step must include: - -- files to read -- exact task -- acceptance criteria -- validation procedure -- status update instructions -- concrete forbidden actions - -## Validation - -Always run: - -```bash -python scripts/validate_docs.py -``` - -For executor changes, also run: - -```bash -python -m pytest scripts/test_execute.py -``` diff --git a/Writing/Gemini/.agents/skills/document-harness/references/phase-templates.md b/Writing/Gemini/.agents/skills/document-harness/references/phase-templates.md deleted file mode 100644 index 95cc266..0000000 --- a/Writing/Gemini/.agents/skills/document-harness/references/phase-templates.md +++ /dev/null @@ -1,85 +0,0 @@ -# Document Harness Phase Templates - -## Top-Level Phase Index - -Create or update `phases/index.json`. - -```json -{ - "phases": [ - { - "dir": "0-document", - "status": "pending" - } - ] -} -``` - -## Task Index - -Create `phases/{task-name}/index.json`. - -```json -{ - "project": "<문서 프로젝트명>", - "phase": "", - "steps": [ - { "step": 0, "name": "rule-synthesis", "status": "pending" }, - { "step": 1, "name": "research-note", "status": "pending" }, - { "step": 2, "name": "draft-documents", "status": "pending" }, - { "step": 3, "name": "draft-feedback-gate", "status": "pending" }, - { "step": 4, "name": "final-documents", "status": "pending" }, - { "step": 5, "name": "final-feedback-gate", "status": "pending" } - ] -} -``` - -## Step File - -Create `phases/{task-name}/step{N}.md`. - -```markdown -# Step {N}: {이름} - -## 읽어야 할 파일 - -먼저 아래 파일들을 읽고 문서 목적과 작성 기준을 파악하라: - -- `/GEMINI.md` -- `/docs/PRD.md` -- `/docs/ARCHITECTURE.md` -- `/docs/ADR.md` -- `/docs/UI_GUIDE.md` -- {이전 step에서 생성/수정된 파일 경로} - -## 작업 - -{구체적인 문서 작성 또는 검토 지시. 파일 경로, 산출물 이름, 반영해야 할 PRD 항목, 출처 기준을 포함한다.} - -## Acceptance Criteria - -```bash -python scripts/validate_docs.py -``` - -## 검증 절차 - -1. 위 AC 커맨드를 실행한다. -2. 문서 체크리스트를 확인한다: - - `docs/PRD.md`의 목적, 독자, 범위를 벗어나지 않았는가? - - 외부 사실은 `docs/ResearchNote.md`의 출처와 연결되는가? - - 초안은 `drafts/`, 최종본은 `final/`에 분리되었는가? - - 사용자 피드백 파일을 삭제하거나 덮어쓰지 않았는가? -3. 결과에 따라 `phases/{task-name}/index.json`의 해당 step을 업데이트한다: - - 성공 -> `"status": "completed"`, `"summary": "산출물 한 줄 요약"` - - 수정 3회 시도 후에도 실패 -> `"status": "error"`, `"error_message": "구체적 에러 내용"` - - 사용자 개입 필요 -> `"status": "blocked"`, `"blocked_reason": "구체적 요청 사항"` 후 즉시 중단 - -## 금지사항 - -- PRD에 없는 문서 목표를 추가하지 마라. 이유: 사용자 의도가 흐려진다. -- 출처 없는 외부 사실을 최종 문서에 단정하지 마라. 이유: 검증 가능성이 사라진다. -- 초안 파일을 최종본으로 덮어쓰지 마라. 이유: 피드백 전후 변경 추적이 어렵다. -- 사용자 피드백 파일을 삭제하지 마라. 이유: 의사결정 기록이 사라진다. -- 직접 `git commit`하지 마라. 이유: `scripts/execute.py`가 step 완료 후 커밋을 관리한다. -``` diff --git a/Writing/Gemini/.agents/skills/document-review/SKILL.md b/Writing/Gemini/.agents/skills/document-review/SKILL.md deleted file mode 100644 index af545b3..0000000 --- a/Writing/Gemini/.agents/skills/document-review/SKILL.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: "document-review" -description: "Use when reviewing Markdown document changes for PRD alignment, source traceability, feedback coverage, structure, and final delivery readiness." ---- - -# Document Review Skill - -Use this skill to review changed Markdown files in the Gemini CLI Markdown Document Harness. - -## Read First - -- `GEMINI.md` -- `docs/PRD.md` -- `docs/ResearchNote.md` -- `docs/DraftFeedback.md` -- `docs/FinalFeedback.md` -- `docs/ARCHITECTURE.md` -- `docs/ADR.md` -- `docs/UI_GUIDE.md` - -## Review Checklist - -1. PRD alignment: purpose, target reader, deliverables, scope, and tone match `docs/PRD.md`. -2. Source traceability: external facts, dates, statistics, claims, and quotations connect to `docs/ResearchNote.md`. -3. Structure: heading hierarchy, section order, and file names match the intended deliverables. -4. Feedback coverage: `docs/DraftFeedback.md` or `docs/FinalFeedback.md` requests are addressed. -5. Draft/final separation: drafts live under `drafts/`; final deliverables live under `final/`. -6. Style quality: avoid generic AI prose, unsupported superlatives, repetition, and vague claims. -7. Validation: `python scripts/validate_docs.py` passes. - -## Output Format - -Lead with findings. Use this table when a full checklist result is useful: - -| 항목 | 결과 | 비고 | -|------|------|------| -| PRD 정합성 | PASS/FAIL | {상세} | -| 출처 추적 | PASS/FAIL | {상세} | -| 문서 구조 | PASS/FAIL | {상세} | -| 피드백 반영 | PASS/FAIL | {상세} | -| 초안/최종본 분리 | PASS/FAIL | {상세} | -| 문체 품질 | PASS/FAIL | {상세} | -| 검증 가능성 | PASS/FAIL | {상세} | - -If there are issues, include concrete file paths and suggested fixes. diff --git a/Writing/Gemini/.gemini/agents/doc-drafter.md b/Writing/Gemini/.gemini/agents/doc-drafter.md deleted file mode 100644 index 678a233..0000000 --- a/Writing/Gemini/.gemini/agents/doc-drafter.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: doc-drafter -description: Turns PRD requirements and ResearchNote evidence into reviewable Markdown drafts under drafts/. -kind: local -tools: - - read_file - - read_many_files - - grep_search - - glob - - write_file - - replace -model: inherit -temperature: 0.4 -max_turns: 30 -timeout_mins: 30 ---- - -# Document Drafter - -You are the drafting specialist for the Gemini CLI Markdown Document Harness. - -## Responsibilities - -- Read `GEMINI.md`, `docs/PRD.md`, `docs/ResearchNote.md`, `docs/ARCHITECTURE.md`, `docs/ADR.md`, and `docs/UI_GUIDE.md` before drafting. -- Create draft documents only under `drafts/`. -- Keep the document goal, audience, scope, and tone aligned with `docs/PRD.md`. -- Tie external claims to `docs/ResearchNote.md` sources. -- Preserve user feedback files and do not overwrite `final/` documents. -- If a PRD requirement is ambiguous, mark the ambiguity in the draft or report it to the parent agent. diff --git a/Writing/Gemini/.gemini/agents/doc-researcher.md b/Writing/Gemini/.gemini/agents/doc-researcher.md deleted file mode 100644 index 34c525a..0000000 --- a/Writing/Gemini/.gemini/agents/doc-researcher.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: doc-researcher -description: Researches PRD keywords, gathers trustworthy sources, and maintains docs/ResearchNote.md for the document harness. -kind: local -tools: - - read_file - - read_many_files - - grep_search - - glob - - google_web_search - - web_fetch - - write_file - - replace -model: inherit -temperature: 0.2 -max_turns: 30 -timeout_mins: 30 ---- - -# Document Researcher - -You are the research specialist for the Gemini CLI Markdown Document Harness. - -## Responsibilities - -- Read `GEMINI.md`, `docs/PRD.md`, `docs/ARCHITECTURE.md`, `docs/ADR.md`, and `docs/UI_GUIDE.md` before researching. -- Treat `docs/PRD.md` as the single source of document requirements. -- Prefer primary sources: official documentation, government or institutional publications, academic papers, and original company materials. -- Record search date, search terms, source URLs, source quality notes, core claims, conflicts, unresolved questions, and intended document usage in `docs/ResearchNote.md`. -- Mark uncertain claims as `확인 필요` instead of presenting them as facts. -- Do not write final prose in `final/`. Your primary output is `docs/ResearchNote.md` and concise research notes for the parent agent. diff --git a/Writing/Gemini/.gemini/agents/doc-reviewer.md b/Writing/Gemini/.gemini/agents/doc-reviewer.md deleted file mode 100644 index cb763c1..0000000 --- a/Writing/Gemini/.gemini/agents/doc-reviewer.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: doc-reviewer -description: Reviews Markdown documents for PRD alignment, evidence quality, structure, and feedback coverage. -kind: local -tools: - - read_file - - read_many_files - - grep_search - - glob -model: inherit -temperature: 0.1 -max_turns: 20 -timeout_mins: 20 ---- - -# Document Reviewer - -You are the review specialist for the Gemini CLI Markdown Document Harness. - -## Responsibilities - -- Review changed Markdown files against `GEMINI.md`, `docs/PRD.md`, `docs/ResearchNote.md`, `docs/DraftFeedback.md`, `docs/FinalFeedback.md`, and `docs/UI_GUIDE.md`. -- Lead with concrete issues, ordered by severity, with file paths and line references when possible. -- Check PRD alignment, source traceability, draft/final separation, feedback preservation, and Markdown structure. -- Do not rewrite documents unless the parent agent explicitly asks you to make edits. diff --git a/Writing/Gemini/.gemini/agents/evidence-checker.md b/Writing/Gemini/.gemini/agents/evidence-checker.md deleted file mode 100644 index b6e4846..0000000 --- a/Writing/Gemini/.gemini/agents/evidence-checker.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: evidence-checker -description: Checks whether factual claims in drafts and final documents are supported by docs/ResearchNote.md sources. -kind: local -tools: - - read_file - - read_many_files - - grep_search - - glob -model: inherit -temperature: 0.1 -max_turns: 20 -timeout_mins: 20 ---- - -# Evidence Checker - -You are the evidence checking specialist for the Gemini CLI Markdown Document Harness. - -## Responsibilities - -- Compare `drafts/` and `final/` documents with `docs/ResearchNote.md`. -- Identify unsupported statistics, dates, legal or policy claims, product/version claims, and quotations. -- Report missing, weak, stale, or conflicting evidence. -- Prefer concise claim-to-source mapping over broad style feedback. diff --git a/Writing/Gemini/.gemini/commands/harness/draft.toml b/Writing/Gemini/.gemini/commands/harness/draft.toml deleted file mode 100644 index 72f8cd4..0000000 --- a/Writing/Gemini/.gemini/commands/harness/draft.toml +++ /dev/null @@ -1,19 +0,0 @@ -description = "Create review drafts under drafts/ from PRD and ResearchNote." - -prompt = """ -Use the document-harness Skill. Create all PRD deliverables under drafts/. - -Drafts must stay inside PRD scope, answer the key questions, use the requested tone, and connect external factual claims to docs/ResearchNote.md. Mark weak or missing evidence instead of inventing support. - -Use @doc-drafter when a focused drafting subagent is useful. - -Additional user instructions: -{{args}} - -Required context: -@{GEMINI.md} -@{docs/PRD.md} -@{docs/ResearchNote.md} -@{docs/DraftFeedback.md} -@{docs/UI_GUIDE.md} -""" diff --git a/Writing/Gemini/.gemini/commands/harness/final.toml b/Writing/Gemini/.gemini/commands/harness/final.toml deleted file mode 100644 index 03037b0..0000000 --- a/Writing/Gemini/.gemini/commands/harness/final.toml +++ /dev/null @@ -1,18 +0,0 @@ -description = "Create final documents under final/ after draft feedback." - -prompt = """ -Use the document-harness Skill. Read docs/DraftFeedback.md and create final deliverables under final/. - -If docs/DraftFeedback.md contains no actionable feedback or approval, mark the relevant phase step blocked instead of creating final documents. Do not overwrite drafts/. Preserve feedback files. - -Additional user instructions: -{{args}} - -Required context: -@{GEMINI.md} -@{docs/PRD.md} -@{docs/ResearchNote.md} -@{docs/DraftFeedback.md} -@{docs/FinalFeedback.md} -@{docs/UI_GUIDE.md} -""" diff --git a/Writing/Gemini/.gemini/commands/harness/plan.toml b/Writing/Gemini/.gemini/commands/harness/plan.toml deleted file mode 100644 index 0afe39d..0000000 --- a/Writing/Gemini/.gemini/commands/harness/plan.toml +++ /dev/null @@ -1,17 +0,0 @@ -description = "Inspect docs/PRD.md and design or update a document harness phase." - -prompt = """ -Use the document-harness Skill. - -Read the project context and determine whether docs/PRD.md is complete enough to start document work. If it is materially incomplete, explain the missing fields and stop. If it is complete enough, create or update an appropriate phase under phases/ using the template in .agents/skills/document-harness/references/phase-templates.md. - -Additional user instructions: -{{args}} - -Required context: -@{GEMINI.md} -@{docs/PRD.md} -@{docs/ARCHITECTURE.md} -@{docs/ADR.md} -@{docs/UI_GUIDE.md} -""" diff --git a/Writing/Gemini/.gemini/commands/harness/research.toml b/Writing/Gemini/.gemini/commands/harness/research.toml deleted file mode 100644 index 3ad0da6..0000000 --- a/Writing/Gemini/.gemini/commands/harness/research.toml +++ /dev/null @@ -1,18 +0,0 @@ -description = "Research PRD keywords and update docs/ResearchNote.md." - -prompt = """ -Use the document-harness Skill. Research the topics required by docs/PRD.md and update docs/ResearchNote.md. - -Prefer official, academic, government, institutional, or other primary sources. Record search date, search terms, source URLs, source quality notes, core findings, conflicts, unresolved questions, and intended document usage. - -Use @doc-researcher when the task benefits from a focused research subagent. - -Additional user instructions: -{{args}} - -Required context: -@{GEMINI.md} -@{docs/PRD.md} -@{docs/ResearchNote.md} -@{docs/UI_GUIDE.md} -""" diff --git a/Writing/Gemini/.gemini/commands/harness/review.toml b/Writing/Gemini/.gemini/commands/harness/review.toml deleted file mode 100644 index 12f7292..0000000 --- a/Writing/Gemini/.gemini/commands/harness/review.toml +++ /dev/null @@ -1,18 +0,0 @@ -description = "Review harness documents for PRD alignment, evidence, structure, and feedback coverage." - -prompt = """ -Use the document-review Skill. Review the current Markdown document harness outputs. - -Lead with findings. Check PRD alignment, source traceability, heading structure, feedback preservation, draft/final separation, style quality, and validation readiness. Use @doc-reviewer or @evidence-checker if a focused subagent would improve the review. - -Review target or extra instructions: -{{args}} - -Required context: -@{GEMINI.md} -@{docs/PRD.md} -@{docs/ResearchNote.md} -@{docs/DraftFeedback.md} -@{docs/FinalFeedback.md} -@{docs/UI_GUIDE.md} -""" diff --git a/Writing/Gemini/.gemini/commands/harness/status.toml b/Writing/Gemini/.gemini/commands/harness/status.toml deleted file mode 100644 index 49ec585..0000000 --- a/Writing/Gemini/.gemini/commands/harness/status.toml +++ /dev/null @@ -1,18 +0,0 @@ -description = "Summarize the current harness state and next required action." - -prompt = """ -Summarize the current Gemini CLI Markdown Document Harness state and identify the next required action. - -Check PRD completeness, ResearchNote status, draft/final outputs, feedback files, and phase status files if present. Do not modify files unless explicitly requested. - -Additional user instructions: -{{args}} - -Required context: -@{GEMINI.md} -@{docs/PRD.md} -@{docs/ResearchNote.md} -@{docs/DraftFeedback.md} -@{docs/FinalFeedback.md} -@{docs/ARCHITECTURE.md} -""" diff --git a/Writing/Gemini/.gemini/hooks/pre_tool_guard.py b/Writing/Gemini/.gemini/hooks/pre_tool_guard.py deleted file mode 100644 index 467bd87..0000000 --- a/Writing/Gemini/.gemini/hooks/pre_tool_guard.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 -"""Gemini CLI BeforeTool hook for obviously destructive shell commands.""" - -import json -import re -import sys -from typing import Any - - -DANGEROUS_PATTERNS = [ - (r"\brm\s+-rf\b", "Recursive force deletion is blocked by the document harness."), - ( - r"\bRemove-Item\b(?=.*\b-Recurse\b|\s-r\b)(?=.*\b-Force\b|\s-f\b)", - "PowerShell recursive force deletion is blocked by the document harness.", - ), - (r"\bgit\s+reset\s+--hard\b", "Hard reset is blocked because it can discard user work."), - (r"\bgit\s+push\b.*\s--force(?:-with-lease)?\b", "Force push is blocked by the document harness."), - (r"\bDROP\s+TABLE\b", "Destructive database commands are blocked by the document harness."), -] - - -def iter_strings(value: Any): - if isinstance(value, str): - yield value - elif isinstance(value, dict): - for key, item in value.items(): - yield str(key) - yield from iter_strings(item) - elif isinstance(value, list): - for item in value: - yield from iter_strings(item) - - -def emit(payload: dict) -> None: - print(json.dumps(payload, ensure_ascii=False)) - - -def main() -> int: - raw = sys.stdin.read() - try: - data = json.loads(raw) if raw.strip() else {} - except json.JSONDecodeError: - data = {} - - haystack = raw - if data: - haystack += "\n" + "\n".join(iter_strings(data.get("tool_input", data))) - - for pattern, reason in DANGEROUS_PATTERNS: - if re.search(pattern, haystack, flags=re.IGNORECASE | re.DOTALL): - emit({"decision": "deny", "reason": reason, "suppressOutput": True}) - return 0 - - emit({}) - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Writing/Gemini/.gemini/hooks/validate_docs_after_agent.py b/Writing/Gemini/.gemini/hooks/validate_docs_after_agent.py deleted file mode 100644 index 4fd451c..0000000 --- a/Writing/Gemini/.gemini/hooks/validate_docs_after_agent.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 -"""Gemini CLI AfterAgent hook that requests a retry when validation fails.""" - -import json -import subprocess -import sys -from pathlib import Path - - -ROOT = Path(__file__).resolve().parents[2] - - -def emit(payload: dict) -> None: - print(json.dumps(payload, ensure_ascii=False)) - - -def main() -> int: - raw = sys.stdin.read() - try: - data = json.loads(raw) if raw.strip() else {} - except json.JSONDecodeError: - data = {} - - result = subprocess.run( - [sys.executable, "scripts/validate_docs.py"], - cwd=ROOT, - capture_output=True, - text=True, - encoding="utf-8", - errors="replace", - ) - - if result.returncode == 0: - emit({}) - return 0 - - details = "\n".join(part for part in [result.stdout.strip(), result.stderr.strip()] if part) - reason = ( - "Document harness validation failed. Continue the turn, fix the listed " - f"issues, and run `python scripts/validate_docs.py` again.\n\n{details}" - ) - - if data.get("stop_hook_active"): - emit({"continue": False, "stopReason": reason, "suppressOutput": True}) - else: - emit({"decision": "deny", "reason": reason, "suppressOutput": True}) - - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Writing/Gemini/.gemini/settings.json b/Writing/Gemini/.gemini/settings.json deleted file mode 100644 index 7bf824a..0000000 --- a/Writing/Gemini/.gemini/settings.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "context": { - "fileName": "GEMINI.md" - }, - "skills": { - "enabled": true, - "disabled": [] - }, - "hooksConfig": { - "enabled": true, - "disabled": [], - "notifications": true - }, - "hooks": { - "BeforeTool": [ - { - "matcher": "run_shell_command", - "hooks": [ - { - "name": "pre-tool-guard", - "type": "command", - "command": "python .gemini/hooks/pre_tool_guard.py", - "timeout": 5000, - "description": "Block obviously destructive shell commands." - } - ] - } - ], - "AfterAgent": [ - { - "matcher": "*", - "hooks": [ - { - "name": "validate-docs-after-agent", - "type": "command", - "command": "python .gemini/hooks/validate_docs_after_agent.py", - "timeout": 60000, - "description": "Run document harness validation after each agent response." - } - ] - } - ] - } -} diff --git a/Writing/Gemini/.gitignore b/Writing/Gemini/.gitignore deleted file mode 100644 index ad8994f..0000000 --- a/Writing/Gemini/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -node_modules/ -.next/ -out/ -next-env.d.ts -tsconfig.tsbuildinfo - -# Python/test cache -__pycache__/ -*.pyc -.pytest_cache/ - -# phase execution outputs -phases/**/phase*-output.json -phases/**/step*-output.json diff --git a/Writing/Gemini/GEMINI.md b/Writing/Gemini/GEMINI.md deleted file mode 100644 index e7b4c15..0000000 --- a/Writing/Gemini/GEMINI.md +++ /dev/null @@ -1,58 +0,0 @@ -# 프로젝트: Gemini CLI Markdown Document Harness Template - -## 목적 -이 템플릿은 Gemini CLI와 Harness Engineering 방식을 이용해 사용자의 목표에 맞는 Markdown 문서를 단계적으로 작성하기 위한 작업 환경이다. - -사용자는 `docs/PRD.md`에 문서의 목적, 개요, 대상 독자, 중요 키워드, 참고 자료를 입력한다. 이후 Gemini CLI는 이 정보를 기준으로 작성 규칙을 구체화하고, 조사 노트, 초안, 피드백 반영본, 최종 문서를 순차적으로 만든다. - -## Gemini CLI 구성 -- `GEMINI.md`: Gemini CLI가 읽는 프로젝트 규칙. -- `.agents/skills/document-harness/`: 문서 작성 Harness의 단계별 실행 절차. -- `.agents/skills/document-review/`: 문서 변경 사항 리뷰 절차. -- `.gemini/settings.json`: context, skills, hooks 설정. -- `.gemini/commands/`: Harness workflow용 custom slash commands. -- `.gemini/agents/`: 조사, 초안, 리뷰에 특화된 Gemini CLI subagents. -- `.gemini/hooks/`: 문서 검증과 위험 명령 방지를 위한 lifecycle hook scripts. - -## 기본 산출물 -- `docs/PRD.md`: 사용자가 작성하는 문서 요구사항의 원천. -- `docs/ResearchNote.md`: 웹 조사 결과, 출처, 쟁점, 문서 반영 메모. -- `drafts/`: 사용자 검토를 위한 초안 문서. -- `final/`: 피드백을 반영한 최종 문서. -- `docs/DraftFeedback.md`: 초안 검토 후 사용자가 남기는 피드백. -- `docs/FinalFeedback.md`: 최종 문서 검토 후 사용자가 남기는 피드백. -- `phases/`: Harness step 실행 계획과 상태 파일. - -## 문서 작성 규칙 -- CRITICAL: `docs/PRD.md`를 단일 요구사항 원천으로 삼는다. PRD에 없는 목표, 독자, 범위, 톤을 임의로 추가하지 마라. -- CRITICAL: 외부 사실, 통계, 최신 정보, 인용, 법/제도/가격/제품 정보는 `docs/ResearchNote.md`의 출처에 근거해야 한다. -- CRITICAL: 출처가 불명확한 주장을 최종 문서에 단정적으로 쓰지 마라. 필요한 경우 "확인 필요" 또는 "출처 필요"로 표시한다. -- CRITICAL: 초안 작성 후와 최종 문서 작성 후에는 사용자 피드백을 받아야 한다. 피드백이 필요한 step은 `blocked` 상태와 구체적인 `blocked_reason`을 기록한다. -- CRITICAL: 최종 문서는 초안과 분리해 `final/` 아래에 작성한다. 초안 파일을 최종본처럼 덮어쓰지 마라. -- 조사 노트에는 검색 일시, 검색어, 출처 URL, 핵심 요지, 문서 반영 여부를 남긴다. -- 문서 구조는 제목 계층을 유지한다. `#`는 문서 제목에만 사용하고, 본문 구조는 `##`, `###`를 사용한다. -- 사용자의 피드백은 삭제하지 말고 별도 피드백 문서에 보존한다. - -## Gemini CLI 작업 규칙 -- 반복 가능한 절차는 `.agents/skills/`의 Skill에 둔다. `GEMINI.md`에는 지속적으로 적용할 짧은 규칙만 유지한다. -- 문서 작성 Harness를 실행하거나 설계할 때는 `document-harness` Skill을 우선 사용한다. -- 문서 변경 사항을 검토할 때는 `document-review` Skill을 사용한다. -- 병렬 조사나 독립 리뷰가 필요한 경우 `.gemini/agents/`의 subagent를 명시적으로 선택한다. -- `scripts/execute.py`가 step 실행 후 git commit을 처리하므로, step을 수행하는 Gemini CLI 세션은 직접 commit하지 않는다. - -## 권장 워크플로우 -1. 사용자가 `docs/PRD.md`를 채운다. -2. Gemini CLI가 PRD를 읽고 `GEMINI.md`의 프로젝트별 작성 규칙을 구체화한다. -3. Gemini CLI 또는 subagents가 웹 검색을 수행하고 `docs/ResearchNote.md`를 작성한다. -4. Gemini CLI가 `drafts/`에 초안을 만들고 사용자 검토를 요청한다. -5. 사용자가 `docs/DraftFeedback.md`에 피드백을 남긴다. -6. Gemini CLI가 피드백을 반영해 `final/`에 최종 문서를 작성한다. -7. 사용자가 `docs/FinalFeedback.md`에 최종 피드백 또는 승인 여부를 남긴다. - -## 명령어 -```bash -python scripts/validate_docs.py -python scripts/execute.py -python scripts/execute.py --push -python -m pytest scripts/test_execute.py -``` diff --git a/Writing/Gemini/README.md b/Writing/Gemini/README.md deleted file mode 100644 index 4ce3637..0000000 --- a/Writing/Gemini/README.md +++ /dev/null @@ -1,252 +0,0 @@ -# Gemini CLI Markdown Document Harness Template - -Gemini CLI 환경에서 Harness Engineering 방식으로 Markdown 문서를 단계적으로 작성하기 위한 템플릿입니다. - -사용자는 `docs/PRD.md`에 만들고 싶은 문서의 목적, 대상 독자, 개요, 중요 키워드, 조사 요구사항을 작성합니다. 이후 Gemini CLI는 PRD를 기준으로 작성 규칙을 구체화하고, 웹 조사, 조사 노트, 초안, 사용자 피드백, 최종 문서를 순서대로 만들어 갑니다. - -## 핵심 아이디어 - -이 템플릿은 한 번에 최종 문서를 쓰는 방식이 아니라, 다음 흐름을 강제합니다. - -```text -PRD 작성 - -> 작성 규칙 구체화 - -> 웹 조사 및 ResearchNote 작성 - -> drafts/ 초안 작성 - -> 사용자 초안 피드백 - -> final/ 최종 문서 작성 - -> 사용자 최종 피드백 또는 승인 -``` - -목표는 빠른 초안 작성보다 사용자의 의도, 출처, 피드백, 최종 산출물을 분리해 관리하는 것입니다. - -## Gemini CLI 구성 - -```text -. -├── GEMINI.md # Gemini CLI가 읽는 프로젝트 기본 규칙 -├── .agents/ -│ └── skills/ -│ ├── document-harness/ # 단계적 문서 작성 Skill -│ └── document-review/ # 문서 리뷰 Skill -├── .gemini/ -│ ├── settings.json # context, skills, hooks 설정 -│ ├── commands/ # Harness custom slash commands -│ ├── hooks/ # hook 실행 스크립트 -│ └── agents/ # 조사, 초안, 리뷰, 근거 점검 subagents -├── docs/ -│ ├── PRD.md # 사용자가 채우는 문서 요구사항 -│ ├── ResearchNote.md # 조사 결과와 출처 장부 -│ ├── DraftFeedback.md # 초안 피드백 -│ ├── FinalFeedback.md # 최종 문서 피드백 -│ ├── ARCHITECTURE.md # 템플릿 구조 설명 -│ ├── ADR.md # 주요 설계 결정 -│ └── UI_GUIDE.md # Markdown 문서 스타일 가이드 -├── drafts/ # 초안 문서 -├── final/ # 최종 문서 -├── phases/ # 단계 실행 계획과 상태 파일 -└── scripts/ - ├── execute.py # Gemini CLI headless 기반 step 순차 실행기 - ├── validate_docs.py # 템플릿 구조 검증 - └── test_execute.py # 실행기 테스트 -``` - -## 빠른 시작 - -1. 템플릿을 git 저장소로 준비합니다. - -```bash -git init -``` - -`scripts/execute.py`는 브랜치 생성과 커밋을 수행하므로 자동 실행을 쓰려면 git 저장소가 필요합니다. - -2. `docs/PRD.md`를 채웁니다. - -최소한 아래 항목은 구체적으로 작성하는 것이 좋습니다. - -- 문서 목적 -- 대상 독자 -- 최종 산출물 -- 문서 개요 -- 중요 키워드 -- 핵심 질문 -- 포함할 범위와 제외할 범위 -- 톤과 스타일 -- 조사 요구사항 -- 승인 기준 - -3. Gemini CLI에서 Harness Skill 또는 custom command를 사용합니다. - -예시 프롬프트: - -```text -document-harness Skill을 사용해서 docs/PRD.md를 읽고 문서 작성 phase를 설계해 주세요. -``` - -또는 custom command를 사용할 수 있습니다. - -```text -/harness:plan -/harness:research -/harness:draft -/harness:review -``` - -4. 생성된 초안을 검토합니다. - -초안은 `drafts/` 아래에 생성됩니다. 검토 후 `docs/DraftFeedback.md`에 피드백을 작성합니다. - -5. 최종본을 검토합니다. - -최종 문서는 `final/` 아래에 생성됩니다. 검토 후 `docs/FinalFeedback.md`에 승인 또는 추가 수정 요청을 작성합니다. - -## 자동 실행 방식 - -Gemini CLI가 `phases/{task-name}/` 아래에 step 파일을 만든 뒤, 실행기는 각 step을 Gemini CLI headless mode로 순차 실행합니다. - -```bash -python scripts/execute.py -``` - -원격 저장소에 push까지 하려면 다음 명령을 사용합니다. - -```bash -python scripts/execute.py --push -``` - -실행기가 처리하는 일: - -- `feat-{task-name}` 브랜치 생성 또는 checkout -- `GEMINI.md`와 `docs/*.md`를 매 step 프롬프트에 주입 -- 완료된 step의 `summary`를 다음 step에 전달 -- 실패 시 최대 3회 재시도 -- step 상태를 `completed`, `blocked`, `error`로 관리 -- step 완료 후 문서 변경과 메타데이터를 커밋 - -## 피드백 게이트 - -사용자 검토가 필요한 단계에서는 step이 `blocked` 상태로 멈출 수 있습니다. - -초안 피드백: - -```text -docs/DraftFeedback.md -``` - -최종 피드백: - -```text -docs/FinalFeedback.md -``` - -피드백을 작성한 뒤 해당 step의 상태를 `pending`으로 되돌리고 다시 실행하면 다음 단계가 진행됩니다. - -## Gemini CLI Skills - -이 템플릿은 repo 공유 Skill을 사용합니다. Gemini CLI는 `.agents/skills/` 경로를 workspace skill alias로 인식합니다. - -`document-harness`: - -- PRD intake -- 작성 규칙 구체화 -- ResearchNote 작성 -- 초안 작성 -- 피드백 게이트 -- 최종 문서 작성 - -`document-review`: - -- PRD 정합성 검토 -- 출처 추적 검토 -- 초안/최종본 분리 확인 -- 피드백 반영 확인 -- 문체와 Markdown 구조 검토 - -## Gemini CLI Subagents - -`.gemini/agents/`에는 문서 작성에 특화된 subagent 역할이 정의되어 있습니다. - -| Agent | 역할 | -|-------|------| -| `doc-researcher` | PRD 키워드 조사, 출처 수집, `docs/ResearchNote.md` 작성 | -| `doc-drafter` | ResearchNote와 PRD를 바탕으로 `drafts/` 초안 작성 | -| `doc-reviewer` | PRD 정합성, 구조, 피드백 반영 여부 리뷰 | -| `evidence-checker` | 문서 주장과 ResearchNote 출처 연결 확인 | - -명시적으로 subagent를 호출하려면 Gemini CLI에서 다음처럼 요청할 수 있습니다. - -```text -@doc-researcher docs/PRD.md의 핵심 키워드를 조사하고 docs/ResearchNote.md를 정리해 주세요. -``` - -## Hooks - -`.gemini/settings.json`은 두 가지 기본 hook을 연결합니다. - -- `BeforeTool`: 위험한 shell 명령을 차단합니다. -- `AfterAgent`: 응답 종료 후 `python scripts/validate_docs.py`를 실행해 템플릿 구조를 검증합니다. - -검증 실패 시 `AfterAgent` hook은 Gemini CLI에 재시도 피드백을 전달합니다. - -## 검증 - -템플릿 구조를 확인합니다. - -```bash -python scripts/validate_docs.py -``` - -실행기 테스트를 실행합니다. - -```bash -python -m pytest scripts/test_execute.py -``` - -현재 기대 결과: - -```text -Document harness validation passed. -``` - -## 문서 작성 규칙 - -- `docs/PRD.md`를 단일 요구사항 원천으로 사용합니다. -- 외부 사실, 통계, 최신 정보, 법/제도/가격/제품 정보는 `docs/ResearchNote.md`의 출처에 근거해야 합니다. -- 출처가 불명확한 내용은 최종 문서에 단정적으로 쓰지 않습니다. -- 초안은 `drafts/`, 최종 문서는 `final/`에 분리합니다. -- 사용자 피드백 파일은 삭제하거나 덮어쓰지 않습니다. -- 문서 제목은 `#`, 주요 섹션은 `##`, 하위 섹션은 `###`를 사용합니다. - -## 추천 사용 프롬프트 - -PRD 검토: - -```text -document-harness Skill을 사용해 docs/PRD.md가 문서 작성을 시작하기에 충분한지 검토해 주세요. -``` - -조사 노트 작성: - -```text -/harness:research -``` - -초안 작성: - -```text -/harness:draft -``` - -문서 리뷰: - -```text -/harness:review -``` - -## 주의사항 - -- 자동 실행기는 git 저장소와 Gemini CLI 설치를 전제로 합니다. -- 최신 정보가 중요한 문서는 `docs/ResearchNote.md`에 조사 일시와 기준일을 남겨야 합니다. -- `GEMINI.md`에는 지속적으로 적용할 규칙만 두고, 긴 절차는 Skill에 둡니다. -- 이 템플릿은 Gemini CLI의 `GEMINI.md`, Skill, custom command, hook, subagent 구조를 기준으로 합니다. diff --git a/Writing/Gemini/docs/ADR.md b/Writing/Gemini/docs/ADR.md deleted file mode 100644 index 2874c04..0000000 --- a/Writing/Gemini/docs/ADR.md +++ /dev/null @@ -1,48 +0,0 @@ -# Architecture Decision Records - -## 철학 -이 템플릿의 핵심 가치는 사용자의 의도를 보존하면서도, 조사와 피드백을 통해 Markdown 문서 품질을 단계적으로 높이는 것이다. 빠르게 초안을 만들되, 근거 없는 최종본을 만들지 않는다. - ---- - -### ADR-001: Markdown-first 문서 산출 -**결정**: 모든 중간 산출물과 최종 산출물은 Markdown으로 작성한다. - -**이유**: Markdown은 버전 관리, 리뷰, 재사용, 자동 변환에 적합하고 AI Agent가 구조를 안정적으로 다루기 쉽다. - -**트레이드오프**: PDF, DOCX, 슬라이드 같은 최종 배포 형식은 별도 변환 단계가 필요하다. - -### ADR-002: PRD를 단일 요구사항 원천으로 사용 -**결정**: `docs/PRD.md`를 문서 목적, 독자, 범위, 톤, 키워드의 기준으로 삼는다. - -**이유**: 단계가 길어질수록 AI Agent가 임의로 목표를 확장할 위험이 있다. 단일 원천을 두면 초안과 최종본을 같은 기준으로 평가할 수 있다. - -**트레이드오프**: PRD가 빈약하면 후속 산출물도 흐려진다. 필요한 경우 PRD 보강을 먼저 요청해야 한다. - -### ADR-003: ResearchNote를 출처 장부로 사용 -**결정**: 웹 조사 결과와 출처 검증은 `docs/ResearchNote.md`에 먼저 정리한 뒤 문서에 반영한다. - -**이유**: 최종 문서에서 어떤 주장에 어떤 근거가 사용되었는지 추적할 수 있다. - -**트레이드오프**: 짧은 문서라도 조사 단계가 하나 추가된다. 대신 사실 오류와 출처 누락 위험을 줄인다. - -### ADR-004: 피드백 지점은 blocked 상태로 표현 -**결정**: 사용자 검토가 필요한 step은 `blocked` 상태와 구체적인 `blocked_reason`을 기록한다. - -**이유**: Harness 실행기가 사용자 개입이 필요한 지점을 명확히 멈출 수 있다. - -**트레이드오프**: 사용자가 피드백을 작성한 뒤 상태를 `pending`으로 되돌려 재실행해야 한다. - -### ADR-005: 초안과 최종본 분리 -**결정**: 초안은 `drafts/`, 최종본은 `final/`에 저장한다. - -**이유**: 사용자 검토 흔적과 최종 납품물을 명확히 분리할 수 있다. - -**트레이드오프**: 파일 수가 늘어난다. 대신 리뷰와 회귀 확인이 쉬워진다. - -### ADR-006: Gemini CLI의 context/Skill/Command/Hook/Subagent 구조 사용 -**결정**: 이 템플릿은 Gemini CLI의 `GEMINI.md`, `.agents/skills`, `.gemini/settings.json`, `.gemini/commands`, `.gemini/hooks`, `.gemini/agents` 구조를 기준으로 한다. - -**이유**: Gemini CLI는 프로젝트 지침을 `GEMINI.md`로 읽고, `SKILL.md` 기반 Agent Skills를 on-demand로 활성화하며, custom slash command와 lifecycle hook, local subagent를 `.gemini/` 아래에서 구성한다. 템플릿의 의도를 Gemini CLI 네이티브 구조에 맞추면 실행 맥락과 재사용성이 좋아진다. - -**트레이드오프**: 다른 CLI 전용 구성과 직접 호환되지 않는다. 대신 Gemini CLI의 headless execution, custom commands, hooks, subagents를 기준으로 문서 작성 자동화가 명확해진다. diff --git a/Writing/Gemini/docs/ARCHITECTURE.md b/Writing/Gemini/docs/ARCHITECTURE.md deleted file mode 100644 index 97804c1..0000000 --- a/Writing/Gemini/docs/ARCHITECTURE.md +++ /dev/null @@ -1,76 +0,0 @@ -# 문서 작성 하네스 아키텍처 - -## 디렉토리 구조 -```text -. -├── GEMINI.md # Gemini CLI가 읽는 프로젝트별 문서 작성 규칙 -├── .agents/ -│ └── skills/ -│ ├── document-harness/ # 단계적 문서 작성 Skill -│ └── document-review/ # 문서 리뷰 Skill -├── .gemini/ -│ ├── settings.json # context, skills, hooks 설정 -│ ├── commands/ # Harness custom slash commands -│ ├── hooks/ # hook 실행 스크립트 -│ └── agents/ # 조사/초안/리뷰 subagents -├── docs/ -│ ├── PRD.md # 사용자 요구사항 원천 -│ ├── ResearchNote.md # 조사 노트와 출처 장부 -│ ├── DraftFeedback.md # 초안 피드백 -│ ├── FinalFeedback.md # 최종 문서 피드백 -│ ├── ARCHITECTURE.md # 하네스 구조 -│ ├── ADR.md # 문서 작성 의사결정 -│ └── UI_GUIDE.md # Markdown 스타일 가이드 -├── drafts/ # 검토용 초안 산출물 -├── final/ # 피드백 반영 최종 산출물 -├── phases/ # Harness task/step 계획과 상태 -└── scripts/ - ├── execute.py # Gemini CLI headless 기반 step 순차 실행기 - ├── validate_docs.py # 문서 템플릿 기본 검증 - └── test_execute.py # execute.py 안전망 테스트 -``` - -## 데이터 흐름 -```text -사용자 입력 - -> docs/PRD.md - -> GEMINI.md 작성 규칙 구체화 - -> Gemini CLI/subagents 웹 검색 및 출처 검증 - -> docs/ResearchNote.md - -> drafts/ 초안 작성 - -> docs/DraftFeedback.md 사용자 피드백 - -> final/ 최종 문서 작성 - -> docs/FinalFeedback.md 최종 피드백 또는 승인 -``` - -## Step 설계 패턴 -권장 phase는 아래 순서를 따른다. - -1. `rule-synthesis`: `docs/PRD.md`를 읽고 `GEMINI.md`의 문서 작성 규칙을 프로젝트에 맞게 구체화한다. -2. `research-note`: 웹 검색과 사용자가 제공한 자료를 바탕으로 `docs/ResearchNote.md`를 작성한다. 필요하면 `doc-researcher` subagent를 사용한다. -3. `draft-documents`: `drafts/`에 사용자 검토용 초안을 작성한다. -4. `draft-feedback-gate`: `docs/DraftFeedback.md`가 비어 있으면 `blocked`로 멈추고 사용자 검토를 요청한다. -5. `final-documents`: 피드백을 반영해 `final/`에 최종 문서를 작성한다. -6. `final-feedback-gate`: `docs/FinalFeedback.md`에 승인 또는 추가 수정 요청이 없으면 `blocked`로 멈춘다. - -## Gemini CLI 구성 책임 -- `GEMINI.md`는 Gemini CLI가 매 작업에서 읽는 짧고 지속적인 규칙을 담는다. -- `.agents/skills/document-harness/`는 phase 생성, research, draft, feedback gate, final 작성 절차를 담는다. -- `.agents/skills/document-review/`는 변경된 Markdown 문서의 리뷰 체크리스트를 담는다. -- `.gemini/commands/`는 반복 작업을 호출하기 위한 project-local slash command를 담는다. -- `.gemini/agents/`는 조사, 초안 작성, 리뷰, 근거 점검 역할을 분리한다. -- `.gemini/settings.json`은 context, skills, hook 실행 설정을 담는다. -- `.gemini/hooks/`는 위험 명령 차단과 AfterAgent 시점 문서 검증을 연결한다. - -## 상태 관리 -- `pending`: 아직 실행되지 않은 step. -- `completed`: step 산출물이 생성되었고 검증이 끝난 상태. -- `blocked`: 사용자 피드백, 자료 제공, 승인 등 외부 입력이 필요한 상태. -- `error`: 자동 수정 3회 후에도 실패한 상태. - -## 파일 책임 -- `docs/PRD.md`는 사용자의 의도와 요구사항을 보존한다. Gemini CLI가 임의로 요구사항을 바꾸지 않는다. -- `docs/ResearchNote.md`는 사실 검증의 근거 장부다. 최종 문서의 외부 주장은 이 파일의 출처와 연결되어야 한다. -- `drafts/`는 논의용이다. 문장이 거칠 수 있지만 구조와 근거는 검토 가능해야 한다. -- `final/`은 납품용이다. 사용자 피드백, 출처, 스타일 기준을 반영해야 한다. -- `phases/`의 `index.json`과 `stepN.md`는 독립 실행 가능한 작업 지시서다. diff --git a/Writing/Gemini/docs/DraftFeedback.md b/Writing/Gemini/docs/DraftFeedback.md deleted file mode 100644 index 06c8544..0000000 --- a/Writing/Gemini/docs/DraftFeedback.md +++ /dev/null @@ -1,23 +0,0 @@ -# Draft Feedback - -초안 검토 후 사용자가 피드백을 남기는 파일이다. AI Agent는 이 파일을 읽고 `final/` 문서에 반영한다. - -## 검토 대상 -- `drafts/{파일명}` - -## 전체 판단 -- {예: 방향 승인 / 구조 수정 필요 / 추가 조사 필요 / 톤 변경 필요} - -## 수정 요청 -| 위치 | 요청 | 이유 | -|------|------|------| -| {섹션 또는 파일명} | {수정 요청} | {왜 필요한지} | - -## 추가로 포함할 내용 -- {추가 내용} - -## 제외하거나 줄일 내용 -- {삭제/축소할 내용} - -## 승인 여부 -{예: 초안 방향 승인 / 아직 승인하지 않음} diff --git a/Writing/Gemini/docs/FinalFeedback.md b/Writing/Gemini/docs/FinalFeedback.md deleted file mode 100644 index 95c388c..0000000 --- a/Writing/Gemini/docs/FinalFeedback.md +++ /dev/null @@ -1,17 +0,0 @@ -# Final Feedback - -최종 문서 검토 후 사용자가 승인 또는 추가 수정 요청을 남기는 파일이다. - -## 검토 대상 -- `final/{파일명}` - -## 승인 여부 -{예: 승인 / 수정 후 승인 / 승인하지 않음} - -## 최종 수정 요청 -| 위치 | 요청 | 우선순위 | -|------|------|----------| -| {섹션 또는 파일명} | {수정 요청} | {높음/중간/낮음} | - -## 비고 -- {추가 의견} diff --git a/Writing/Gemini/docs/PRD.md b/Writing/Gemini/docs/PRD.md deleted file mode 100644 index ad4fcae..0000000 --- a/Writing/Gemini/docs/PRD.md +++ /dev/null @@ -1,68 +0,0 @@ -# PRD: {문서 프로젝트명} - -이 파일은 Gemini CLI 문서 작성 Harness의 출발점이다. 사용자는 아래 항목을 가능한 한 구체적으로 채운다. Gemini CLI는 이 문서를 기준으로 작성 규칙, 조사 계획, 초안, 최종 문서를 만든다. - -## 문서 목적 -{이 문서가 해결하려는 문제, 설득하려는 주장, 설명하려는 주제, 또는 독자가 얻어야 할 결과를 한 문단으로 작성} - -## 대상 독자 -- 주요 독자: {예: 경영진, 개발자, 학생, 고객, 정책 담당자} -- 독자의 배경지식: {초급/중급/전문가, 알고 있다고 가정해도 되는 것} -- 독자가 문서를 읽은 뒤 해야 할 행동: {결정, 학습, 실행, 검토, 공유 등} - -## 최종 산출물 -| 문서명 | 목적 | 예상 분량 | 필수 포함 요소 | -|--------|------|-----------|----------------| -| {예: executive-summary.md} | {요약/설득/보고} | {예: 2쪽} | {핵심 메시지, 근거, 권고안} | -| {예: full-report.md} | {상세 설명} | {예: 10쪽} | {배경, 분석, 결론, 참고문헌} | - -## 문서 개요 -{원하는 목차, 포함해야 할 흐름, 반드시 다뤄야 할 섹션을 작성} - -## 중요 키워드 -- {키워드 1} -- {키워드 2} -- {키워드 3} - -## 핵심 질문 -- {문서가 반드시 답해야 하는 질문 1} -- {문서가 반드시 답해야 하는 질문 2} -- {문서가 반드시 답해야 하는 질문 3} - -## 범위 -### 포함할 것 -- {포함 범위 1} -- {포함 범위 2} - -### 제외할 것 -- {제외 범위 1} -- {제외 범위 2} - -## 톤과 스타일 -- 톤: {예: 전문적, 차분한 보고서, 친근한 설명문, 강한 설득형} -- 언어: {예: 한국어, 영어, 한영 병기} -- 문체: {예: 간결한 문장, 긴 분석형 문단, bullet 중심} -- 금지 표현: {예: 과장 광고 문구, "혁신적인", "압도적인" 같은 근거 없는 표현} - -## 참고 자료 -사용자가 이미 가진 자료나 반드시 참고해야 할 링크를 적는다. - -| 제목 | URL 또는 파일 경로 | 참고 이유 | -|------|-------------------|-----------| -| {자료명} | {URL/path} | {왜 중요한지} | - -## 조사 요구사항 -- 검색해야 할 주제: {예: 시장 규모, 기술 동향, 경쟁 사례, 법적 요건} -- 선호 출처: {예: 공식 문서, 학술 논문, 정부/기관 자료, 기업 보고서} -- 피해야 할 출처: {예: 출처 불명 블로그, 홍보성 기사} -- 최신성 기준: {예: 최근 2년, 2025년 이후, 최신 버전} - -## 품질 기준 -- {예: 모든 핵심 주장에 출처를 달 것} -- {예: 결론 전에 대안과 반론을 함께 검토할 것} -- {예: 초안은 빠르게, 최종본은 문장 품질과 일관성을 엄격히 볼 것} - -## 사용자 피드백 방식 -- 초안 피드백 위치: `docs/DraftFeedback.md` -- 최종 피드백 위치: `docs/FinalFeedback.md` -- 승인 기준: {예: 사용자가 명시적으로 "승인"이라고 남기면 완료} diff --git a/Writing/Gemini/docs/ResearchNote.md b/Writing/Gemini/docs/ResearchNote.md deleted file mode 100644 index d04849a..0000000 --- a/Writing/Gemini/docs/ResearchNote.md +++ /dev/null @@ -1,54 +0,0 @@ -# Research Note: {문서 프로젝트명} - -이 파일은 조사 내용과 출처를 보존하는 장부다. 최종 문서에 들어가는 외부 사실, 통계, 인용, 사례는 가능한 한 이 파일의 항목과 연결되어야 한다. - -## 조사 범위 -- 기준 PRD: `docs/PRD.md` -- 조사 주제: {조사할 주제} -- 제외 주제: {조사하지 않을 주제} -- 최신성 기준: {예: 최근 2년, 2025년 이후, 최신 공식 문서} - -## 조사 일시 -- 시작: {YYYY-MM-DD HH:mm, timezone} -- 종료: {YYYY-MM-DD HH:mm, timezone} -- 조사자: AI Agent - -## 검색어 -| 검색어 | 목적 | 결과 메모 | -|--------|------|-----------| -| {검색어} | {무엇을 확인하려 했는지} | {핵심 결과} | - -## 핵심 결론 -1. {조사에서 확인한 핵심 결론} -2. {조사에서 확인한 핵심 결론} -3. {조사에서 확인한 핵심 결론} - -## 출처 목록 -| ID | 제목 | URL | 게시일/확인일 | 신뢰도 | 관련 키워드 | -|----|------|-----|---------------|--------|-------------| -| S1 | {출처 제목} | {URL} | {날짜} | {공식/학술/언론/블로그 등} | {키워드} | - -## 출처별 메모 -### S1: {출처 제목} -- URL: {URL} -- 요지: {핵심 내용 요약} -- 문서에 쓸 수 있는 내용: {반영할 사실/사례/근거} -- 주의사항: {한계, 편향, 오래된 정보, 상충 자료} - -## 키워드별 정리 -### {키워드} -- 확인된 사실: {내용} -- 관련 출처: {S1, S2} -- 문서 반영 위치: {draft/final의 예상 섹션} - -## 쟁점과 상반된 주장 -| 쟁점 | 주장 A | 주장 B | 판단/처리 | -|------|--------|--------|-----------| -| {쟁점} | {내용과 출처} | {내용과 출처} | {문서에서 어떻게 다룰지} | - -## 확인 필요 -- {추가 확인이 필요한 사실} -- {사용자에게 물어봐야 할 내용} - -## 문서 반영 메모 -- {어떤 결론을 어떤 문서/섹션에 반영할지} diff --git a/Writing/Gemini/docs/UI_GUIDE.md b/Writing/Gemini/docs/UI_GUIDE.md deleted file mode 100644 index 34479a4..0000000 --- a/Writing/Gemini/docs/UI_GUIDE.md +++ /dev/null @@ -1,54 +0,0 @@ -# Markdown 문서 스타일 가이드 - -## 원칙 -1. 독자의 다음 행동이 분명해야 한다. 설명문이라면 이해, 보고서라면 판단, 가이드라면 실행이 가능해야 한다. -2. 근거와 의견을 섞지 않는다. 사실, 해석, 권고를 구분해 쓴다. -3. AI가 쓴 듯한 일반론보다 사용자의 목적과 키워드에 맞춘 구체성을 우선한다. - -## AI 문서 안티패턴 -| 금지 사항 | 이유 | -|-----------|------| -| "오늘날 빠르게 변화하는 시대에" 같은 상투적 도입 | 정보 밀도가 낮고 AI 생성문처럼 보인다 | -| 근거 없는 최상급 표현 | 신뢰를 떨어뜨린다 | -| 출처 없는 통계와 수치 | 검증할 수 없다 | -| 같은 의미의 문장을 반복해 분량 늘리기 | 독자의 시간을 낭비한다 | -| 목차와 본문 제목 불일치 | 리뷰와 유지보수가 어려워진다 | -| PRD에 없는 독자나 목표 추가 | 사용자 의도를 벗어난다 | -| ResearchNote에 없는 외부 주장 단정 | 출처 추적이 끊긴다 | -| GEMINI.md와 Skill 지침 불일치 | Gemini CLI 실행 맥락이 흔들린다 | - -## 구조 -- 문서 제목은 `#` 하나만 사용한다. -- 주요 섹션은 `##`, 하위 섹션은 `###`를 사용한다. -- 한 섹션에는 하나의 중심 메시지만 둔다. -- 긴 목록은 표로 바꿀 수 있는지 검토한다. -- 결론 문서라면 "요약 -> 근거 -> 판단/권고 -> 한계" 순서를 우선 고려한다. -- 설명 문서라면 "맥락 -> 핵심 개념 -> 절차/예시 -> 주의사항" 순서를 우선 고려한다. - -## 문체 -- 문장은 가능한 한 짧게 쓴다. -- 모호한 주어를 피한다. -- "중요하다", "효과적이다"처럼 평가를 쓸 때는 이유나 근거를 바로 붙인다. -- 불확실한 정보는 확률적 표현 또는 확인 필요 표시를 사용한다. -- 한국어 문서에서는 불필요한 영어 약어를 피하고, 처음 등장할 때 풀어쓴다. - -## 출처 표기 -- 외부 사실은 문장 끝이나 문단 끝에 출처 링크를 붙인다. -- 긴 직접 인용보다 요약과 해석을 우선한다. -- 같은 출처를 반복해서 사용할 때도 어떤 주장에 연결되는지 분명히 한다. -- 출처가 상충하면 `docs/ResearchNote.md`의 "쟁점/상반된 주장"에 기록한다. - -## 표와 목록 -- 비교, 분류, 의사결정 기준은 표를 우선 검토한다. -- 순서가 중요한 절차는 번호 목록을 사용한다. -- 단순 나열은 bullet을 사용한다. -- 표는 너무 넓어지면 섹션을 나누거나 요약 표와 상세 설명을 분리한다. - -## 최종 점검 -- PRD의 목적과 대상 독자에 맞는가? -- 모든 핵심 질문에 답했는가? -- 외부 주장에 출처가 있는가? -- 초안 피드백이 반영되었는가? -- 문서 제목, 섹션 제목, 파일명이 산출물 목적과 맞는가? -- 최종 문서는 `final/` 아래에 있는가? -- Gemini CLI Skill, subagent, command, hook 지침과 충돌하지 않는가? diff --git a/Writing/Gemini/drafts/.gitkeep b/Writing/Gemini/drafts/.gitkeep deleted file mode 100644 index 8b13789..0000000 --- a/Writing/Gemini/drafts/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Writing/Gemini/final/.gitkeep b/Writing/Gemini/final/.gitkeep deleted file mode 100644 index 8b13789..0000000 --- a/Writing/Gemini/final/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Writing/Gemini/scripts/execute.py b/Writing/Gemini/scripts/execute.py deleted file mode 100644 index 422c536..0000000 --- a/Writing/Gemini/scripts/execute.py +++ /dev/null @@ -1,427 +0,0 @@ -#!/usr/bin/env python3 -""" -Gemini Harness Step Executor — phase 내 step을 순차 실행하고 자가 교정한다. - -Usage: - python scripts/execute.py [--push] -""" - -import argparse -import contextlib -import json -import subprocess -import sys -import threading -import time -import types -from datetime import datetime, timezone, timedelta -from pathlib import Path -from typing import Optional - -ROOT = Path(__file__).resolve().parent.parent - - -@contextlib.contextmanager -def progress_indicator(label: str): - """터미널 진행 표시기. with 문으로 사용하며 .elapsed 로 경과 시간을 읽는다.""" - frames = "◐◓◑◒" - stop = threading.Event() - t0 = time.monotonic() - - def _animate(): - idx = 0 - while not stop.wait(0.12): - sec = int(time.monotonic() - t0) - sys.stderr.write(f"\r{frames[idx % len(frames)]} {label} [{sec}s]") - sys.stderr.flush() - idx += 1 - sys.stderr.write("\r" + " " * (len(label) + 20) + "\r") - sys.stderr.flush() - - th = threading.Thread(target=_animate, daemon=True) - th.start() - info = types.SimpleNamespace(elapsed=0.0) - try: - yield info - finally: - stop.set() - th.join() - info.elapsed = time.monotonic() - t0 - - -class StepExecutor: - """Phase 디렉토리 안의 step들을 Gemini CLI로 순차 실행하는 하네스.""" - - MAX_RETRIES = 3 - FEAT_MSG = "feat({phase}): step {num} — {name}" - CHORE_MSG = "chore({phase}): step {num} output" - TZ = timezone(timedelta(hours=9)) - - def __init__(self, phase_dir_name: str, *, auto_push: bool = False): - self._root = str(ROOT) - self._phases_dir = ROOT / "phases" - self._phase_dir = self._phases_dir / phase_dir_name - self._phase_dir_name = phase_dir_name - self._top_index_file = self._phases_dir / "index.json" - self._auto_push = auto_push - - if not self._phase_dir.is_dir(): - print(f"ERROR: {self._phase_dir} not found") - sys.exit(1) - - self._index_file = self._phase_dir / "index.json" - if not self._index_file.exists(): - print(f"ERROR: {self._index_file} not found") - sys.exit(1) - - idx = self._read_json(self._index_file) - self._project = idx.get("project", "project") - self._phase_name = idx.get("phase", phase_dir_name) - self._total = len(idx["steps"]) - - def run(self): - self._print_header() - self._check_blockers() - self._checkout_branch() - guardrails = self._load_guardrails() - self._ensure_created_at() - self._execute_all_steps(guardrails) - self._finalize() - - # --- timestamps --- - - def _stamp(self) -> str: - return datetime.now(self.TZ).strftime("%Y-%m-%dT%H:%M:%S%z") - - # --- JSON I/O --- - - @staticmethod - def _read_json(p: Path) -> dict: - return json.loads(p.read_text(encoding="utf-8")) - - @staticmethod - def _write_json(p: Path, data: dict): - p.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8") - - # --- git --- - - def _run_git(self, *args) -> subprocess.CompletedProcess: - cmd = ["git"] + list(args) - return subprocess.run(cmd, cwd=self._root, capture_output=True, text=True) - - def _checkout_branch(self): - branch = f"feat-{self._phase_name}" - - r = self._run_git("rev-parse", "--abbrev-ref", "HEAD") - if r.returncode != 0: - print(" ERROR: git을 사용할 수 없거나 git repo가 아닙니다.") - print(f" {r.stderr.strip()}") - sys.exit(1) - - if r.stdout.strip() == branch: - return - - r = self._run_git("rev-parse", "--verify", branch) - r = self._run_git("checkout", branch) if r.returncode == 0 else self._run_git("checkout", "-b", branch) - - if r.returncode != 0: - print(f" ERROR: 브랜치 '{branch}' checkout 실패.") - print(f" {r.stderr.strip()}") - print(" Hint: 변경사항을 stash하거나 commit한 후 다시 시도하세요.") - sys.exit(1) - - print(f" Branch: {branch}") - - def _commit_step(self, step_num: int, step_name: str): - output_rel = f"phases/{self._phase_dir_name}/step{step_num}-output.json" - index_rel = f"phases/{self._phase_dir_name}/index.json" - - self._run_git("add", "-A") - self._run_git("reset", "HEAD", "--", output_rel) - self._run_git("reset", "HEAD", "--", index_rel) - - if self._run_git("diff", "--cached", "--quiet").returncode != 0: - msg = self.FEAT_MSG.format(phase=self._phase_name, num=step_num, name=step_name) - r = self._run_git("commit", "-m", msg) - if r.returncode == 0: - print(f" Commit: {msg}") - else: - print(f" WARN: 코드 커밋 실패: {r.stderr.strip()}") - - self._run_git("add", "-A") - if self._run_git("diff", "--cached", "--quiet").returncode != 0: - msg = self.CHORE_MSG.format(phase=self._phase_name, num=step_num) - r = self._run_git("commit", "-m", msg) - if r.returncode != 0: - print(f" WARN: housekeeping 커밋 실패: {r.stderr.strip()}") - - # --- top-level index --- - - def _update_top_index(self, status: str): - if not self._top_index_file.exists(): - return - top = self._read_json(self._top_index_file) - ts = self._stamp() - for phase in top.get("phases", []): - if phase.get("dir") == self._phase_dir_name: - phase["status"] = status - ts_key = {"completed": "completed_at", "error": "failed_at", "blocked": "blocked_at"}.get(status) - if ts_key: - phase[ts_key] = ts - break - self._write_json(self._top_index_file, top) - - # --- guardrails & context --- - - def _load_guardrails(self) -> str: - sections = [] - gemini_md = ROOT / "GEMINI.md" - if gemini_md.exists(): - sections.append(f"## 프로젝트 규칙 (GEMINI.md)\n\n{gemini_md.read_text(encoding='utf-8')}") - docs_dir = ROOT / "docs" - if docs_dir.is_dir(): - for doc in sorted(docs_dir.glob("*.md")): - sections.append(f"## {doc.stem}\n\n{doc.read_text(encoding='utf-8')}") - return "\n\n---\n\n".join(sections) if sections else "" - - @staticmethod - def _build_step_context(index: dict) -> str: - lines = [ - f"- Step {s['step']} ({s['name']}): {s['summary']}" - for s in index["steps"] - if s["status"] == "completed" and s.get("summary") - ] - if not lines: - return "" - return "## 이전 Step 산출물\n\n" + "\n".join(lines) + "\n\n" - - def _build_preamble(self, guardrails: str, step_context: str, - prev_error: Optional[str] = None) -> str: - retry_section = "" - if prev_error: - retry_section = ( - "\n## 이전 시도 실패 — 아래 에러를 반드시 참고하여 수정하라\n\n" - f"{prev_error}\n\n---\n\n" - ) - return ( - f"당신은 {self._project} 프로젝트의 Gemini CLI 문서 작성 에이전트입니다. 아래 step을 수행하세요.\n\n" - f"{guardrails}\n\n---\n\n" - f"{step_context}{retry_section}" - "## 작업 규칙\n\n" - "1. 이전 step에서 작성된 문서와 메모를 확인하고 일관성을 유지하라.\n" - "2. 이 step에 명시된 작업만 수행하라. 추가 산출물이나 임의 요구사항을 만들지 마라.\n" - "3. 기존 문서 구조와 피드백 기록을 깨뜨리지 마라.\n" - "4. AC(Acceptance Criteria) 검증을 직접 실행하라.\n" - f"5. /phases/{self._phase_dir_name}/index.json의 해당 step status를 업데이트하라:\n" - " - AC 통과 -> \"completed\" + \"summary\" 필드에 이 step의 산출물을 한 줄로 요약\n" - f" - {self.MAX_RETRIES}회 수정 시도 후에도 실패 -> \"error\" + \"error_message\" 기록\n" - " - 사용자 개입이 필요한 경우 -> \"blocked\" + \"blocked_reason\" 기록 후 즉시 중단\n" - "6. 직접 git commit하지 마라. commit은 scripts/execute.py가 step 완료 후 수행한다.\n" - "7. 병렬 조사나 독립 리뷰가 필요하고 step에서 허용했다면 .gemini/agents의 subagent 역할을 활용하라.\n\n---\n\n" - ) - - # --- Gemini CLI invocation --- - - def _invoke_gemini(self, step: dict, preamble: str) -> dict: - step_num, step_name = step["step"], step["name"] - step_file = self._phase_dir / f"step{step_num}.md" - - if not step_file.exists(): - print(f" ERROR: {step_file} not found") - sys.exit(1) - - prompt = preamble + step_file.read_text(encoding="utf-8") - cmd = ["gemini", "--output-format", "json", "--approval-mode", "yolo"] - - try: - result = subprocess.run( - cmd, - cwd=self._root, - input=prompt, - capture_output=True, - text=True, - encoding="utf-8", - errors="replace", - timeout=1800, - ) - except FileNotFoundError: - print("\n ERROR: Gemini CLI를 찾을 수 없습니다. `gemini --version`이 실행되는지 확인하세요.") - sys.exit(1) - - if result.returncode != 0: - print(f"\n WARN: Gemini CLI가 비정상 종료됨 (code {result.returncode})") - if result.stderr: - print(f" stderr: {result.stderr[:500]}") - - output = { - "step": step_num, - "name": step_name, - "exitCode": result.returncode, - "stdout": result.stdout, - "stderr": result.stderr, - } - out_path = self._phase_dir / f"step{step_num}-output.json" - with open(out_path, "w", encoding="utf-8") as f: - json.dump(output, f, indent=2, ensure_ascii=False) - - return output - - # --- 헤더 & 검증 --- - - def _print_header(self): - print(f"\n{'='*60}") - print(" Gemini Harness Step Executor") - print(f" Phase: {self._phase_name} | Steps: {self._total}") - if self._auto_push: - print(" Auto-push: enabled") - print(f"{'='*60}") - - def _check_blockers(self): - index = self._read_json(self._index_file) - for s in reversed(index["steps"]): - if s["status"] == "error": - print(f"\n ✗ Step {s['step']} ({s['name']}) failed.") - print(f" Error: {s.get('error_message', 'unknown')}") - print(" Fix and reset status to 'pending' to retry.") - sys.exit(1) - if s["status"] == "blocked": - print(f"\n ⏸ Step {s['step']} ({s['name']}) blocked.") - print(f" Reason: {s.get('blocked_reason', 'unknown')}") - print(" Resolve and reset status to 'pending' to retry.") - sys.exit(2) - if s["status"] != "pending": - break - - def _ensure_created_at(self): - index = self._read_json(self._index_file) - if "created_at" not in index: - index["created_at"] = self._stamp() - self._write_json(self._index_file, index) - - # --- 실행 루프 --- - - def _execute_single_step(self, step: dict, guardrails: str) -> bool: - """단일 step 실행 (재시도 포함). 완료되면 True, 실패/차단이면 False.""" - step_num, step_name = step["step"], step["name"] - done = sum(1 for s in self._read_json(self._index_file)["steps"] if s["status"] == "completed") - prev_error = None - - for attempt in range(1, self.MAX_RETRIES + 1): - index = self._read_json(self._index_file) - step_context = self._build_step_context(index) - preamble = self._build_preamble(guardrails, step_context, prev_error) - - tag = f"Step {step_num}/{self._total - 1} ({done} done): {step_name}" - if attempt > 1: - tag += f" [retry {attempt}/{self.MAX_RETRIES}]" - - with progress_indicator(tag) as pi: - self._invoke_gemini(step, preamble) - elapsed = int(pi.elapsed) - - index = self._read_json(self._index_file) - status = next((s.get("status", "pending") for s in index["steps"] if s["step"] == step_num), "pending") - ts = self._stamp() - - if status == "completed": - for s in index["steps"]: - if s["step"] == step_num: - s["completed_at"] = ts - self._write_json(self._index_file, index) - self._commit_step(step_num, step_name) - print(f" ✓ Step {step_num}: {step_name} [{elapsed}s]") - return True - - if status == "blocked": - for s in index["steps"]: - if s["step"] == step_num: - s["blocked_at"] = ts - self._write_json(self._index_file, index) - reason = next((s.get("blocked_reason", "") for s in index["steps"] if s["step"] == step_num), "") - print(f" ⏸ Step {step_num}: {step_name} blocked [{elapsed}s]") - print(f" Reason: {reason}") - self._update_top_index("blocked") - sys.exit(2) - - err_msg = next( - (s.get("error_message", "Step did not update status") for s in index["steps"] if s["step"] == step_num), - "Step did not update status", - ) - - if attempt < self.MAX_RETRIES: - for s in index["steps"]: - if s["step"] == step_num: - s["status"] = "pending" - s.pop("error_message", None) - self._write_json(self._index_file, index) - prev_error = err_msg - print(f" ↻ Step {step_num}: retry {attempt}/{self.MAX_RETRIES} — {err_msg}") - else: - for s in index["steps"]: - if s["step"] == step_num: - s["status"] = "error" - s["error_message"] = f"[{self.MAX_RETRIES}회 시도 후 실패] {err_msg}" - s["failed_at"] = ts - self._write_json(self._index_file, index) - self._commit_step(step_num, step_name) - print(f" ✗ Step {step_num}: {step_name} failed after {self.MAX_RETRIES} attempts [{elapsed}s]") - print(f" Error: {err_msg}") - self._update_top_index("error") - sys.exit(1) - - return False - - def _execute_all_steps(self, guardrails: str): - while True: - index = self._read_json(self._index_file) - pending = next((s for s in index["steps"] if s["status"] == "pending"), None) - if pending is None: - print("\n All steps completed!") - return - - step_num = pending["step"] - for s in index["steps"]: - if s["step"] == step_num and "started_at" not in s: - s["started_at"] = self._stamp() - self._write_json(self._index_file, index) - break - - self._execute_single_step(pending, guardrails) - - def _finalize(self): - index = self._read_json(self._index_file) - index["completed_at"] = self._stamp() - self._write_json(self._index_file, index) - self._update_top_index("completed") - - self._run_git("add", "-A") - if self._run_git("diff", "--cached", "--quiet").returncode != 0: - msg = f"chore({self._phase_name}): mark phase completed" - r = self._run_git("commit", "-m", msg) - if r.returncode == 0: - print(f" ✓ {msg}") - - if self._auto_push: - branch = f"feat-{self._phase_name}" - r = self._run_git("push", "-u", "origin", branch) - if r.returncode != 0: - print(f"\n ERROR: git push 실패: {r.stderr.strip()}") - sys.exit(1) - print(f" ✓ Pushed to origin/{branch}") - - print(f"\n{'='*60}") - print(f" Phase '{self._phase_name}' completed!") - print(f"{'='*60}") - - -def main(): - parser = argparse.ArgumentParser(description="Gemini Harness Step Executor") - parser.add_argument("phase_dir", help="Phase directory name (e.g. 0-document)") - parser.add_argument("--push", action="store_true", help="Push branch after completion") - args = parser.parse_args() - - StepExecutor(args.phase_dir, auto_push=args.push).run() - - -if __name__ == "__main__": - main() diff --git a/Writing/Gemini/scripts/test_execute.py b/Writing/Gemini/scripts/test_execute.py deleted file mode 100644 index e8bc4be..0000000 --- a/Writing/Gemini/scripts/test_execute.py +++ /dev/null @@ -1,560 +0,0 @@ -""" -execute.py 리팩터링 안전망 테스트. -리팩터링 전후 동작이 동일한지 검증한다. -""" - -import json -import os -import subprocess -import sys -import textwrap -from datetime import datetime, timezone, timedelta -from pathlib import Path -from unittest.mock import patch, MagicMock - -import pytest - -sys.path.insert(0, str(Path(__file__).parent)) -import execute as ex - - -# --------------------------------------------------------------------------- -# Fixtures -# --------------------------------------------------------------------------- - -@pytest.fixture -def tmp_project(tmp_path): - """phases/, GEMINI.md, docs/ 를 갖춘 임시 프로젝트 구조.""" - phases_dir = tmp_path / "phases" - phases_dir.mkdir() - - gemini_md = tmp_path / "GEMINI.md" - gemini_md.write_text("# Rules\n- rule one\n- rule two", encoding="utf-8") - - docs_dir = tmp_path / "docs" - docs_dir.mkdir() - (docs_dir / "arch.md").write_text("# Architecture\nSome content", encoding="utf-8") - (docs_dir / "guide.md").write_text("# Guide\nAnother doc", encoding="utf-8") - - return tmp_path - - -@pytest.fixture -def phase_dir(tmp_project): - """step 3개를 가진 phase 디렉토리.""" - d = tmp_project / "phases" / "0-mvp" - d.mkdir() - - index = { - "project": "TestProject", - "phase": "mvp", - "steps": [ - {"step": 0, "name": "setup", "status": "completed", "summary": "프로젝트 초기화 완료"}, - {"step": 1, "name": "core", "status": "completed", "summary": "핵심 로직 구현"}, - {"step": 2, "name": "ui", "status": "pending"}, - ], - } - (d / "index.json").write_text(json.dumps(index, indent=2, ensure_ascii=False), encoding="utf-8") - (d / "step2.md").write_text("# Step 2: UI\n\nUI를 구현하세요.", encoding="utf-8") - - return d - - -@pytest.fixture -def top_index(tmp_project): - """phases/index.json (top-level).""" - top = { - "phases": [ - {"dir": "0-mvp", "status": "pending"}, - {"dir": "1-polish", "status": "pending"}, - ] - } - p = tmp_project / "phases" / "index.json" - p.write_text(json.dumps(top, indent=2), encoding="utf-8") - return p - - -@pytest.fixture -def executor(tmp_project, phase_dir): - """테스트용 StepExecutor 인스턴스. git 호출은 별도 mock 필요.""" - with patch.object(ex, "ROOT", tmp_project): - inst = ex.StepExecutor("0-mvp") - # 내부 경로를 tmp_project 기준으로 재설정 - inst._root = str(tmp_project) - inst._phases_dir = tmp_project / "phases" - inst._phase_dir = phase_dir - inst._phase_dir_name = "0-mvp" - inst._index_file = phase_dir / "index.json" - inst._top_index_file = tmp_project / "phases" / "index.json" - return inst - - -# --------------------------------------------------------------------------- -# _stamp (= 이전 now_iso) -# --------------------------------------------------------------------------- - -class TestStamp: - def test_returns_kst_timestamp(self, executor): - result = executor._stamp() - assert "+0900" in result - - def test_format_is_iso(self, executor): - result = executor._stamp() - dt = datetime.strptime(result, "%Y-%m-%dT%H:%M:%S%z") - assert dt.tzinfo is not None - - def test_is_current_time(self, executor): - before = datetime.now(ex.StepExecutor.TZ).replace(microsecond=0) - result = executor._stamp() - after = datetime.now(ex.StepExecutor.TZ).replace(microsecond=0) + timedelta(seconds=1) - parsed = datetime.strptime(result, "%Y-%m-%dT%H:%M:%S%z") - assert before <= parsed <= after - - -# --------------------------------------------------------------------------- -# _read_json / _write_json -# --------------------------------------------------------------------------- - -class TestJsonHelpers: - def test_roundtrip(self, tmp_path): - data = {"key": "값", "nested": [1, 2, 3]} - p = tmp_path / "test.json" - ex.StepExecutor._write_json(p, data) - loaded = ex.StepExecutor._read_json(p) - assert loaded == data - - def test_save_ensures_ascii_false(self, tmp_path): - p = tmp_path / "test.json" - ex.StepExecutor._write_json(p, {"한글": "테스트"}) - raw = p.read_text(encoding="utf-8") - assert "한글" in raw - assert "\\u" not in raw - - def test_save_indented(self, tmp_path): - p = tmp_path / "test.json" - ex.StepExecutor._write_json(p, {"a": 1}) - raw = p.read_text(encoding="utf-8") - assert "\n" in raw - - def test_load_nonexistent_raises(self, tmp_path): - with pytest.raises(FileNotFoundError): - ex.StepExecutor._read_json(tmp_path / "nope.json") - - -# --------------------------------------------------------------------------- -# _load_guardrails -# --------------------------------------------------------------------------- - -class TestLoadGuardrails: - def test_loads_gemini_md_and_docs(self, executor, tmp_project): - with patch.object(ex, "ROOT", tmp_project): - result = executor._load_guardrails() - assert "# Rules" in result - assert "rule one" in result - assert "# Architecture" in result - assert "# Guide" in result - - def test_sections_separated_by_divider(self, executor, tmp_project): - with patch.object(ex, "ROOT", tmp_project): - result = executor._load_guardrails() - assert "---" in result - - def test_docs_sorted_alphabetically(self, executor, tmp_project): - with patch.object(ex, "ROOT", tmp_project): - result = executor._load_guardrails() - arch_pos = result.index("arch") - guide_pos = result.index("guide") - assert arch_pos < guide_pos - - def test_no_gemini_md(self, executor, tmp_project): - (tmp_project / "GEMINI.md").unlink() - with patch.object(ex, "ROOT", tmp_project): - result = executor._load_guardrails() - assert "GEMINI.md" not in result - assert "Architecture" in result - - def test_no_docs_dir(self, executor, tmp_project): - import shutil - shutil.rmtree(tmp_project / "docs") - with patch.object(ex, "ROOT", tmp_project): - result = executor._load_guardrails() - assert "Rules" in result - assert "Architecture" not in result - - def test_empty_project(self, tmp_path): - with patch.object(ex, "ROOT", tmp_path): - # executor가 필요 없는 static-like 동작이므로 임시 인스턴스 - phases_dir = tmp_path / "phases" / "dummy" - phases_dir.mkdir(parents=True) - idx = {"project": "T", "phase": "t", "steps": []} - (phases_dir / "index.json").write_text(json.dumps(idx), encoding="utf-8") - inst = ex.StepExecutor.__new__(ex.StepExecutor) - result = inst._load_guardrails() - assert result == "" - - -# --------------------------------------------------------------------------- -# _build_step_context -# --------------------------------------------------------------------------- - -class TestBuildStepContext: - def test_includes_completed_with_summary(self, phase_dir): - index = json.loads((phase_dir / "index.json").read_text(encoding="utf-8")) - result = ex.StepExecutor._build_step_context(index) - assert "Step 0 (setup): 프로젝트 초기화 완료" in result - assert "Step 1 (core): 핵심 로직 구현" in result - - def test_excludes_pending(self, phase_dir): - index = json.loads((phase_dir / "index.json").read_text(encoding="utf-8")) - result = ex.StepExecutor._build_step_context(index) - assert "ui" not in result - - def test_excludes_completed_without_summary(self, phase_dir): - index = json.loads((phase_dir / "index.json").read_text(encoding="utf-8")) - del index["steps"][0]["summary"] - result = ex.StepExecutor._build_step_context(index) - assert "setup" not in result - assert "core" in result - - def test_empty_when_no_completed(self): - index = {"steps": [{"step": 0, "name": "a", "status": "pending"}]} - result = ex.StepExecutor._build_step_context(index) - assert result == "" - - def test_has_header(self, phase_dir): - index = json.loads((phase_dir / "index.json").read_text(encoding="utf-8")) - result = ex.StepExecutor._build_step_context(index) - assert result.startswith("## 이전 Step 산출물") - - -# --------------------------------------------------------------------------- -# _build_preamble -# --------------------------------------------------------------------------- - -class TestBuildPreamble: - def test_includes_project_name(self, executor): - result = executor._build_preamble("", "") - assert "TestProject" in result - - def test_includes_guardrails(self, executor): - result = executor._build_preamble("GUARD_CONTENT", "") - assert "GUARD_CONTENT" in result - - def test_includes_step_context(self, executor): - ctx = "## 이전 Step 산출물\n\n- Step 0: done" - result = executor._build_preamble("", ctx) - assert "이전 Step 산출물" in result - - def test_tells_agent_not_to_commit_directly(self, executor): - result = executor._build_preamble("", "") - assert "직접 git commit하지 마라" in result - - def test_includes_rules(self, executor): - result = executor._build_preamble("", "") - assert "작업 규칙" in result - assert "AC" in result - - def test_no_retry_section_by_default(self, executor): - result = executor._build_preamble("", "") - assert "이전 시도 실패" not in result - - def test_retry_section_with_prev_error(self, executor): - result = executor._build_preamble("", "", prev_error="타입 에러 발생") - assert "이전 시도 실패" in result - assert "타입 에러 발생" in result - - def test_includes_max_retries(self, executor): - result = executor._build_preamble("", "") - assert str(ex.StepExecutor.MAX_RETRIES) in result - - def test_includes_index_path(self, executor): - result = executor._build_preamble("", "") - assert "/phases/0-mvp/index.json" in result - - -# --------------------------------------------------------------------------- -# _update_top_index -# --------------------------------------------------------------------------- - -class TestUpdateTopIndex: - def test_completed(self, executor, top_index): - executor._top_index_file = top_index - executor._update_top_index("completed") - data = json.loads(top_index.read_text(encoding="utf-8")) - mvp = next(p for p in data["phases"] if p["dir"] == "0-mvp") - assert mvp["status"] == "completed" - assert "completed_at" in mvp - - def test_error(self, executor, top_index): - executor._top_index_file = top_index - executor._update_top_index("error") - data = json.loads(top_index.read_text(encoding="utf-8")) - mvp = next(p for p in data["phases"] if p["dir"] == "0-mvp") - assert mvp["status"] == "error" - assert "failed_at" in mvp - - def test_blocked(self, executor, top_index): - executor._top_index_file = top_index - executor._update_top_index("blocked") - data = json.loads(top_index.read_text(encoding="utf-8")) - mvp = next(p for p in data["phases"] if p["dir"] == "0-mvp") - assert mvp["status"] == "blocked" - assert "blocked_at" in mvp - - def test_other_phases_unchanged(self, executor, top_index): - executor._top_index_file = top_index - executor._update_top_index("completed") - data = json.loads(top_index.read_text(encoding="utf-8")) - polish = next(p for p in data["phases"] if p["dir"] == "1-polish") - assert polish["status"] == "pending" - - def test_nonexistent_dir_is_noop(self, executor, top_index): - executor._top_index_file = top_index - executor._phase_dir_name = "no-such-dir" - original = json.loads(top_index.read_text(encoding="utf-8")) - executor._update_top_index("completed") - after = json.loads(top_index.read_text(encoding="utf-8")) - for p_before, p_after in zip(original["phases"], after["phases"]): - assert p_before["status"] == p_after["status"] - - def test_no_top_index_file(self, executor, tmp_path): - executor._top_index_file = tmp_path / "nonexistent.json" - executor._update_top_index("completed") # should not raise - - -# --------------------------------------------------------------------------- -# _checkout_branch (mocked) -# --------------------------------------------------------------------------- - -class TestCheckoutBranch: - def _mock_git(self, executor, responses): - call_idx = {"i": 0} - def fake_git(*args): - idx = call_idx["i"] - call_idx["i"] += 1 - if idx < len(responses): - return responses[idx] - return MagicMock(returncode=0, stdout="", stderr="") - executor._run_git = fake_git - - def test_already_on_branch(self, executor): - self._mock_git(executor, [ - MagicMock(returncode=0, stdout="feat-mvp\n", stderr=""), - ]) - executor._checkout_branch() # should return without checkout - - def test_branch_exists_checkout(self, executor): - self._mock_git(executor, [ - MagicMock(returncode=0, stdout="main\n", stderr=""), - MagicMock(returncode=0, stdout="", stderr=""), - MagicMock(returncode=0, stdout="", stderr=""), - ]) - executor._checkout_branch() - - def test_branch_not_exists_create(self, executor): - self._mock_git(executor, [ - MagicMock(returncode=0, stdout="main\n", stderr=""), - MagicMock(returncode=1, stdout="", stderr="not found"), - MagicMock(returncode=0, stdout="", stderr=""), - ]) - executor._checkout_branch() - - def test_checkout_fails_exits(self, executor): - self._mock_git(executor, [ - MagicMock(returncode=0, stdout="main\n", stderr=""), - MagicMock(returncode=1, stdout="", stderr=""), - MagicMock(returncode=1, stdout="", stderr="dirty tree"), - ]) - with pytest.raises(SystemExit) as exc_info: - executor._checkout_branch() - assert exc_info.value.code == 1 - - def test_no_git_exits(self, executor): - self._mock_git(executor, [ - MagicMock(returncode=1, stdout="", stderr="not a git repo"), - ]) - with pytest.raises(SystemExit) as exc_info: - executor._checkout_branch() - assert exc_info.value.code == 1 - - -# --------------------------------------------------------------------------- -# _commit_step (mocked) -# --------------------------------------------------------------------------- - -class TestCommitStep: - def test_two_phase_commit(self, executor): - calls = [] - def fake_git(*args): - calls.append(args) - if args[:2] == ("diff", "--cached"): - return MagicMock(returncode=1) - return MagicMock(returncode=0, stdout="", stderr="") - executor._run_git = fake_git - - executor._commit_step(2, "ui") - - commit_calls = [c for c in calls if c[0] == "commit"] - assert len(commit_calls) == 2 - assert "feat(mvp):" in commit_calls[0][2] - assert "chore(mvp):" in commit_calls[1][2] - - def test_no_code_changes_skips_feat_commit(self, executor): - call_count = {"diff": 0} - calls = [] - def fake_git(*args): - calls.append(args) - if args[:2] == ("diff", "--cached"): - call_count["diff"] += 1 - if call_count["diff"] == 1: - return MagicMock(returncode=0) - return MagicMock(returncode=1) - return MagicMock(returncode=0, stdout="", stderr="") - executor._run_git = fake_git - - executor._commit_step(2, "ui") - - commit_msgs = [c[2] for c in calls if c[0] == "commit"] - assert len(commit_msgs) == 1 - assert "chore" in commit_msgs[0] - - -# --------------------------------------------------------------------------- -# _invoke_gemini (mocked) -# --------------------------------------------------------------------------- - -class TestInvokeGemini: - def test_invokes_gemini_with_correct_args(self, executor): - mock_result = MagicMock(returncode=0, stdout='{"result": "ok"}', stderr="") - step = {"step": 2, "name": "ui"} - preamble = "PREAMBLE\n" - - with patch("subprocess.run", return_value=mock_result) as mock_run: - output = executor._invoke_gemini(step, preamble) - - cmd = mock_run.call_args[0][0] - assert cmd[0] == "gemini" - assert "--output-format" in cmd - assert "json" in cmd - assert "--approval-mode" in cmd - assert "yolo" in cmd - assert "PREAMBLE" in mock_run.call_args[1]["input"] - assert "UI를 구현하세요" in mock_run.call_args[1]["input"] - - def test_saves_output_json(self, executor): - mock_result = MagicMock(returncode=0, stdout='{"ok": true}', stderr="") - step = {"step": 2, "name": "ui"} - - with patch("subprocess.run", return_value=mock_result): - executor._invoke_gemini(step, "preamble") - - output_file = executor._phase_dir / "step2-output.json" - assert output_file.exists() - data = json.loads(output_file.read_text(encoding="utf-8")) - assert data["step"] == 2 - assert data["name"] == "ui" - assert data["exitCode"] == 0 - - def test_nonexistent_step_file_exits(self, executor): - step = {"step": 99, "name": "nonexistent"} - with pytest.raises(SystemExit) as exc_info: - executor._invoke_gemini(step, "preamble") - assert exc_info.value.code == 1 - - def test_timeout_is_1800(self, executor): - mock_result = MagicMock(returncode=0, stdout="{}", stderr="") - step = {"step": 2, "name": "ui"} - - with patch("subprocess.run", return_value=mock_result) as mock_run: - executor._invoke_gemini(step, "preamble") - - assert mock_run.call_args[1]["timeout"] == 1800 - - -# --------------------------------------------------------------------------- -# progress_indicator (= 이전 Spinner) -# --------------------------------------------------------------------------- - -class TestProgressIndicator: - def test_context_manager(self): - import time - with ex.progress_indicator("test") as pi: - time.sleep(0.15) - assert pi.elapsed >= 0.1 - - def test_elapsed_increases(self): - import time - with ex.progress_indicator("test") as pi: - time.sleep(0.2) - assert pi.elapsed > 0 - - -# --------------------------------------------------------------------------- -# main() CLI 파싱 (mocked) -# --------------------------------------------------------------------------- - -class TestMainCli: - def test_no_args_exits(self): - with patch("sys.argv", ["execute.py"]): - with pytest.raises(SystemExit) as exc_info: - ex.main() - assert exc_info.value.code == 2 # argparse exits with 2 - - def test_invalid_phase_dir_exits(self): - with patch("sys.argv", ["execute.py", "nonexistent"]): - with patch.object(ex, "ROOT", Path("/tmp/fake_nonexistent")): - with pytest.raises(SystemExit) as exc_info: - ex.main() - assert exc_info.value.code == 1 - - def test_missing_index_exits(self, tmp_project): - (tmp_project / "phases" / "empty").mkdir() - with patch("sys.argv", ["execute.py", "empty"]): - with patch.object(ex, "ROOT", tmp_project): - with pytest.raises(SystemExit) as exc_info: - ex.main() - assert exc_info.value.code == 1 - - -# --------------------------------------------------------------------------- -# _check_blockers (= 이전 main() error/blocked 체크) -# --------------------------------------------------------------------------- - -class TestCheckBlockers: - def _make_executor_with_steps(self, tmp_project, steps): - d = tmp_project / "phases" / "test-phase" - d.mkdir(exist_ok=True) - index = {"project": "T", "phase": "test", "steps": steps} - (d / "index.json").write_text(json.dumps(index), encoding="utf-8") - - with patch.object(ex, "ROOT", tmp_project): - inst = ex.StepExecutor.__new__(ex.StepExecutor) - inst._root = str(tmp_project) - inst._phases_dir = tmp_project / "phases" - inst._phase_dir = d - inst._phase_dir_name = "test-phase" - inst._index_file = d / "index.json" - inst._top_index_file = tmp_project / "phases" / "index.json" - inst._phase_name = "test" - inst._total = len(steps) - return inst - - def test_error_step_exits_1(self, tmp_project): - steps = [ - {"step": 0, "name": "ok", "status": "completed"}, - {"step": 1, "name": "bad", "status": "error", "error_message": "fail"}, - ] - inst = self._make_executor_with_steps(tmp_project, steps) - with pytest.raises(SystemExit) as exc_info: - inst._check_blockers() - assert exc_info.value.code == 1 - - def test_blocked_step_exits_2(self, tmp_project): - steps = [ - {"step": 0, "name": "ok", "status": "completed"}, - {"step": 1, "name": "stuck", "status": "blocked", "blocked_reason": "API key"}, - ] - inst = self._make_executor_with_steps(tmp_project, steps) - with pytest.raises(SystemExit) as exc_info: - inst._check_blockers() - assert exc_info.value.code == 2 diff --git a/Writing/Gemini/scripts/validate_docs.py b/Writing/Gemini/scripts/validate_docs.py deleted file mode 100644 index 9a127a4..0000000 --- a/Writing/Gemini/scripts/validate_docs.py +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/env python3 -""" -Basic validation for the Gemini CLI Markdown document harness template. - -This check is intentionally lightweight: it verifies that the template files -exist and keep the sections that later Harness steps depend on. -""" - -import json -from pathlib import Path -import sys - -try: - import tomllib -except ModuleNotFoundError: # pragma: no cover - Python < 3.11 compatibility - tomllib = None - - -ROOT = Path(__file__).resolve().parent.parent - -REQUIRED_FILES = [ - "README.md", - "GEMINI.md", - "docs/PRD.md", - "docs/ResearchNote.md", - "docs/DraftFeedback.md", - "docs/FinalFeedback.md", - "docs/ARCHITECTURE.md", - "docs/ADR.md", - "docs/UI_GUIDE.md", - ".agents/skills/document-harness/SKILL.md", - ".agents/skills/document-harness/references/phase-templates.md", - ".agents/skills/document-review/SKILL.md", - ".gemini/settings.json", - ".gemini/hooks/pre_tool_guard.py", - ".gemini/hooks/validate_docs_after_agent.py", - ".gemini/agents/doc-researcher.md", - ".gemini/agents/doc-drafter.md", - ".gemini/agents/doc-reviewer.md", - ".gemini/agents/evidence-checker.md", - ".gemini/commands/harness/plan.toml", - ".gemini/commands/harness/research.toml", - ".gemini/commands/harness/draft.toml", - ".gemini/commands/harness/final.toml", - ".gemini/commands/harness/review.toml", - ".gemini/commands/harness/status.toml", -] - -REQUIRED_DIRS = [ - "docs", - "scripts", - ".agents", - ".agents/skills", - ".agents/skills/document-harness", - ".agents/skills/document-review", - ".gemini", - ".gemini/commands", - ".gemini/commands/harness", - ".gemini/hooks", - ".gemini/agents", -] - -REQUIRED_SECTIONS = { - "README.md": [ - "## 핵심 아이디어", - "## Gemini CLI 구성", - "## 빠른 시작", - "## 자동 실행 방식", - "## 피드백 게이트", - "## Gemini CLI Skills", - "## Gemini CLI Subagents", - "## Hooks", - "## 검증", - ], - "docs/PRD.md": [ - "## 문서 목적", - "## 대상 독자", - "## 최종 산출물", - "## 문서 개요", - "## 중요 키워드", - "## 핵심 질문", - "## 범위", - "## 톤과 스타일", - "## 조사 요구사항", - "## 사용자 피드백 방식", - ], - "docs/ResearchNote.md": [ - "## 조사 범위", - "## 조사 일시", - "## 검색어", - "## 핵심 결론", - "## 출처 목록", - "## 쟁점과 상반된 주장", - "## 확인 필요", - ], - "GEMINI.md": [ - "## 목적", - "## Gemini CLI 구성", - "## 기본 산출물", - "## 문서 작성 규칙", - "## Gemini CLI 작업 규칙", - "## 권장 워크플로우", - "## 명령어", - ], - "docs/ARCHITECTURE.md": [ - "## 디렉토리 구조", - "## 데이터 흐름", - "## Step 설계 패턴", - "## Gemini CLI 구성 책임", - "## 상태 관리", - "## 파일 책임", - ], - ".agents/skills/document-harness/SKILL.md": [ - "# Document Harness Skill", - "## Operating Rules", - "## Staged Workflow", - "## Validation", - ], - ".agents/skills/document-review/SKILL.md": [ - "# Document Review Skill", - "## Read First", - "## Review Checklist", - "## Output Format", - ], -} - -REQUIRED_JSON_FILES = [ - ".gemini/settings.json", -] - -REQUIRED_TOML_FILES = [ - ".gemini/commands/harness/plan.toml", - ".gemini/commands/harness/research.toml", - ".gemini/commands/harness/draft.toml", - ".gemini/commands/harness/final.toml", - ".gemini/commands/harness/review.toml", - ".gemini/commands/harness/status.toml", -] - -FORBIDDEN_FILES = [ - "AGENTS.md", - ".codex/config.toml", - ".codex/hooks.json", - ".codex/hooks/pre_tool_guard.py", - ".codex/hooks/stop_validate.py", - ".codex/agents/doc_researcher.toml", - ".codex/agents/doc_drafter.toml", - ".codex/agents/doc_reviewer.toml", - ".codex/agents/evidence_checker.toml", -] - - -def first_nonempty_line(path: Path) -> str: - for line in path.read_text(encoding="utf-8").splitlines(): - if line.strip(): - return line.strip() - return "" - - -def markdown_file_has_valid_start(path: Path) -> bool: - first = first_nonempty_line(path) - if first.startswith("# "): - return True - if first == "---" and path.name == "SKILL.md": - return True - if first == "---" and path.parent.name == "agents": - return True - return False - - -def main() -> int: - errors: list[str] = [] - - for rel in REQUIRED_DIRS: - path = ROOT / rel - if not path.is_dir(): - errors.append(f"missing directory: {rel}") - - for rel in REQUIRED_FILES: - path = ROOT / rel - if not path.is_file(): - errors.append(f"missing file: {rel}") - continue - - if path.suffix == ".md": - if not markdown_file_has_valid_start(path): - errors.append(f"markdown file must start with a level-1 heading, Skill frontmatter, or agent frontmatter: {rel}") - - for rel in FORBIDDEN_FILES: - path = ROOT / rel - if path.exists(): - errors.append(f"forbidden legacy file remains: {rel}") - - for rel, sections in REQUIRED_SECTIONS.items(): - path = ROOT / rel - if not path.is_file(): - continue - - text = path.read_text(encoding="utf-8") - for section in sections: - if section not in text: - errors.append(f"missing section in {rel}: {section}") - - for rel in REQUIRED_JSON_FILES: - path = ROOT / rel - if not path.is_file(): - continue - try: - json.loads(path.read_text(encoding="utf-8")) - except json.JSONDecodeError as exc: - errors.append(f"invalid JSON in {rel}: {exc}") - - if tomllib is not None: - for rel in REQUIRED_TOML_FILES: - path = ROOT / rel - if not path.is_file(): - continue - try: - tomllib.loads(path.read_text(encoding="utf-8")) - except tomllib.TOMLDecodeError as exc: - errors.append(f"invalid TOML in {rel}: {exc}") - - if errors: - print("Document harness validation failed:") - for error in errors: - print(f"- {error}") - return 1 - - print("Document harness validation passed.") - return 0 - - -if __name__ == "__main__": - sys.exit(main())