diff --git a/Coding/.agents/plugins/marketplace.json b/Coding/Codex/.agents/plugins/marketplace.json similarity index 100% rename from Coding/.agents/plugins/marketplace.json rename to Coding/Codex/.agents/plugins/marketplace.json diff --git a/Coding/.agents/skills/harness-review/SKILL.md b/Coding/Codex/.agents/skills/harness-review/SKILL.md similarity index 100% rename from Coding/.agents/skills/harness-review/SKILL.md rename to Coding/Codex/.agents/skills/harness-review/SKILL.md diff --git a/Coding/.agents/skills/harness-review/agents/openai.yaml b/Coding/Codex/.agents/skills/harness-review/agents/openai.yaml similarity index 100% rename from Coding/.agents/skills/harness-review/agents/openai.yaml rename to Coding/Codex/.agents/skills/harness-review/agents/openai.yaml diff --git a/Coding/.agents/skills/harness-workflow/SKILL.md b/Coding/Codex/.agents/skills/harness-workflow/SKILL.md similarity index 100% rename from Coding/.agents/skills/harness-workflow/SKILL.md rename to Coding/Codex/.agents/skills/harness-workflow/SKILL.md diff --git a/Coding/.agents/skills/harness-workflow/agents/openai.yaml b/Coding/Codex/.agents/skills/harness-workflow/agents/openai.yaml similarity index 100% rename from Coding/.agents/skills/harness-workflow/agents/openai.yaml rename to Coding/Codex/.agents/skills/harness-workflow/agents/openai.yaml diff --git a/Coding/.codex/agents/harness-reviewer.toml b/Coding/Codex/.codex/agents/harness-reviewer.toml similarity index 100% rename from Coding/.codex/agents/harness-reviewer.toml rename to Coding/Codex/.codex/agents/harness-reviewer.toml diff --git a/Coding/.codex/agents/phase-planner.toml b/Coding/Codex/.codex/agents/phase-planner.toml similarity index 100% rename from Coding/.codex/agents/phase-planner.toml rename to Coding/Codex/.codex/agents/phase-planner.toml diff --git a/Coding/.codex/config.toml b/Coding/Codex/.codex/config.toml similarity index 100% rename from Coding/.codex/config.toml rename to Coding/Codex/.codex/config.toml diff --git a/Coding/.codex/hooks.json b/Coding/Codex/.codex/hooks.json similarity index 100% rename from Coding/.codex/hooks.json rename to Coding/Codex/.codex/hooks.json diff --git a/Coding/.codex/hooks/__pycache__/pre_tool_use_policy.cpython-312.pyc b/Coding/Codex/.codex/hooks/__pycache__/pre_tool_use_policy.cpython-312.pyc similarity index 100% rename from Coding/.codex/hooks/__pycache__/pre_tool_use_policy.cpython-312.pyc rename to Coding/Codex/.codex/hooks/__pycache__/pre_tool_use_policy.cpython-312.pyc diff --git a/Coding/.codex/hooks/__pycache__/stop_continue.cpython-312.pyc b/Coding/Codex/.codex/hooks/__pycache__/stop_continue.cpython-312.pyc similarity index 100% rename from Coding/.codex/hooks/__pycache__/stop_continue.cpython-312.pyc rename to Coding/Codex/.codex/hooks/__pycache__/stop_continue.cpython-312.pyc diff --git a/Coding/.codex/hooks/pre_tool_use_policy.py b/Coding/Codex/.codex/hooks/pre_tool_use_policy.py similarity index 100% rename from Coding/.codex/hooks/pre_tool_use_policy.py rename to Coding/Codex/.codex/hooks/pre_tool_use_policy.py diff --git a/Coding/.codex/hooks/stop_continue.py b/Coding/Codex/.codex/hooks/stop_continue.py similarity index 100% rename from Coding/.codex/hooks/stop_continue.py rename to Coding/Codex/.codex/hooks/stop_continue.py diff --git a/Coding/AGENTS.md b/Coding/Codex/AGENTS.md similarity index 100% rename from Coding/AGENTS.md rename to Coding/Codex/AGENTS.md diff --git a/Coding/README.md b/Coding/Codex/README.md similarity index 100% rename from Coding/README.md rename to Coding/Codex/README.md diff --git a/Coding/docs/ADR.md b/Coding/Codex/docs/ADR.md similarity index 100% rename from Coding/docs/ADR.md rename to Coding/Codex/docs/ADR.md diff --git a/Coding/docs/ARCHITECTURE.md b/Coding/Codex/docs/ARCHITECTURE.md similarity index 100% rename from Coding/docs/ARCHITECTURE.md rename to Coding/Codex/docs/ARCHITECTURE.md diff --git a/Coding/docs/PRD.md b/Coding/Codex/docs/PRD.md similarity index 100% rename from Coding/docs/PRD.md rename to Coding/Codex/docs/PRD.md diff --git a/Coding/docs/UI_GUIDE.md b/Coding/Codex/docs/UI_GUIDE.md similarity index 100% rename from Coding/docs/UI_GUIDE.md rename to Coding/Codex/docs/UI_GUIDE.md diff --git a/Coding/plugins/harness-engineering/.codex-plugin/plugin.json b/Coding/Codex/plugins/harness-engineering/.codex-plugin/plugin.json similarity index 100% rename from Coding/plugins/harness-engineering/.codex-plugin/plugin.json rename to Coding/Codex/plugins/harness-engineering/.codex-plugin/plugin.json diff --git a/Coding/plugins/harness-engineering/agents/openai.yaml b/Coding/Codex/plugins/harness-engineering/agents/openai.yaml similarity index 100% rename from Coding/plugins/harness-engineering/agents/openai.yaml rename to Coding/Codex/plugins/harness-engineering/agents/openai.yaml diff --git a/Coding/plugins/harness-engineering/commands/harness.md b/Coding/Codex/plugins/harness-engineering/commands/harness.md similarity index 100% rename from Coding/plugins/harness-engineering/commands/harness.md rename to Coding/Codex/plugins/harness-engineering/commands/harness.md diff --git a/Coding/plugins/harness-engineering/commands/review.md b/Coding/Codex/plugins/harness-engineering/commands/review.md similarity index 100% rename from Coding/plugins/harness-engineering/commands/review.md rename to Coding/Codex/plugins/harness-engineering/commands/review.md diff --git a/Coding/scripts/__pycache__/execute.cpython-312.pyc b/Coding/Codex/scripts/__pycache__/execute.cpython-312.pyc similarity index 100% rename from Coding/scripts/__pycache__/execute.cpython-312.pyc rename to Coding/Codex/scripts/__pycache__/execute.cpython-312.pyc diff --git a/Coding/scripts/__pycache__/validate_workspace.cpython-312.pyc b/Coding/Codex/scripts/__pycache__/validate_workspace.cpython-312.pyc similarity index 100% rename from Coding/scripts/__pycache__/validate_workspace.cpython-312.pyc rename to Coding/Codex/scripts/__pycache__/validate_workspace.cpython-312.pyc diff --git a/Coding/scripts/execute.py b/Coding/Codex/scripts/execute.py similarity index 100% rename from Coding/scripts/execute.py rename to Coding/Codex/scripts/execute.py diff --git a/Coding/scripts/test_execute.py b/Coding/Codex/scripts/test_execute.py similarity index 100% rename from Coding/scripts/test_execute.py rename to Coding/Codex/scripts/test_execute.py diff --git a/Coding/scripts/validate_workspace.py b/Coding/Codex/scripts/validate_workspace.py similarity index 100% rename from Coding/scripts/validate_workspace.py rename to Coding/Codex/scripts/validate_workspace.py diff --git a/Document/.agents/plugins/marketplace.json b/Coding/Gemini/.agents/plugins/marketplace.json similarity index 88% rename from Document/.agents/plugins/marketplace.json rename to Coding/Gemini/.agents/plugins/marketplace.json index bdbb792..056725e 100644 --- a/Document/.agents/plugins/marketplace.json +++ b/Coding/Gemini/.agents/plugins/marketplace.json @@ -1,7 +1,7 @@ { "name": "local-harness-engineering", "interface": { - "displayName": "Local Report Harness" + "displayName": "Local Harness Engineering" }, "plugins": [ { diff --git a/Coding/Gemini/.agents/skills/harness-review/SKILL.md b/Coding/Gemini/.agents/skills/harness-review/SKILL.md new file mode 100644 index 0000000..c0fcf8d --- /dev/null +++ b/Coding/Gemini/.agents/skills/harness-review/SKILL.md @@ -0,0 +1,57 @@ +--- +name: harness-review +description: Review a Harness Engineering repository against its persistent rules and design docs. Use when Codex is asked to review local changes, generated phase files, or implementation output against `AGENTS.md`, `docs/ARCHITECTURE.md`, `docs/ADR.md`, `docs/UI_GUIDE.md`, testing expectations, and Harness step acceptance criteria. +--- + +# Harness Review + +Use this skill when the user wants a repository-grounded review instead of generic commentary. + +## Review input set + +Read these first: + +- `/AGENTS.md` +- `/docs/ARCHITECTURE.md` +- `/docs/ADR.md` +- `/docs/UI_GUIDE.md` +- the changed files or generated `phases/` files under review + +If the user explicitly asks for delegated review, prefer the repo custom agent `harness_reviewer` or built-in read-only explorers. + +## Checklist + +Evaluate the patch against these questions: + +1. Does it follow the architecture described in `docs/ARCHITECTURE.md`? +2. Does it stay within the technology choices documented in `docs/ADR.md`? +3. Are new or changed behaviors covered by tests or other explicit validation? +4. Does it violate any CRITICAL rule in `AGENTS.md`? +5. Do generated `phases/` files remain self-contained, executable, and internally consistent? +6. If the user expects verification, does `python scripts/validate_workspace.py` succeed or is the failure explained? + +## Output rules + +- Lead with findings, ordered by severity. +- Include file references for each finding. +- Explain the concrete risk or regression, not just the rule name. +- If there are no findings, say so explicitly and mention residual risks or missing evidence. +- Keep summaries brief after the findings. + +## Preferred review table + +When the user asks for a checklist-style review, use this table: + +| Item | Result | Notes | +|------|------|------| +| Architecture compliance | PASS/FAIL | {details} | +| Tech stack compliance | PASS/FAIL | {details} | +| Test coverage | PASS/FAIL | {details} | +| CRITICAL rules | PASS/FAIL | {details} | +| Build and validation | PASS/FAIL | {details} | + +## What not to do + +- Do not approve changes just because they compile. +- Do not focus on style-only issues when correctness, architecture drift, or missing validation exists. +- Do not assume a passing hook means the implementation is acceptable; review the actual diff and docs. diff --git a/Coding/Gemini/.agents/skills/harness-review/agents/openai.yaml b/Coding/Gemini/.agents/skills/harness-review/agents/openai.yaml new file mode 100644 index 0000000..555439e --- /dev/null +++ b/Coding/Gemini/.agents/skills/harness-review/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Harness Review" + short_description: "Review changes against Harness project rules" + default_prompt: "Use Harness review to check architecture, tests, and rules." diff --git a/Coding/Gemini/.agents/skills/harness-workflow/SKILL.md b/Coding/Gemini/.agents/skills/harness-workflow/SKILL.md new file mode 100644 index 0000000..6d6f4c2 --- /dev/null +++ b/Coding/Gemini/.agents/skills/harness-workflow/SKILL.md @@ -0,0 +1,145 @@ +--- +name: harness-workflow +description: Plan and run the Harness Engineering workflow for this repository. Use when Codex needs to read `AGENTS.md` and `docs/*.md`, discuss implementation scope, draft phase plans, or create/update `phases/index.json`, `phases/{phase}/index.json`, and `phases/{phase}/stepN.md` files for staged execution. +--- + +# Harness Workflow + +Use this skill when the user is working in the Harness template and wants structured planning or phase-file generation. + +## Workflow + +### 1. Explore first + +Read these files before proposing steps: + +- `/AGENTS.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` +- `/docs/ADR.md` +- `/docs/UI_GUIDE.md` + +If the user explicitly asks for parallel exploration, use built-in Codex subagents such as `explorer`, or the repo-scoped custom agent `phase_planner`. + +### 2. Discuss before locking the plan + +If scope, sequencing, or architecture choices are still ambiguous, surface the decision points before creating `phases/` files. + +### 3. Design steps with strict boundaries + +When drafting a phase plan: + +1. Keep scope minimal. One step should usually touch one layer or one module. +2. Make each step self-contained. Every `stepN.md` must work in an isolated Codex session. +3. List prerequisite files explicitly. Never rely on "as discussed above". +4. Specify interfaces or invariants, not line-by-line implementations. +5. Use executable acceptance commands, not vague success criteria. +6. Write concrete warnings in "do not do X because Y" form. +7. Use kebab-case step names. + +## Files to generate + +### `phases/index.json` + +Top-level phase registry. Append to `phases[]` when the file already exists. + +```json +{ + "phases": [ + { + "dir": "0-mvp", + "status": "pending" + } + ] +} +``` + +- `dir`: phase directory name. +- `status`: `pending`, `completed`, `error`, or `blocked`. +- Timestamp fields are written by `scripts/execute.py`; do not seed them during planning. + +### `phases/{phase}/index.json` + +```json +{ + "project": "", + "phase": "", + "steps": [ + { "step": 0, "name": "project-setup", "status": "pending" }, + { "step": 1, "name": "core-types", "status": "pending" }, + { "step": 2, "name": "api-layer", "status": "pending" } + ] +} +``` + +- `project`: from `AGENTS.md`. +- `phase`: directory name. +- `steps[].step`: zero-based integer. +- `steps[].name`: kebab-case slug. +- `steps[].status`: initialize to `pending`. + +### `phases/{phase}/stepN.md` + +Each step file should contain: + +1. A title. +2. A "read these files first" section. +3. A concrete task section. +4. Executable acceptance criteria. +5. Verification instructions. +6. Explicit prohibitions. + +Recommended structure: + +```markdown +# Step {N}: {name} + +## Read First +- /AGENTS.md +- /docs/ARCHITECTURE.md +- /docs/ADR.md +- {files from previous steps} + +## Task +{specific instructions} + +## Acceptance Criteria +```bash +python scripts/validate_workspace.py +``` + +## Verification +1. Run the acceptance commands. +2. Check AGENTS and docs for rule drift. +3. Update the matching step in phases/{phase}/index.json: + - completed + summary + - error + error_message + - blocked + blocked_reason + +## Do Not +- {concrete prohibition} +``` +``` + +## Execution + +Run the generated phase with: + +```bash +python scripts/execute.py +python scripts/execute.py --push +``` + +`scripts/execute.py` handles: + +- `feat-{phase}` branch checkout/creation +- guardrail injection from `AGENTS.md` and `docs/*.md` +- accumulation of completed-step summaries into later prompts +- up to 3 retries with prior error feedback +- two-phase commit of code changes and metadata updates +- timestamps such as `created_at`, `started_at`, `completed_at`, `failed_at`, and `blocked_at` + +## Recovery rules + +- If a step is `error`, reset its status to `pending`, remove `error_message`, then rerun. +- If a step is `blocked`, resolve the blocker, reset to `pending`, remove `blocked_reason`, then rerun. diff --git a/Coding/Gemini/.agents/skills/harness-workflow/agents/openai.yaml b/Coding/Gemini/.agents/skills/harness-workflow/agents/openai.yaml new file mode 100644 index 0000000..890daa1 --- /dev/null +++ b/Coding/Gemini/.agents/skills/harness-workflow/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Harness Workflow" + short_description: "Guide Codex through Harness phase planning" + default_prompt: "Use the Harness workflow to plan phases and step files." diff --git a/Coding/Gemini/.codex/agents/harness-reviewer.toml b/Coding/Gemini/.codex/agents/harness-reviewer.toml new file mode 100644 index 0000000..95263fd --- /dev/null +++ b/Coding/Gemini/.codex/agents/harness-reviewer.toml @@ -0,0 +1,11 @@ +name = "harness_reviewer" +description = "Read-only reviewer for Harness projects, focused on architecture drift, critical rule violations, and missing validation." +model = "gpt-5.4" +model_reasoning_effort = "high" +sandbox_mode = "read-only" +developer_instructions = """ +Review changes like a repository owner. +Prioritize correctness, architecture compliance, behavior regressions, and missing tests over style. +Always compare the patch against AGENTS.md, docs/ARCHITECTURE.md, docs/ADR.md, and the requested acceptance criteria. +Lead with concrete findings and file references. If no material issues are found, say so explicitly and mention residual risks. +""" diff --git a/Coding/Gemini/.codex/agents/phase-planner.toml b/Coding/Gemini/.codex/agents/phase-planner.toml new file mode 100644 index 0000000..35d5389 --- /dev/null +++ b/Coding/Gemini/.codex/agents/phase-planner.toml @@ -0,0 +1,12 @@ +name = "phase_planner" +description = "Read-heavy Harness planner that decomposes docs into minimal, self-contained phase and step files." +model = "gpt-5.4" +model_reasoning_effort = "high" +sandbox_mode = "read-only" +developer_instructions = """ +Plan before implementing. +Read AGENTS.md and the docs directory, identify the smallest coherent phase boundaries, and draft self-contained steps. +Keep each step scoped to one layer or one module when possible. +Do not make code changes unless the parent agent explicitly asks you to write files. +Return concrete file paths, acceptance commands, and blocking assumptions. +""" diff --git a/Document/.codex/config.toml b/Coding/Gemini/.codex/config.toml similarity index 69% rename from Document/.codex/config.toml rename to Coding/Gemini/.codex/config.toml index c6967e0..39ca33a 100644 --- a/Document/.codex/config.toml +++ b/Coding/Gemini/.codex/config.toml @@ -1,4 +1,4 @@ -# Project-scoped Codex defaults for the Report Harness template. +# Project-scoped Codex defaults for the Harness template. # As of 2026-04-15, hooks are experimental and disabled on native Windows. [features] diff --git a/Document/.codex/hooks.json b/Coding/Gemini/.codex/hooks.json similarity index 100% rename from Document/.codex/hooks.json rename to Coding/Gemini/.codex/hooks.json diff --git a/Document/.codex/hooks/__pycache__/pre_tool_use_policy.cpython-312.pyc b/Coding/Gemini/.codex/hooks/__pycache__/pre_tool_use_policy.cpython-312.pyc similarity index 100% rename from Document/.codex/hooks/__pycache__/pre_tool_use_policy.cpython-312.pyc rename to Coding/Gemini/.codex/hooks/__pycache__/pre_tool_use_policy.cpython-312.pyc diff --git a/Document/.codex/hooks/__pycache__/stop_continue.cpython-312.pyc b/Coding/Gemini/.codex/hooks/__pycache__/stop_continue.cpython-312.pyc similarity index 100% rename from Document/.codex/hooks/__pycache__/stop_continue.cpython-312.pyc rename to Coding/Gemini/.codex/hooks/__pycache__/stop_continue.cpython-312.pyc diff --git a/Document/.codex/hooks/pre_tool_use_policy.py b/Coding/Gemini/.codex/hooks/pre_tool_use_policy.py similarity index 100% rename from Document/.codex/hooks/pre_tool_use_policy.py rename to Coding/Gemini/.codex/hooks/pre_tool_use_policy.py diff --git a/Document/.codex/hooks/stop_continue.py b/Coding/Gemini/.codex/hooks/stop_continue.py similarity index 92% rename from Document/.codex/hooks/stop_continue.py rename to Coding/Gemini/.codex/hooks/stop_continue.py index b576aab..e61f2ae 100644 --- a/Document/.codex/hooks/stop_continue.py +++ b/Coding/Gemini/.codex/hooks/stop_continue.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Run Report Harness validation when a Codex turn stops and request one more pass if it fails.""" +"""Run repository validation when a Codex turn stops and request one more pass if it fails.""" from __future__ import annotations diff --git a/Coding/Gemini/AGENTS.md b/Coding/Gemini/AGENTS.md new file mode 100644 index 0000000..37a55bf --- /dev/null +++ b/Coding/Gemini/AGENTS.md @@ -0,0 +1,40 @@ +# Project: {프로젝트명} + +## Repository Role +- This repository is a Codex-first Harness Engineering template. +- Persistent repository instructions live in this `AGENTS.md`. +- Reusable repo-scoped workflows live in `.agents/skills/`. +- Project-scoped custom agents live in `.codex/agents/`. +- Experimental hooks live in `.codex/hooks.json`. + +## 기술 스택 +- {프레임워크 (예: Next.js 15)} +- {언어 (예: TypeScript strict mode)} +- {스타일링 (예: Tailwind CSS)} + +## 아키텍처 규칙 +- CRITICAL: {절대 지켜야 할 규칙 1 (예: 모든 API 로직은 app/api/ 라우트 핸들러에서만 처리)} +- CRITICAL: {절대 지켜야 할 규칙 2 (예: 클라이언트 컴포넌트에서 직접 외부 API를 호출하지 말 것)} +- {일반 규칙 (예: 컴포넌트는 components/ 폴더에, 타입은 types/ 폴더에 분리)} + +## Harness Workflow +- 먼저 `docs/PRD.md`, `docs/ARCHITECTURE.md`, `docs/ADR.md`, `docs/UI_GUIDE.md`를 읽고 기획/설계 의도를 파악할 것 +- 단계별 실행 계획이 필요하면 repo skill `harness-workflow`를 사용해 `phases/` 아래 파일을 설계할 것 +- 변경사항 리뷰가 필요하면 repo skill `harness-review` 또는 Codex의 `/review`를 사용할 것 +- `phases/{phase}/index.json`은 phase 진행 상태의 단일 진실 공급원으로 취급할 것 +- 각 `stepN.md`는 독립된 Codex 세션에서도 실행 가능하도록 자기완결적으로 작성할 것 + +## 개발 프로세스 +- CRITICAL: 새 기능 구현 시 반드시 테스트를 먼저 작성하고, 테스트가 통과하는 구현을 작성할 것 (TDD) +- 커밋 메시지는 conventional commits 형식을 따를 것 (`feat:`, `fix:`, `docs:`, `refactor:`) +- `scripts/execute.py`는 step 완료 후 코드/메타데이터 커밋을 정리하므로, step 프롬프트 안에서 별도 커밋을 만들 필요는 없음 + +## 검증 +- 기본 검증 스크립트는 `python scripts/validate_workspace.py` +- Node 프로젝트면 `package.json`의 `lint`, `build`, `test` 스크립트를 자동 탐지해 순서대로 실행 +- 다른 스택이면 `HARNESS_VALIDATION_COMMANDS` 환경 변수에 줄바꿈 기준으로 검증 커맨드를 지정 + +## 명령어 +- `python scripts/execute.py `: Codex 기반 phase 순차 실행 +- `python scripts/execute.py --push`: phase 완료 후 브랜치 push +- `python scripts/validate_workspace.py`: 저장소 검증 diff --git a/Coding/Gemini/README.md b/Coding/Gemini/README.md new file mode 100644 index 0000000..cad8f9e --- /dev/null +++ b/Coding/Gemini/README.md @@ -0,0 +1,2 @@ +# Agentic-AI-Template + diff --git a/Coding/Gemini/docs/ADR.md b/Coding/Gemini/docs/ADR.md new file mode 100644 index 0000000..216ad6d --- /dev/null +++ b/Coding/Gemini/docs/ADR.md @@ -0,0 +1,21 @@ +# Architecture Decision Records + +## 철학 +{프로젝트의 핵심 가치관 (예: MVP 속도 최우선. 외부 의존성 최소화. 작동하는 최소 구현을 선택.)} + +--- + +### ADR-001: {결정 사항 (예: Next.js App Router 선택)} +**결정**: {뭘 선택했는지} +**이유**: {왜 선택했는지} +**트레이드오프**: {뭘 포기했는지} + +### ADR-002: {결정 사항} +**결정**: {뭘 선택했는지} +**이유**: {왜 선택했는지} +**트레이드오프**: {뭘 포기했는지} + +### ADR-003: {결정 사항} +**결정**: {뭘 선택했는지} +**이유**: {왜 선택했는지} +**트레이드오프**: {뭘 포기했는지} diff --git a/Coding/Gemini/docs/ARCHITECTURE.md b/Coding/Gemini/docs/ARCHITECTURE.md new file mode 100644 index 0000000..2ff9891 --- /dev/null +++ b/Coding/Gemini/docs/ARCHITECTURE.md @@ -0,0 +1,24 @@ +# 아키텍처 + +## 디렉토리 구조 +``` +src/ +├── app/ # 페이지 + API 라우트 +├── components/ # UI 컴포넌트 +├── types/ # TypeScript 타입 정의 +├── lib/ # 유틸리티 + 헬퍼 +└── services/ # 외부 API 래퍼 +``` + +## 패턴 +{사용하는 디자인 패턴 (예: Server Components 기본, 인터랙션이 필요한 곳만 Client Component)} + +## 데이터 흐름 +``` +{데이터가 어떻게 흐르는지 (예: +사용자 입력 → Client Component → API Route → 외부 API → 응답 → UI 업데이트 +)} +``` + +## 상태 관리 +{상태 관리 방식 (예: 서버 상태는 Server Components, 클라이언트 상태는 useState/useReducer)} diff --git a/Coding/Gemini/docs/PRD.md b/Coding/Gemini/docs/PRD.md new file mode 100644 index 0000000..b1950bb --- /dev/null +++ b/Coding/Gemini/docs/PRD.md @@ -0,0 +1,21 @@ +# PRD: {프로젝트명} + +## 목표 +{이 프로젝트가 해결하려는 문제를 한 줄로 요약} + +## 사용자 +{누가 이 제품을 쓰는지} + +## 핵심 기능 +1. {기능 1} +2. {기능 2} +3. {기능 3} + +## MVP 제외 사항 +- {안 만들 것 1} +- {안 만들 것 2} +- {안 만들 것 3} + +## 디자인 +- {디자인 방향 (예: 다크모드 고정, 미니멀)} +- {색상 (예: 무채색 + 포인트 1가지)} diff --git a/Coding/Gemini/docs/UI_GUIDE.md b/Coding/Gemini/docs/UI_GUIDE.md new file mode 100644 index 0000000..c3d280f --- /dev/null +++ b/Coding/Gemini/docs/UI_GUIDE.md @@ -0,0 +1,76 @@ +# UI 디자인 가이드 + +## 디자인 원칙 +1. {원칙 1 — 예: "도구처럼 보여야 한다. 마케팅 페이지가 아니라 매일 쓰는 대시보드."} +2. {원칙 2} +3. {원칙 3} + +## AI 슬롭 안티패턴 — 하지 마라 +| 금지 사항 | 이유 | +|-----------|------| +| backdrop-filter: blur() | glass morphism은 AI 템플릿의 가장 흔한 징후 | +| gradient-text (배경 그라데이션 텍스트) | AI가 만든 SaaS 랜딩의 1번 특징 | +| "Powered by AI" 배지 | 기능이 아니라 장식. 사용자에게 가치 없음 | +| box-shadow 글로우 애니메이션 | 네온 글로우 = AI 슬롭 | +| 보라/인디고 브랜드 색상 | "AI = 보라색" 클리셰 | +| 모든 카드에 동일한 rounded-2xl | 균일한 둥근 모서리는 템플릿 느낌 | +| 배경 gradient orb (blur-3xl 원형) | 모든 AI 랜딩 페이지에 있는 장식 | + +## 색상 +### 배경 +| 용도 | 값 | +|------|------| +| 페이지 | {예: #0a0a0a} | +| 카드 | {예: #141414} | + +### 텍스트 +| 용도 | 값 | +|------|------| +| 주 텍스트 | {예: text-white} | +| 본문 | {예: text-neutral-300} | +| 보조 | {예: text-neutral-400} | +| 비활성 | {예: text-neutral-500} | + +### 데이터/시맨틱 색상 +| 용도 | 값 | +|------|------| +| {긍정/성공} | {예: #22c55e} | +| {부정/에러} | {예: #ef4444} | +| {중립/기본} | {예: #525252} | + +## 컴포넌트 +### 카드 +``` +{예: rounded-lg bg-[#141414] border border-neutral-800 p-6} +``` + +### 버튼 +``` +Primary: {예: rounded-lg bg-white text-black hover:bg-neutral-200} +Text: {예: text-neutral-500 hover:text-neutral-300} +``` + +### 입력 필드 +``` +{예: rounded-lg bg-neutral-900 border border-neutral-800 px-4 py-3} +``` + +## 레이아웃 +- 전체 너비: {예: max-w-5xl} +- 정렬: {예: 좌측 정렬 기본. 중앙 정렬 금지} +- 간격: {예: gap-3~4, 섹션 간 space-y-8} + +## 타이포그래피 +| 용도 | 스타일 | +|------|--------| +| 페이지 제목 | {예: text-4xl font-semibold text-white} | +| 카드 제목 | {예: text-sm font-medium text-neutral-400} | +| 본문 | {예: text-sm text-neutral-300 leading-relaxed} | + +## 애니메이션 +- {허용할 애니메이션만 나열. 예: fade-in (0.4s), slide-up (0.5s)} +- {그 외 모든 애니메이션 금지} + +## 아이콘 +- {예: SVG 인라인, strokeWidth 1.5} +- {예: 아이콘 컨테이너(둥근 배경 박스)로 감싸지 않는다} diff --git a/Coding/Gemini/plugins/harness-engineering/.codex-plugin/plugin.json b/Coding/Gemini/plugins/harness-engineering/.codex-plugin/plugin.json new file mode 100644 index 0000000..60b9951 --- /dev/null +++ b/Coding/Gemini/plugins/harness-engineering/.codex-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "name": "harness-engineering", + "version": "1.0.0", + "description": "Repo-local Harness Engineering slash commands for Codex.", + "interface": { + "displayName": "Harness Engineering", + "shortDescription": "Harness planning and review prompts for this repo", + "longDescription": "Optional local plugin that exposes Harness Engineering slash commands while the core workflow remains in repo-native AGENTS, skills, custom agents, and hooks.", + "developerName": "Local Repository", + "category": "Productivity", + "capabilities": [ + "Interactive", + "Read", + "Write" + ], + "defaultPrompt": [ + "Use Harness Engineering to plan a new phase for this repository.", + "Review my changes against the Harness docs and rules." + ], + "brandColor": "#2563EB" + } +} diff --git a/Coding/Gemini/plugins/harness-engineering/agents/openai.yaml b/Coding/Gemini/plugins/harness-engineering/agents/openai.yaml new file mode 100644 index 0000000..2671cba --- /dev/null +++ b/Coding/Gemini/plugins/harness-engineering/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Harness Engineering" + short_description: "Use Harness slash commands in this repository" + default_prompt: "Use Harness Engineering to plan a phase or review changes in this repository." diff --git a/Document/plugins/harness-engineering/commands/harness.md b/Coding/Gemini/plugins/harness-engineering/commands/harness.md similarity index 59% rename from Document/plugins/harness-engineering/commands/harness.md rename to Coding/Gemini/plugins/harness-engineering/commands/harness.md index 59d3394..e4bc9df 100644 --- a/Document/plugins/harness-engineering/commands/harness.md +++ b/Coding/Gemini/plugins/harness-engineering/commands/harness.md @@ -1,14 +1,13 @@ --- -description: Run the Report Harness planning workflow for this repository. +description: Run the Harness Engineering planning workflow for this repository. --- # /harness ## Preflight -- Read `/AGENTS.md`, `/docs/PRD.md`, `/docs/ARCHITECTURE.md`, `/docs/ADR.md`, `/docs/UI_GUIDE.md`, `/docs/RESEARCH_LOG.md`, `/docs/REPORT_DRAFT.md`, and `/docs/FEEDBACK.md` if they exist. -- Confirm whether the user wants discussion only, a draft report plan, or file generation under `phases/`. -- Check that the report topic, purpose, keywords, direction, audience, and output format are available. +- Read `/AGENTS.md`, `/docs/PRD.md`, `/docs/ARCHITECTURE.md`, `/docs/ADR.md`, and `/docs/UI_GUIDE.md` if they exist. +- Confirm whether the user wants discussion only, a draft plan, or file generation under `phases/`. - Note whether the user explicitly asked for subagents; only then consider `phase_planner` or built-in explorers/workers. ## Plan @@ -16,7 +15,6 @@ description: Run the Report Harness planning workflow for this repository. - State what will be created or updated before editing files. - If a plan already exists under `phases/`, say whether you are extending it or replacing part of it. - Keep each proposed step small, self-contained, and independently executable. -- Separate source discovery, deep research, source review, outline, draft, and feedback steps. ## Commands @@ -30,16 +28,15 @@ description: Run the Report Harness planning workflow for this repository. ## Verification - Re-read the generated phase files for consistency. -- Check that step numbering, phase names, report artifacts, and acceptance commands line up. -- Confirm that research steps require web search and `docs/RESEARCH_LOG.md` updates. +- Check that step numbering, phase names, and acceptance commands line up. - If the repo has a validator, prefer `python scripts/validate_workspace.py` as the default acceptance command unless the user specified a narrower command. ## Summary ## Result -- **Action**: planned or generated Report Harness phase files +- **Action**: planned or generated Harness phase files - **Status**: success | partial | failed -- **Details**: phase name, step count, report artifacts, and any blockers +- **Details**: phase name, step count, and any blockers ## Next Steps diff --git a/Coding/Gemini/plugins/harness-engineering/commands/review.md b/Coding/Gemini/plugins/harness-engineering/commands/review.md new file mode 100644 index 0000000..568d6e6 --- /dev/null +++ b/Coding/Gemini/plugins/harness-engineering/commands/review.md @@ -0,0 +1,38 @@ +--- +description: Review local changes against Harness repository rules and docs. +--- + +# /review + +## Preflight + +- Read `/AGENTS.md`, `/docs/ARCHITECTURE.md`, `/docs/ADR.md`, and `/docs/UI_GUIDE.md` if they exist. +- Identify the changed files or generated `phases/` artifacts that need review. +- If the user wants a delegated review, use the read-only custom agent `harness_reviewer` only when they explicitly asked for subagents. + +## Plan + +- State what evidence will be checked: docs, changed files, generated phase files, and validation output if available. +- Prioritize correctness, architecture drift, CRITICAL rule violations, and missing tests over style commentary. + +## Commands + +- Invoke `$harness-review`. +- Use Codex built-in `/review` when the user specifically wants a code-review style pass over the working tree or git diff. +- If validation is relevant, run `python scripts/validate_workspace.py` or explain why it was not run. + +## Verification + +- Confirm that every finding is tied to a file and an actual rule or behavioral risk. +- If no findings remain, say so explicitly and mention residual risks or missing evidence. + +## Summary + +## Result +- **Action**: reviewed Harness changes +- **Status**: success | partial | failed +- **Details**: findings, docs checked, and validation status + +## Next Steps + +- Suggest the smallest follow-up: fix the top finding, rerun validation, or execute a pending phase. diff --git a/Coding/Gemini/scripts/__pycache__/execute.cpython-312.pyc b/Coding/Gemini/scripts/__pycache__/execute.cpython-312.pyc new file mode 100644 index 0000000..b92b985 Binary files /dev/null and b/Coding/Gemini/scripts/__pycache__/execute.cpython-312.pyc differ diff --git a/Coding/Gemini/scripts/__pycache__/validate_workspace.cpython-312.pyc b/Coding/Gemini/scripts/__pycache__/validate_workspace.cpython-312.pyc new file mode 100644 index 0000000..525ce64 Binary files /dev/null and b/Coding/Gemini/scripts/__pycache__/validate_workspace.cpython-312.pyc differ diff --git a/Document/scripts/execute.py b/Coding/Gemini/scripts/execute.py similarity index 84% rename from Document/scripts/execute.py rename to Coding/Gemini/scripts/execute.py index b5161a6..171f684 100644 --- a/Document/scripts/execute.py +++ b/Coding/Gemini/scripts/execute.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Report Harness Step Executor - run report phase steps sequentially with Codex and self-correction. +Harness Step Executor - run phase steps sequentially with Codex and self-correction. Usage: python scripts/execute.py [--push] @@ -53,7 +53,7 @@ class StepExecutor: """Phase 디렉토리 안의 step들을 순차 실행하는 하네스.""" MAX_RETRIES = 3 - DOCS_MSG = "docs({phase}): step {num} - {name}" + FEAT_MSG = "feat({phase}): step {num} - {name}" CHORE_MSG = "chore({phase}): step {num} output" TZ = timezone(timedelta(hours=9)) @@ -64,7 +64,6 @@ class StepExecutor: self._phase_dir_name = phase_dir_name self._top_index_file = self._phases_dir / "index.json" self._auto_push = auto_push - self._git_enabled: Optional[bool] = None if not self._phase_dir.is_dir(): print(f"ERROR: {self._phase_dir} not found") @@ -110,25 +109,12 @@ class StepExecutor: cmd = ["git"] + list(args) return subprocess.run(cmd, cwd=self._root, capture_output=True, text=True) - def _ensure_git_enabled(self) -> bool: - if self._git_enabled is not None: - return self._git_enabled - - r = self._run_git("rev-parse", "--is-inside-work-tree") - self._git_enabled = r.returncode == 0 and r.stdout.strip() == "true" - if not self._git_enabled: - print(" WARN: git 저장소가 아니므로 브랜치/커밋 관리를 건너뜁니다.") - return self._git_enabled - def _checkout_branch(self): - if not self._ensure_git_enabled(): - return - - branch = f"report-{self._phase_name}" + branch = f"feat-{self._phase_name}" r = self._run_git("rev-parse", "--abbrev-ref", "HEAD") if r.returncode != 0: - print(f" ERROR: 현재 브랜치를 확인할 수 없습니다.") + print(f" ERROR: git을 사용할 수 없거나 git repo가 아닙니다.") print(f" {r.stderr.strip()}") sys.exit(1) @@ -147,9 +133,6 @@ class StepExecutor: print(f" Branch: {branch}") def _commit_step(self, step_num: int, step_name: str): - if not self._ensure_git_enabled(): - return - output_rel = f"phases/{self._phase_dir_name}/step{step_num}-output.json" index_rel = f"phases/{self._phase_dir_name}/index.json" @@ -158,12 +141,12 @@ class StepExecutor: self._run_git("reset", "HEAD", "--", index_rel) if self._run_git("diff", "--cached", "--quiet").returncode != 0: - msg = self.DOCS_MSG.format(phase=self._phase_name, num=step_num, name=step_name) + 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()}") + print(f" WARN: 코드 커밋 실패: {r.stderr.strip()}") self._run_git("add", "-A") if self._run_git("diff", "--cached", "--quiet").returncode != 0: @@ -223,20 +206,19 @@ class StepExecutor: f"{prev_error}\n\n---\n\n" ) return ( - f"당신은 {self._project} 보고서 프로젝트의 리서치/작성 에이전트입니다. 아래 step을 수행하세요.\n\n" + f"당신은 {self._project} 프로젝트의 개발자입니다. 아래 step을 수행하세요.\n\n" f"{guardrails}\n\n---\n\n" f"{step_context}{retry_section}" f"## 작업 규칙\n\n" - f"1. 이전 step 산출물과 `docs/*.md` 문서를 확인하고 보고서 맥락을 유지하라.\n" - f"2. 이 step에 명시된 리서치, 출처 검토, 작성, 피드백 작업만 수행하라. 범위 밖 산출물을 만들지 마라.\n" - f"3. 사실 주장, 수치, 최신 동향, 법/정책/시장 정보는 웹 검색으로 확인하고 출처를 `docs/RESEARCH_LOG.md`에 기록하라.\n" - f"4. 출처가 부족한 핵심 주장은 본문에 단정하지 말고 `docs/RESEARCH_LOG.md`의 `검증 필요`에 남겨라.\n" - f"5. AC(Acceptance Criteria) 검증을 직접 실행하라.\n" - f"6. /phases/{self._phase_dir_name}/index.json의 해당 step status를 업데이트하라:\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" - 사용자 개입이 필요한 경우 (보고서 방향성 누락, 유료 자료 접근, 인증, 수동 판단 등) → \"blocked\" + \"blocked_reason\" 기록 후 즉시 중단\n" - f"7. 변경사항은 워킹 트리에 남겨라. step 완료 후 커밋은 execute.py가 정리한다.\n\n---\n\n" + f" - 사용자 개입이 필요한 경우 (API 키, 인증, 수동 설정 등) → \"blocked\" + \"blocked_reason\" 기록 후 즉시 중단\n" + f"6. 변경사항은 워킹 트리에 남겨라. step 완료 후 커밋은 execute.py가 정리한다.\n\n---\n\n" ) # --- Codex 호출 --- @@ -285,7 +267,7 @@ class StepExecutor: def _print_header(self): print(f"\n{'='*60}") - print(f" Report Harness Step Executor") + print(f" Harness Step Executor") print(f" Phase: {self._phase_name} | Steps: {self._total}") if self._auto_push: print(f" Auto-push: enabled") @@ -391,7 +373,7 @@ class StepExecutor: 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 report steps completed!") + print("\n All steps completed!") return step_num = pending["step"] @@ -409,20 +391,15 @@ class StepExecutor: self._write_json(self._index_file, index) self._update_top_index("completed") - if self._ensure_git_enabled(): - 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}") + 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: - if not self._ensure_git_enabled(): - print(" WARN: git 저장소가 아니므로 push를 건너뜁니다.") - return - - branch = f"report-{self._phase_name}" + 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()}") diff --git a/Document/scripts/test_execute.py b/Coding/Gemini/scripts/test_execute.py similarity index 90% rename from Document/scripts/test_execute.py rename to Coding/Gemini/scripts/test_execute.py index c7b0292..1039319 100644 --- a/Document/scripts/test_execute.py +++ b/Coding/Gemini/scripts/test_execute.py @@ -1,4 +1,4 @@ -"""execute.py Report Harness safety-net tests.""" +"""execute.py Codex migration safety-net tests.""" import json import sys @@ -18,7 +18,7 @@ import execute as ex @pytest.fixture def tmp_project(tmp_path): - """phases/, AGENTS.md, docs/ 를 갖춘 임시 보고서 프로젝트 구조.""" + """phases/, AGENTS.md, docs/ 를 갖춘 임시 프로젝트 구조.""" phases_dir = tmp_path / "phases" phases_dir.mkdir() @@ -35,7 +35,7 @@ def tmp_project(tmp_path): @pytest.fixture def phase_dir(tmp_project): - """step 3개를 가진 report phase 디렉토리.""" + """step 3개를 가진 phase 디렉토리.""" d = tmp_project / "phases" / "0-mvp" d.mkdir() @@ -43,13 +43,13 @@ def phase_dir(tmp_project): "project": "TestProject", "phase": "mvp", "steps": [ - {"step": 0, "name": "brief-audit", "status": "completed", "summary": "보고서 브리프 점검 완료"}, - {"step": 1, "name": "deep-research", "status": "completed", "summary": "핵심 리서치 완료"}, - {"step": 2, "name": "draft-report", "status": "pending"}, + {"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)) - (d / "step2.md").write_text("# Step 2: draft-report\n\n보고서 초안을 작성하세요.") + (d / "step2.md").write_text("# Step 2: UI\n\nUI를 구현하세요.") return d @@ -195,20 +195,20 @@ class TestBuildStepContext: def test_includes_completed_with_summary(self, phase_dir): index = json.loads((phase_dir / "index.json").read_text()) result = ex.StepExecutor._build_step_context(index) - assert "Step 0 (brief-audit): 보고서 브리프 점검 완료" in result - assert "Step 1 (deep-research): 핵심 리서치 완료" in result + 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()) result = ex.StepExecutor._build_step_context(index) - assert "draft-report" not in result + assert "ui" not in result def test_excludes_completed_without_summary(self, phase_dir): index = json.loads((phase_dir / "index.json").read_text()) del index["steps"][0]["summary"] result = ex.StepExecutor._build_step_context(index) - assert "brief-audit" not in result - assert "deep-research" in result + assert "setup" not in result + assert "core" in result def test_empty_when_no_completed(self): index = {"steps": [{"step": 0, "name": "a", "status": "pending"}]} @@ -243,15 +243,6 @@ class TestBuildPreamble: result = executor._build_preamble("", "") assert "커밋은 execute.py가 정리한다" in result - def test_mentions_report_agent_role(self, executor): - result = executor._build_preamble("", "") - assert "리서치/작성 에이전트" in result - - def test_requires_source_logging(self, executor): - result = executor._build_preamble("", "") - assert "docs/RESEARCH_LOG.md" in result - assert "출처" in result - def test_includes_rules(self, executor): result = executor._build_preamble("", "") assert "작업 규칙" in result @@ -342,14 +333,12 @@ class TestCheckoutBranch: def test_already_on_branch(self, executor): self._mock_git(executor, [ - MagicMock(returncode=0, stdout="true\n", stderr=""), - MagicMock(returncode=0, stdout="report-mvp\n", stderr=""), + 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="true\n", stderr=""), MagicMock(returncode=0, stdout="main\n", stderr=""), MagicMock(returncode=0, stdout="", stderr=""), MagicMock(returncode=0, stdout="", stderr=""), @@ -358,7 +347,6 @@ class TestCheckoutBranch: def test_branch_not_exists_create(self, executor): self._mock_git(executor, [ - MagicMock(returncode=0, stdout="true\n", stderr=""), MagicMock(returncode=0, stdout="main\n", stderr=""), MagicMock(returncode=1, stdout="", stderr="not found"), MagicMock(returncode=0, stdout="", stderr=""), @@ -367,7 +355,6 @@ class TestCheckoutBranch: def test_checkout_fails_exits(self, executor): self._mock_git(executor, [ - MagicMock(returncode=0, stdout="true\n", stderr=""), MagicMock(returncode=0, stdout="main\n", stderr=""), MagicMock(returncode=1, stdout="", stderr=""), MagicMock(returncode=1, stdout="", stderr="dirty tree"), @@ -376,12 +363,13 @@ class TestCheckoutBranch: executor._checkout_branch() assert exc_info.value.code == 1 - def test_no_git_skips_branch_management(self, executor): + def test_no_git_exits(self, executor): self._mock_git(executor, [ MagicMock(returncode=1, stdout="", stderr="not a git repo"), ]) - executor._checkout_branch() - assert executor._git_enabled is False + with pytest.raises(SystemExit) as exc_info: + executor._checkout_branch() + assert exc_info.value.code == 1 # --------------------------------------------------------------------------- @@ -393,8 +381,6 @@ class TestCommitStep: calls = [] def fake_git(*args): calls.append(args) - if args == ("rev-parse", "--is-inside-work-tree"): - return MagicMock(returncode=0, stdout="true\n", stderr="") if args[:2] == ("diff", "--cached"): return MagicMock(returncode=1) return MagicMock(returncode=0, stdout="", stderr="") @@ -404,16 +390,14 @@ class TestCommitStep: commit_calls = [c for c in calls if c[0] == "commit"] assert len(commit_calls) == 2 - assert "docs(mvp):" in commit_calls[0][2] + assert "feat(mvp):" in commit_calls[0][2] assert "chore(mvp):" in commit_calls[1][2] - def test_no_report_changes_skips_docs_commit(self, executor): + def test_no_code_changes_skips_feat_commit(self, executor): call_count = {"diff": 0} calls = [] def fake_git(*args): calls.append(args) - if args == ("rev-parse", "--is-inside-work-tree"): - return MagicMock(returncode=0, stdout="true\n", stderr="") if args[:2] == ("diff", "--cached"): call_count["diff"] += 1 if call_count["diff"] == 1: @@ -450,7 +434,7 @@ class TestInvokeCodex: assert "--json" in cmd assert "-o" in cmd assert "PREAMBLE" in kwargs["input"] - assert "보고서 초안을 작성하세요" in kwargs["input"] + assert "UI를 구현하세요" in kwargs["input"] assert output["finalMessage"] is None def test_saves_output_json(self, executor): diff --git a/Document/scripts/validate_workspace.py b/Coding/Gemini/scripts/validate_workspace.py similarity index 55% rename from Document/scripts/validate_workspace.py rename to Coding/Gemini/scripts/validate_workspace.py index bedb0d1..f7c0526 100644 --- a/Document/scripts/validate_workspace.py +++ b/Coding/Gemini/scripts/validate_workspace.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Run repository validation commands for the Report Harness template.""" +"""Run repository validation commands for the Harness template.""" from __future__ import annotations @@ -11,35 +11,6 @@ from pathlib import Path DEFAULT_NPM_ORDER = ("lint", "build", "test") -REQUIRED_DOCS = ( - "PRD.md", - "ARCHITECTURE.md", - "ADR.md", - "UI_GUIDE.md", - "RESEARCH_LOG.md", - "REPORT_DRAFT.md", - "FEEDBACK.md", -) -REQUIRED_SECTIONS = { - "PRD.md": ( - "## 보고서 주제", - "## 용도", - "## 핵심 키워드", - "## 방향성", - ), - "RESEARCH_LOG.md": ( - "## 출처 목록", - "## 검증 필요", - ), - "REPORT_DRAFT.md": ( - "## Executive Summary", - "## 참고 출처", - "## 사용자 피드백 질문", - ), - "FEEDBACK.md": ( - "## 다음 반복에서 확인할 사항", - ), -} def load_env_commands() -> list[str]: @@ -76,30 +47,6 @@ def discover_commands(root: Path) -> list[str]: return load_npm_commands(root) -def validate_report_docs(root: Path) -> list[str]: - errors: list[str] = [] - docs_dir = root / "docs" - if not docs_dir.is_dir(): - return ["Missing docs/ directory."] - - for filename in REQUIRED_DOCS: - path = docs_dir / filename - if not path.exists(): - errors.append(f"Missing docs/{filename}.") - continue - - text = path.read_text(encoding="utf-8") - if not text.strip(): - errors.append(f"docs/{filename} is empty.") - continue - - for heading in REQUIRED_SECTIONS.get(filename, ()): - if heading not in text: - errors.append(f"docs/{filename} missing section: {heading}") - - return errors - - def run_command(command: str, root: Path) -> subprocess.CompletedProcess: return subprocess.run( command, @@ -120,19 +67,11 @@ def emit_stream(prefix: str, content: str, *, stream) -> None: def main() -> int: root = Path(__file__).resolve().parent.parent - doc_errors = validate_report_docs(root) - if doc_errors: - print("Report template validation failed:", file=sys.stderr) - for error in doc_errors: - print(f"- {error}", file=sys.stderr) - return 1 - commands = discover_commands(root) if not commands: - print("Report template validation succeeded.") - print("No extra validation commands configured.") - print("Set HARNESS_VALIDATION_COMMANDS or add npm scripts for lint/build/test if needed.") + print("No validation commands configured.") + print("Set HARNESS_VALIDATION_COMMANDS or add npm scripts for lint/build/test.") return 0 for command in commands: diff --git a/Document/.agents/skills/harness-review/SKILL.md b/Document/.agents/skills/harness-review/SKILL.md deleted file mode 100644 index 532fccd..0000000 --- a/Document/.agents/skills/harness-review/SKILL.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -name: harness-review -description: Review a Report Harness repository against its persistent rules and report docs. Use when Codex is asked to review local changes, generated phase files, research logs, report drafts, feedback handling, source quality, or output against `AGENTS.md`, `docs/ARCHITECTURE.md`, `docs/ADR.md`, `docs/UI_GUIDE.md`, and Harness step acceptance criteria. ---- - -# Report Harness Review - -Use this skill when the user wants a repository-grounded report review instead of generic commentary. - -## Review input set - -Read these first: - -- `/AGENTS.md` -- `/docs/PRD.md` -- `/docs/ARCHITECTURE.md` -- `/docs/ADR.md` -- `/docs/UI_GUIDE.md` -- `/docs/RESEARCH_LOG.md` -- `/docs/REPORT_DRAFT.md` -- `/docs/FEEDBACK.md` -- the changed files or generated `phases/` files under review - -If the user explicitly asks for delegated review, prefer the repo custom agent `harness_reviewer` or built-in read-only explorers. - -## Checklist - -Evaluate the patch against these questions: - -1. Does it follow the report workflow described in `docs/ARCHITECTURE.md`? -2. Does it stay within the research and writing decisions documented in `docs/ADR.md`? -3. Does it satisfy the topic, purpose, keywords, direction, audience, and output format in `docs/PRD.md`? -4. Are new factual claims backed by sources in `docs/RESEARCH_LOG.md` or clearly marked as `검증 필요`? -5. Are source quality, publication/access dates, conflicts, and uncertainty handled explicitly? -6. Does it violate any CRITICAL rule in `AGENTS.md`? -7. Do generated `phases/` files remain self-contained, executable, and internally consistent? -8. If the user expects verification, does `python scripts/validate_workspace.py` succeed or is the failure explained? - -## Output rules - -- Lead with findings, ordered by severity. -- Include file references for each finding. -- Explain the concrete report-quality risk, not just the rule name. -- If there are no findings, say so explicitly and mention residual risks or missing evidence. -- Keep summaries brief after the findings. - -## Preferred review table - -When the user asks for a checklist-style review, use this table: - -| Item | Result | Notes | -|------|------|------| -| Brief alignment | PASS/FAIL | {details} | -| Research coverage | PASS/FAIL | {details} | -| Source quality | PASS/FAIL | {details} | -| Claim support | PASS/FAIL | {details} | -| Feedback handling | PASS/FAIL | {details} | -| CRITICAL rules | PASS/FAIL | {details} | -| Validation | PASS/FAIL | {details} | - -## What not to do - -- Do not approve a draft just because it is well written. -- Do not ignore unsupported claims, stale sources, or missing counterarguments. -- Do not assume a passing hook means the report is acceptable; review the actual documents and phase files. diff --git a/Document/.agents/skills/harness-review/agents/openai.yaml b/Document/.agents/skills/harness-review/agents/openai.yaml deleted file mode 100644 index fb7ec2c..0000000 --- a/Document/.agents/skills/harness-review/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Report Harness Review" - short_description: "Review report artifacts against Report Harness rules" - default_prompt: "Use Report Harness review to check source quality, claim support, feedback handling, and rules." diff --git a/Document/.agents/skills/harness-workflow/SKILL.md b/Document/.agents/skills/harness-workflow/SKILL.md deleted file mode 100644 index 7882b0c..0000000 --- a/Document/.agents/skills/harness-workflow/SKILL.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -name: harness-workflow -description: Plan and run the Report Harness workflow for this repository. Use when Codex needs to read `AGENTS.md` and `docs/*.md`, discuss report scope, draft research/drafting/revision phase plans, or create/update `phases/index.json`, `phases/{phase}/index.json`, and `phases/{phase}/stepN.md` files for staged execution. ---- - -# Report Harness Workflow - -Use this skill when the user is working in the report-generation Harness template and wants structured planning or phase-file generation. - -## Workflow - -### 1. Explore first - -Read these files before proposing steps: - -- `/AGENTS.md` -- `/docs/PRD.md` -- `/docs/ARCHITECTURE.md` -- `/docs/ADR.md` -- `/docs/UI_GUIDE.md` -- `/docs/RESEARCH_LOG.md` -- `/docs/REPORT_DRAFT.md` -- `/docs/FEEDBACK.md` - -If the user explicitly asks for parallel exploration, use built-in Codex subagents such as `explorer`, or the repo-scoped custom agent `phase_planner`. - -### 2. Confirm report inputs before locking the plan - -Check that `docs/PRD.md` has enough information for: - -1. Report topic. -2. Purpose and target reader. -3. Required keywords. -4. Direction or stance. -5. Output format and success criteria. - -If any of these are missing and cannot be reasonably inferred, create a short `blocked` step or ask the user for the missing input instead of fabricating intent. - -### 3. Design steps with strict boundaries - -When drafting a report phase plan: - -1. Keep each step focused on one research question, source-review pass, outline pass, draft pass, or feedback pass. -2. Make each `stepN.md` self-contained so it can run in an isolated Codex session. -3. List prerequisite files explicitly. Never rely on "as discussed above". -4. Specify report artifacts and invariants, not line-by-line prose. -5. Use executable acceptance commands, not vague success criteria. -6. Write concrete warnings in "do not do X because Y" form. -7. Use kebab-case step names. -8. Require web search for facts, current information, market data, policy/law, statistics, or recommendations. -9. Require source notes in `docs/RESEARCH_LOG.md` before adding claims to `docs/REPORT_DRAFT.md`. - -Recommended step sequence: - -1. `brief-audit`: validate the report brief and derive research questions. -2. `source-discovery`: perform broad web search and record candidate sources. -3. `deep-research-{topic}`: research one specific keyword, stakeholder, region, or issue. -4. `source-review`: check source quality, conflicts, freshness, and missing evidence. -5. `outline`: design the report structure from the evidence. -6. `draft-report`: write or revise `docs/REPORT_DRAFT.md`. -7. `feedback-questions`: add targeted questions for the user and update `docs/FEEDBACK.md`. - -## Files to generate - -### `phases/index.json` - -Top-level phase registry. Append to `phases[]` when the file already exists. - -```json -{ - "phases": [ - { - "dir": "0-research", - "status": "pending" - } - ] -} -``` - -- `dir`: phase directory name. -- `status`: `pending`, `completed`, `error`, or `blocked`. -- Timestamp fields are written by `scripts/execute.py`; do not seed them during planning. - -### `phases/{phase}/index.json` - -```json -{ - "project": "", - "phase": "", - "steps": [ - { "step": 0, "name": "brief-audit", "status": "pending" }, - { "step": 1, "name": "source-discovery", "status": "pending" }, - { "step": 2, "name": "draft-outline", "status": "pending" } - ] -} -``` - -- `project`: from `AGENTS.md` or `docs/PRD.md`. -- `phase`: directory name. -- `steps[].step`: zero-based integer. -- `steps[].name`: kebab-case slug. -- `steps[].status`: initialize to `pending`. - -### `phases/{phase}/stepN.md` - -Each step file should contain: - -1. A title. -2. A "read these files first" section. -3. A concrete task section. -4. Executable acceptance criteria. -5. Verification instructions. -6. Explicit prohibitions. - -Recommended structure: - -````markdown -# Step {N}: {name} - -## Read First -- /AGENTS.md -- /docs/PRD.md -- /docs/ARCHITECTURE.md -- /docs/ADR.md -- /docs/UI_GUIDE.md -- /docs/RESEARCH_LOG.md -- /docs/REPORT_DRAFT.md -- /docs/FEEDBACK.md -- {files from previous steps} - -## Task -{specific report research, source review, drafting, or feedback instructions} - -## Acceptance Criteria -```bash -python scripts/validate_workspace.py -``` - -## Verification -1. Run the acceptance commands. -2. Check `AGENTS.md` and `docs/*.md` for report-rule drift. -3. Confirm every new factual claim has a source or is listed under `검증 필요`. -4. Update the matching step in `phases/{phase}/index.json`: - - completed + summary - - error + error_message - - blocked + blocked_reason - -## Do Not -- {concrete prohibition} -```` - -## Execution - -Run the generated phase with: - -```bash -python scripts/execute.py -python scripts/execute.py --push -``` - -`scripts/execute.py` handles: - -- `report-{phase}` branch checkout/creation when the directory is a git repository -- guardrail injection from `AGENTS.md` and `docs/*.md` -- accumulation of completed-step summaries into later prompts -- up to 3 retries with prior error feedback -- two-phase commit of report artifact changes and metadata updates -- timestamps such as `created_at`, `started_at`, `completed_at`, `failed_at`, and `blocked_at` - -## Recovery rules - -- If a step is `error`, reset its status to `pending`, remove `error_message`, then rerun. -- If a step is `blocked`, resolve the blocker, reset to `pending`, remove `blocked_reason`, then rerun. diff --git a/Document/.agents/skills/harness-workflow/agents/openai.yaml b/Document/.agents/skills/harness-workflow/agents/openai.yaml deleted file mode 100644 index 1a4bc9e..0000000 --- a/Document/.agents/skills/harness-workflow/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Report Harness Workflow" - short_description: "Guide Codex through report phase planning" - default_prompt: "Use the Report Harness workflow to plan research, drafting, and revision steps." diff --git a/Document/.codex/agents/harness-reviewer.toml b/Document/.codex/agents/harness-reviewer.toml deleted file mode 100644 index 84ea06c..0000000 --- a/Document/.codex/agents/harness-reviewer.toml +++ /dev/null @@ -1,11 +0,0 @@ -name = "harness_reviewer" -description = "Read-only reviewer for Report Harness projects, focused on brief alignment, source quality, unsupported claims, feedback handling, and critical rule violations." -model = "gpt-5.4" -model_reasoning_effort = "high" -sandbox_mode = "read-only" -developer_instructions = """ -Review changes like a report owner. -Prioritize brief alignment, factual support, source quality, uncertainty handling, feedback traceability, and CRITICAL rules over style. -Always compare the patch against AGENTS.md, docs/PRD.md, docs/ARCHITECTURE.md, docs/ADR.md, docs/UI_GUIDE.md, and the requested acceptance criteria. -Lead with concrete findings and file references. If no material issues are found, say so explicitly and mention residual risks. -""" diff --git a/Document/.codex/agents/phase-planner.toml b/Document/.codex/agents/phase-planner.toml deleted file mode 100644 index 522f931..0000000 --- a/Document/.codex/agents/phase-planner.toml +++ /dev/null @@ -1,12 +0,0 @@ -name = "phase_planner" -description = "Read-heavy Report Harness planner that decomposes report briefs into minimal, self-contained research, drafting, and revision phase files." -model = "gpt-5.4" -model_reasoning_effort = "high" -sandbox_mode = "read-only" -developer_instructions = """ -Plan before implementing. -Read AGENTS.md and the docs directory, identify the smallest coherent report phase boundaries, and draft self-contained steps. -Keep each step scoped to one research question, source review task, outline task, draft task, or feedback task when possible. -Do not make file changes unless the parent agent explicitly asks you to write files. -Return concrete file paths, acceptance commands, source requirements, and blocking assumptions. -""" diff --git a/Document/AGENTS.md b/Document/AGENTS.md deleted file mode 100644 index a3940e6..0000000 --- a/Document/AGENTS.md +++ /dev/null @@ -1,60 +0,0 @@ -# Project: {보고서 프로젝트명} - -## Repository Role -- This repository is a Codex-first Report Generation Harness template. -- Persistent repository instructions live in this `AGENTS.md`. -- Reusable repo-scoped workflows live in `.agents/skills/`. -- Project-scoped custom agents live in `.codex/agents/`. -- Experimental hooks live in `.codex/hooks.json`. -- The repository is optimized for evidence-backed report creation, not application development. - -## 보고서 입력값 -- `docs/PRD.md`에 아래 내용을 먼저 입력한다. -- 보고서 주제: {무엇에 대한 보고서인지 한 문장으로 작성} -- 용도: {의사결정, 내부 공유, 시장 조사, 전략 수립, 기술 검토, 정책 검토 등} -- 핵심 키워드: {보고서 내용에 반드시 포함할 키워드, 산업, 기업, 지역, 기간, 이해관계자} -- 방향성: {중립 분석, 찬반 비교, 실행 전략, 리스크 중심, 트렌드 전망, 근거 기반 추천 등} -- 독자: {최종 보고서를 읽는 사람과 기대 수준} -- 형식: {요약 보고서, 심층 보고서, 임원 브리프, 조사 메모, 백서 초안 등} - -## 산출물 규칙 -- CRITICAL: 사실 주장, 수치, 최신 동향, 법/정책/시장 정보는 반드시 웹 검색으로 확인하고 출처를 남길 것. -- CRITICAL: 출처가 없는 핵심 주장은 보고서 본문에 단정적으로 쓰지 말고 `docs/RESEARCH_LOG.md`의 `검증 필요` 항목에 남길 것. -- CRITICAL: 검색 출처는 제목, 발행기관, URL, 발행일 또는 접근일, 핵심 내용, 신뢰도 메모를 함께 기록할 것. -- CRITICAL: 사용자가 제공한 방향성과 충돌하는 보고서 구조 변경은 먼저 피드백 요청 항목으로 남길 것. -- 보고서 초안은 `docs/REPORT_DRAFT.md`에 작성한다. -- 조사 누적 기록은 `docs/RESEARCH_LOG.md`에 작성한다. -- 사용자 피드백과 반영 이력은 `docs/FEEDBACK.md`에 작성한다. -- 상대 날짜 표현(오늘, 최근, 작년 등)은 가능한 한 절대 날짜로 풀어 쓴다. - -## Report Harness Workflow -- 먼저 `docs/PRD.md`, `docs/ARCHITECTURE.md`, `docs/ADR.md`, `docs/UI_GUIDE.md`, `docs/RESEARCH_LOG.md`, `docs/REPORT_DRAFT.md`, `docs/FEEDBACK.md`를 읽고 보고서 의도를 파악할 것. -- 단계별 실행 계획이 필요하면 repo skill `harness-workflow`를 사용해 `phases/` 아래 파일을 설계할 것. -- 변경사항 리뷰가 필요하면 repo skill `harness-review` 또는 Codex의 `/review`를 사용할 것. -- `phases/{phase}/index.json`은 phase 진행 상태의 단일 진실 공급원으로 취급할 것. -- 각 `stepN.md`는 독립된 Codex 세션에서도 실행 가능하도록 자기완결적으로 작성할 것. - -## 보고서 생성 프로세스 -- 사용자가 `docs/PRD.md`의 입력값을 채운다. -- Codex는 필요한 AI agent 역할을 설계한다. 예: research lead, domain researcher, source reviewer, outline writer, report editor. -- 웹 검색을 통해 주제별 근거를 축적하고, 모든 핵심 근거를 `docs/RESEARCH_LOG.md`에 남긴다. -- 축적된 근거를 바탕으로 `docs/REPORT_DRAFT.md`에 초안을 작성한다. -- 초안 말미에 사용자에게 확인할 피드백 질문을 남기고, 답변은 `docs/FEEDBACK.md`에 축적한다. -- 피드백 반영 후 필요한 추가 검색, 구조 변경, 문체 조정, 결론 보강을 반복한다. - -## 품질 기준 -- 결론은 근거보다 앞서가면 안 된다. -- 반대 근거, 불확실성, 데이터 한계, 이해관계자별 관점을 별도 섹션으로 다룬다. -- 출처 품질은 1차 자료, 공식 문서, 학술/정부/공공기관, 신뢰도 높은 언론/리서치 순으로 우선한다. -- 웹 검색 결과가 서로 충돌하면 충돌 내용을 숨기지 말고 비교 표로 정리한다. -- 보고서에는 독자가 다음 행동을 결정할 수 있는 요약, 근거, 리스크, 권고가 포함되어야 한다. - -## 검증 -- 기본 검증 스크립트는 `python scripts/validate_workspace.py`. -- `HARNESS_VALIDATION_COMMANDS` 환경 변수에 줄바꿈 기준으로 검증 커맨드를 지정하면 해당 커맨드를 실행한다. -- Node 프로젝트가 추가된 경우 `package.json`의 `lint`, `build`, `test` 스크립트를 자동 탐지해 순서대로 실행한다. - -## 명령어 -- `python scripts/execute.py `: Codex 기반 report phase 순차 실행. -- `python scripts/execute.py --push`: phase 완료 후 브랜치 push. -- `python scripts/validate_workspace.py`: 저장소 검증. diff --git a/Document/README.md b/Document/README.md deleted file mode 100644 index 3a83db0..0000000 --- a/Document/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Report Generation Harness Template - -Codex-first template for creating evidence-backed reports through staged research, drafting, user feedback, and revision. - -## How To Use -1. Fill in `docs/PRD.md` with the report topic, purpose, keywords, direction, audience, and output format. -2. Run the default phase with `python scripts/execute.py 0-report-generation`, or ask Codex to plan a custom report phase with the repo `harness-workflow` skill or `/harness` command. -3. Review `docs/RESEARCH_LOG.md` and `docs/REPORT_DRAFT.md`. -4. Add user feedback to `docs/FEEDBACK.md`, then run the next revision phase. - -## Core Files -- `AGENTS.md`: persistent report-generation rules. -- `docs/PRD.md`: report brief and required user input. -- `docs/ARCHITECTURE.md`: report workflow and artifact architecture. -- `docs/ADR.md`: research and writing decisions. -- `docs/UI_GUIDE.md`: report style guide. -- `docs/RESEARCH_LOG.md`: accumulated web research and source notes. -- `docs/REPORT_DRAFT.md`: current report draft. -- `docs/FEEDBACK.md`: feedback and revision log. - -## Commands -```bash -python scripts/validate_workspace.py -python scripts/execute.py 0-report-generation -python scripts/execute.py 0-report-generation --push -``` diff --git a/Document/docs/ADR.md b/Document/docs/ADR.md deleted file mode 100644 index bc3d0a3..0000000 --- a/Document/docs/ADR.md +++ /dev/null @@ -1,26 +0,0 @@ -# Report Decision Records - -## 철학 -보고서는 빠르게 그럴듯한 문장을 만드는 것이 아니라, 사용자가 판단할 수 있는 근거를 축적하고 그 근거로부터 결론을 구성하는 작업이다. 속도보다 출처, 검증 가능성, 피드백 반복성을 우선한다. - ---- - -### ADR-001: 문서 파일을 단일 진실 공급원으로 사용 -**결정**: 보고서 입력, 리서치 로그, 초안, 피드백을 모두 `docs/` 아래 Markdown 파일로 관리한다. -**이유**: 독립된 Codex 세션과 phase 실행 사이에서도 맥락을 잃지 않고 이어갈 수 있다. -**트레이드오프**: 문서가 길어질수록 정리 비용이 늘어나므로 중복 출처와 폐기된 주장 정리가 필요하다. - -### ADR-002: 웹 검색과 출처 기록을 기본값으로 둔다 -**결정**: 최신성 또는 사실 정확성이 필요한 보고서 내용은 웹 검색 후 `docs/RESEARCH_LOG.md`에 출처를 기록한다. -**이유**: 보고서 품질은 모델 기억보다 검증 가능한 근거에 의해 결정된다. -**트레이드오프**: 초안 작성 속도는 느려지지만, 사용자가 검토하고 수정할 수 있는 근거가 남는다. - -### ADR-003: 초안 작성 전에 리서치 로그를 먼저 축적한다 -**결정**: 바로 본문을 쓰지 않고 리서치 질문, 검색 결과, 상충 근거, 한계를 먼저 정리한다. -**이유**: 결론이 근거보다 앞서가는 것을 막고, 보고서 방향성을 더 쉽게 조정할 수 있다. -**트레이드오프**: 짧은 보고서에도 최소한의 리서치 단계가 필요하다. - -### ADR-004: 피드백 반복을 명시적 산출물로 관리한다 -**결정**: 사용자 피드백은 `docs/FEEDBACK.md`에 원문과 반영 결과를 함께 기록한다. -**이유**: 반복 작성 과정에서 어떤 요구가 반영되었고 무엇이 보류되었는지 추적할 수 있다. -**트레이드오프**: 피드백 관리 문서를 별도로 갱신해야 한다. diff --git a/Document/docs/ARCHITECTURE.md b/Document/docs/ARCHITECTURE.md deleted file mode 100644 index 299036b..0000000 --- a/Document/docs/ARCHITECTURE.md +++ /dev/null @@ -1,71 +0,0 @@ -# 보고서 생성 아키텍처 - -## 디렉토리 구조 -```text -docs/ -├── PRD.md # 사용자가 입력하는 보고서 브리프 -├── ARCHITECTURE.md # 보고서 생성 흐름과 산출물 구조 -├── ADR.md # 리서치/작성 의사결정 -├── UI_GUIDE.md # 보고서 문체와 형식 가이드 -├── RESEARCH_LOG.md # 웹 검색 결과, 출처, 검증 메모 -├── REPORT_DRAFT.md # 현재 보고서 초안 -└── FEEDBACK.md # 사용자 피드백과 반영 이력 - -phases/ -├── index.json -└── {phase}/ - ├── index.json - ├── step0.md - ├── step1.md - └── stepN.md -``` - -## 에이전트 역할 패턴 -- Research Lead: 브리프를 읽고 리서치 질문, 검색 범위, 단계 계획을 정의한다. -- Domain Researcher: 특정 키워드, 산업, 지역, 기간을 맡아 웹 검색하고 근거를 축적한다. -- Source Reviewer: 출처 신뢰도, 최신성, 상충 자료, 과장된 해석을 점검한다. -- Outline Writer: 리서치 로그를 바탕으로 보고서 구조와 핵심 메시지를 설계한다. -- Report Editor: 초안을 작성하고 문체, 흐름, 근거 연결, 피드백 반영을 정리한다. - -하나의 Codex 세션이 여러 역할을 수행할 수 있다. 사용자가 명시적으로 subagent 사용을 요청한 경우에만 병렬 subagent를 구성한다. - -## 데이터 흐름 -```text -사용자 입력 - -> docs/PRD.md - -> phase plan 생성 - -> 웹 검색 및 출처 검증 - -> docs/RESEARCH_LOG.md 축적 - -> 개요와 논점 정리 - -> docs/REPORT_DRAFT.md 초안 작성 - -> 사용자 피드백 요청 - -> docs/FEEDBACK.md 기록 - -> 추가 검색/수정 반복 -``` - -## 산출물 불변식 -- `docs/PRD.md`는 사용자 의도와 범위의 단일 진실 공급원이다. -- `docs/RESEARCH_LOG.md`는 모든 핵심 사실과 출처의 단일 진실 공급원이다. -- `docs/REPORT_DRAFT.md`에는 검증된 근거와 명시된 한계만 반영한다. -- `docs/FEEDBACK.md`에는 피드백 원문, 해석, 반영 여부, 미반영 이유를 기록한다. -- `phases/{phase}/index.json`은 단계 실행 상태의 단일 진실 공급원이다. - -## 리서치 기록 단위 -각 출처는 아래 필드를 포함해야 한다. - -```text -- 주장/논점: -- 출처 제목: -- 발행기관: -- URL: -- 발행일: -- 접근일: -- 핵심 내용: -- 신뢰도 메모: -- 보고서 반영 위치: -``` - -## 상태 관리 -- 보고서 상태는 별도 애플리케이션 상태가 아니라 문서 파일의 변경 이력으로 관리한다. -- 단계 상태는 `phases/{phase}/index.json`의 `pending`, `completed`, `error`, `blocked`로 관리한다. -- 사용자의 추가 판단이 필요한 경우 Codex는 추측하지 말고 해당 step을 `blocked`로 표시하고 `blocked_reason`을 기록한다. diff --git a/Document/docs/FEEDBACK.md b/Document/docs/FEEDBACK.md deleted file mode 100644 index c2d9b11..0000000 --- a/Document/docs/FEEDBACK.md +++ /dev/null @@ -1,15 +0,0 @@ -# Feedback Log - -이 파일은 사용자 피드백, 해석, 반영 여부, 추가 리서치 필요 사항을 기록한다. - -## Feedback 001 -- 날짜: {YYYY-MM-DD} -- 피드백 원문: {사용자 피드백} -- 해석: {보고서에 어떤 변경이 필요한지} -- 반영 위치: {docs/REPORT_DRAFT.md 섹션명 또는 기타 파일} -- 상태: {pending | applied | rejected | needs-research} -- 미반영 이유: {해당 시 작성} - -## 다음 반복에서 확인할 사항 -- {확인할 사항 1} -- {확인할 사항 2} diff --git a/Document/docs/PRD.md b/Document/docs/PRD.md deleted file mode 100644 index da2beac..0000000 --- a/Document/docs/PRD.md +++ /dev/null @@ -1,54 +0,0 @@ -# Report Brief: {보고서 제목} - -## 입력 상태 -- 작성 상태: {draft | ready-for-research | ready-for-draft | revision} -- 마지막 업데이트: {YYYY-MM-DD} -- 작성자/요청자: {이름 또는 팀} - -## 보고서 주제 -{무엇에 대한 보고서인지 한 문장으로 작성한다.} - -## 용도 -{이 보고서가 사용될 의사결정, 회의, 내부 공유, 투자 검토, 전략 수립, 정책 검토, 기술 검토 등의 목적을 적는다.} - -## 핵심 키워드 -- {키워드 1} -- {키워드 2} -- {키워드 3} -- {산업, 기업, 지역, 기간, 제도, 기술, 이해관계자 등} - -## 방향성 -{중립 분석, 찬반 비교, 시장 전망, 실행 전략, 리스크 중심, 정책 영향 분석, 기술 타당성 검토 등 원하는 논조와 분석 방향을 적는다.} - -## 독자 -- 주요 독자: {임원, 실무자, 투자자, 고객, 정책 담당자 등} -- 독자의 배경지식: {낮음 | 중간 | 높음} -- 독자가 보고서로 결정해야 하는 것: {결정 또는 행동} - -## 산출물 형식 -- 형식: {요약 보고서 | 심층 보고서 | 임원 브리프 | 조사 메모 | 백서 초안 | 발표자료용 원고} -- 목표 분량: {예: 3쪽, 10쪽, 2,000자, 8개 섹션} -- 언어와 문체: {예: 한국어, 전문적이고 간결한 문체} -- 표/그림 요구: {예: 비교표 필수, 그래프 후보 제안, 이미지 불필요} - -## 반드시 포함할 질문 -1. {질문 1} -2. {질문 2} -3. {질문 3} - -## 제외 범위 -- {다루지 않을 내용 1} -- {다루지 않을 내용 2} -- {다루지 않을 내용 3} - -## 성공 기준 -- {성공 기준 1: 예: 핵심 주장마다 출처가 있다} -- {성공 기준 2: 예: 독자가 실행 결정을 내릴 수 있는 권고가 있다} -- {성공 기준 3: 예: 불확실성과 반대 근거가 별도 정리되어 있다} - -## 사용자 피드백 질문 -초안 작성 후 사용자에게 반드시 확인할 질문을 적는다. - -1. {확인 질문 1} -2. {확인 질문 2} -3. {확인 질문 3} diff --git a/Document/docs/REPORT_DRAFT.md b/Document/docs/REPORT_DRAFT.md deleted file mode 100644 index 3314c81..0000000 --- a/Document/docs/REPORT_DRAFT.md +++ /dev/null @@ -1,36 +0,0 @@ -# Report Draft: {보고서 제목} - -## Executive Summary -{핵심 결론과 권고를 3~5개 문장 또는 bullet로 작성한다.} - -## 배경과 문제 정의 -{보고서 주제의 맥락, 독자가 알아야 할 배경, 분석 범위를 작성한다.} - -## 핵심 발견사항 -1. {발견사항 1} -2. {발견사항 2} -3. {발견사항 3} - -## 세부 분석 -{근거와 출처를 연결해 분석을 작성한다.} - -## 리스크와 불확실성 -- {리스크 1} -- {리스크 2} -- {데이터 한계 또는 추가 확인 필요 사항} - -## 선택지 또는 시나리오 -| 선택지 | 장점 | 단점 | 필요한 조건 | -|--------|------|------|-------------| -| {선택지 A} | {장점} | {단점} | {조건} | - -## 권고안 -{보고서 목적에 맞는 판단, 실행 순서, 의사결정 포인트를 작성한다.} - -## 참고 출처 -- {출처명, URL, 발행일 또는 접근일} - -## 사용자 피드백 질문 -1. {초안 검토 질문 1} -2. {초안 검토 질문 2} -3. {초안 검토 질문 3} diff --git a/Document/docs/RESEARCH_LOG.md b/Document/docs/RESEARCH_LOG.md deleted file mode 100644 index d74f55f..0000000 --- a/Document/docs/RESEARCH_LOG.md +++ /dev/null @@ -1,31 +0,0 @@ -# Research Log - -이 파일은 보고서 작성에 사용되는 웹 검색 결과와 검증 메모를 축적하는 공간이다. 출처 없는 핵심 주장은 보고서 본문에 단정적으로 반영하지 않는다. - -## 리서치 질문 -1. {질문 1} -2. {질문 2} -3. {질문 3} - -## 출처 목록 - -### Source 001: {출처 제목} -- 주장/논점: {이 출처가 뒷받침하거나 반박하는 주장} -- 발행기관: {기관명} -- URL: {https://...} -- 발행일: {YYYY-MM-DD 또는 알 수 없음} -- 접근일: {YYYY-MM-DD} -- 핵심 내용: {보고서에 필요한 내용 요약} -- 신뢰도 메모: {1차 자료/공식 문서/언론/블로그/이해관계 여부} -- 보고서 반영 위치: {섹션명 또는 미반영} - -## 상충 근거 -| 쟁점 | 출처 A | 출처 B | 해석 | -|------|--------|--------|------| -| {쟁점} | {요약} | {요약} | {어떻게 다룰지} | - -## 검증 필요 -- {출처가 부족하거나 추가 확인이 필요한 주장} - -## 폐기한 주장 -- {근거 부족, 오래된 자료, 방향성 불일치 등으로 제외한 내용과 이유} diff --git a/Document/docs/UI_GUIDE.md b/Document/docs/UI_GUIDE.md deleted file mode 100644 index ed8659b..0000000 --- a/Document/docs/UI_GUIDE.md +++ /dev/null @@ -1,53 +0,0 @@ -# 보고서 스타일 가이드 - -## 작성 원칙 -1. 독자가 먼저 결론을 파악하고, 바로 근거와 한계를 확인할 수 있게 쓴다. -2. 핵심 주장은 출처, 수치, 비교 기준 중 하나 이상으로 뒷받침한다. -3. 불확실성, 반대 근거, 데이터 한계는 숨기지 않고 별도 문단으로 정리한다. -4. 사용자의 방향성을 따르되 근거와 충돌하면 충돌 사실을 명시한다. - -## 기본 구조 -보고서 형식이 별도로 지정되지 않았다면 아래 구조를 사용한다. - -1. Executive Summary -2. 배경과 문제 정의 -3. 핵심 발견사항 -4. 세부 분석 -5. 리스크와 불확실성 -6. 선택지 또는 시나리오 -7. 권고안 -8. 참고 출처 -9. 사용자 피드백 질문 - -## 문체 -- 한국어 기본 문체는 전문적이고 간결하게 쓴다. -- 과장 표현, 광고 문구, 막연한 낙관론을 피한다. -- `중요하다`, `빠르게 성장한다`, `리스크가 크다` 같은 표현은 가능한 한 근거를 붙인다. -- 숫자는 단위와 기준 시점을 함께 쓴다. -- 상대 날짜는 절대 날짜로 풀어 쓴다. 예: `최근` 대신 `2025년 4분기 이후`. - -## 표와 목록 -- 비교, 장단점, 출처 충돌, 시나리오 분석은 표를 우선 사용한다. -- 5개 이상의 병렬 항목은 목록으로 정리한다. -- 표에는 비교 기준을 명확히 적는다. - -## 출처 표기 -- 본문에는 간결한 출처명을 적고, 상세 정보는 `참고 출처` 또는 `docs/RESEARCH_LOG.md`에 둔다. -- URL만 나열하지 말고 출처가 뒷받침하는 주장을 함께 적는다. -- 출처가 오래되었거나 이해관계가 있으면 신뢰도 메모를 남긴다. - -## 하지 말 것 -| 금지 사항 | 이유 | -|-----------|------| -| 출처 없는 수치 단정 | 검증 불가능한 보고서가 됨 | -| 검색 결과 상위 문서만 요약 | 편향과 최신성 오류 가능성이 큼 | -| 결론부터 정하고 근거를 끼워 맞춤 | 분석 신뢰도를 떨어뜨림 | -| 사용자가 준 키워드 누락 | 입력 의도와 산출물이 어긋남 | -| 피드백 반영 여부 미기록 | 반복 작업에서 맥락이 사라짐 | - -## 최종 점검 -- 보고서 주제, 용도, 키워드, 방향성이 본문에 반영되었는가? -- 핵심 주장마다 출처 또는 검증 메모가 있는가? -- 상충 근거와 한계를 다루었는가? -- 독자가 취할 수 있는 다음 행동이 분명한가? -- 사용자에게 확인할 피드백 질문이 남아 있는가? diff --git a/Document/phases/0-report-generation/index.json b/Document/phases/0-report-generation/index.json deleted file mode 100644 index 082aa9b..0000000 --- a/Document/phases/0-report-generation/index.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "project": "{보고서 프로젝트명}", - "phase": "0-report-generation", - "steps": [ - { - "step": 0, - "name": "brief-audit", - "status": "pending" - }, - { - "step": 1, - "name": "source-discovery", - "status": "pending" - }, - { - "step": 2, - "name": "deep-research", - "status": "pending" - }, - { - "step": 3, - "name": "source-review-outline", - "status": "pending" - }, - { - "step": 4, - "name": "draft-report", - "status": "pending" - }, - { - "step": 5, - "name": "feedback-setup", - "status": "pending" - } - ] -} diff --git a/Document/phases/0-report-generation/step0.md b/Document/phases/0-report-generation/step0.md deleted file mode 100644 index 816b1e7..0000000 --- a/Document/phases/0-report-generation/step0.md +++ /dev/null @@ -1,34 +0,0 @@ -# Step 0: brief-audit - -## Read First -- /AGENTS.md -- /docs/PRD.md -- /docs/ARCHITECTURE.md -- /docs/ADR.md -- /docs/UI_GUIDE.md -- /docs/RESEARCH_LOG.md -- /docs/REPORT_DRAFT.md -- /docs/FEEDBACK.md - -## Task -Audit `docs/PRD.md` for the minimum report inputs: topic, purpose, keywords, direction, audience, output format, required questions, exclusions, and success criteria. - -If the brief is usable, update `docs/RESEARCH_LOG.md` with a concrete research question list derived from the brief. If a critical input is still a placeholder or empty, update this step in `phases/0-report-generation/index.json` as `blocked` with a concise `blocked_reason` and stop. - -## Acceptance Criteria -```bash -python scripts/validate_workspace.py -``` - -## Verification -1. Run the acceptance command. -2. Confirm the report brief has enough information to guide research. -3. Confirm `docs/RESEARCH_LOG.md` contains research questions aligned with the brief. -4. Update this step in `phases/0-report-generation/index.json`: - - `completed` + `summary` - - `blocked` + `blocked_reason` - - `error` + `error_message` - -## Do Not -- Do not invent missing report intent because later research will follow the wrong direction. -- Do not start drafting the report in this step. diff --git a/Document/phases/0-report-generation/step1.md b/Document/phases/0-report-generation/step1.md deleted file mode 100644 index 231e5ce..0000000 --- a/Document/phases/0-report-generation/step1.md +++ /dev/null @@ -1,33 +0,0 @@ -# Step 1: source-discovery - -## Read First -- /AGENTS.md -- /docs/PRD.md -- /docs/ARCHITECTURE.md -- /docs/ADR.md -- /docs/UI_GUIDE.md -- /docs/RESEARCH_LOG.md -- /phases/0-report-generation/index.json - -## Task -Perform broad web search for the report topic, purpose, and keywords in `docs/PRD.md`. - -Update `docs/RESEARCH_LOG.md` with candidate sources. Prioritize official documents, primary sources, public data, academic or government sources, credible industry research, and reputable journalism. Record each source with title, publisher, URL, publication date or access date, key content, reliability note, and likely report section. - -Aim for enough source diversity to cover the main claims, counterarguments, and uncertainty. If web access is unavailable or a required source is behind authentication/paywall, mark the step `blocked` with the reason. - -## Acceptance Criteria -```bash -python scripts/validate_workspace.py -``` - -## Verification -1. Run the acceptance command. -2. Confirm `docs/RESEARCH_LOG.md` contains source entries with URLs and reliability notes. -3. Confirm weak or unverified claims are listed under `검증 필요`. -4. Update this step in `phases/0-report-generation/index.json`. - -## Do Not -- Do not use model memory as a substitute for web search. -- Do not copy long source text into the repository; summarize only what is needed. -- Do not add unsupported claims to `docs/REPORT_DRAFT.md` in this step. diff --git a/Document/phases/0-report-generation/step2.md b/Document/phases/0-report-generation/step2.md deleted file mode 100644 index 81a7595..0000000 --- a/Document/phases/0-report-generation/step2.md +++ /dev/null @@ -1,36 +0,0 @@ -# Step 2: deep-research - -## Read First -- /AGENTS.md -- /docs/PRD.md -- /docs/ARCHITECTURE.md -- /docs/ADR.md -- /docs/UI_GUIDE.md -- /docs/RESEARCH_LOG.md -- /phases/0-report-generation/index.json - -## Task -Deepen the research around the most important keywords, stakeholders, time periods, regions, and required questions from `docs/PRD.md`. - -Use web search to fill evidence gaps, find current data, and look for conflicting viewpoints. Update: - -- `docs/RESEARCH_LOG.md` source list with additional source entries. -- `docs/RESEARCH_LOG.md` `상충 근거` table when sources disagree. -- `docs/RESEARCH_LOG.md` `검증 필요` for claims that still lack enough support. -- `docs/RESEARCH_LOG.md` `폐기한 주장` for claims excluded because the evidence is weak, outdated, or outside scope. - -## Acceptance Criteria -```bash -python scripts/validate_workspace.py -``` - -## Verification -1. Run the acceptance command. -2. Confirm the required questions in `docs/PRD.md` have research coverage or explicit gaps. -3. Confirm conflicts and uncertainty are visible in `docs/RESEARCH_LOG.md`. -4. Update this step in `phases/0-report-generation/index.json`. - -## Do Not -- Do not resolve conflicting evidence by ignoring one side. -- Do not overfit research to a predetermined conclusion. -- Do not write the final report body before the source review and outline step. diff --git a/Document/phases/0-report-generation/step3.md b/Document/phases/0-report-generation/step3.md deleted file mode 100644 index 11d3870..0000000 --- a/Document/phases/0-report-generation/step3.md +++ /dev/null @@ -1,32 +0,0 @@ -# Step 3: source-review-outline - -## Read First -- /AGENTS.md -- /docs/PRD.md -- /docs/ARCHITECTURE.md -- /docs/ADR.md -- /docs/UI_GUIDE.md -- /docs/RESEARCH_LOG.md -- /docs/REPORT_DRAFT.md -- /phases/0-report-generation/index.json - -## Task -Review `docs/RESEARCH_LOG.md` for source quality, source freshness, conflicts, weak claims, and missing evidence. Then create or refine the report outline in `docs/REPORT_DRAFT.md`. - -The outline must reflect the report purpose, keywords, direction, audience, and output format from `docs/PRD.md`. Include placeholders for key claims, supporting sources, risks, uncertainty, and feedback questions. - -## Acceptance Criteria -```bash -python scripts/validate_workspace.py -``` - -## Verification -1. Run the acceptance command. -2. Confirm the outline follows `docs/UI_GUIDE.md`. -3. Confirm major sections have evidence sources or explicit evidence gaps. -4. Update this step in `phases/0-report-generation/index.json`. - -## Do Not -- Do not hide source weaknesses because they are inconvenient. -- Do not delete useful research history from `docs/RESEARCH_LOG.md`. -- Do not write polished conclusions that are not supported by the reviewed sources. diff --git a/Document/phases/0-report-generation/step4.md b/Document/phases/0-report-generation/step4.md deleted file mode 100644 index e50dc02..0000000 --- a/Document/phases/0-report-generation/step4.md +++ /dev/null @@ -1,32 +0,0 @@ -# Step 4: draft-report - -## Read First -- /AGENTS.md -- /docs/PRD.md -- /docs/ARCHITECTURE.md -- /docs/ADR.md -- /docs/UI_GUIDE.md -- /docs/RESEARCH_LOG.md -- /docs/REPORT_DRAFT.md -- /phases/0-report-generation/index.json - -## Task -Write or revise `docs/REPORT_DRAFT.md` into a coherent report draft. - -Use only claims supported by `docs/RESEARCH_LOG.md` or clearly label unresolved claims as uncertain. Follow the target format, audience, direction, and success criteria in `docs/PRD.md`. Include executive summary, background, core findings, detailed analysis, risks and uncertainty, options or scenarios, recommendations, references, and user feedback questions unless the brief specifies a different structure. - -## Acceptance Criteria -```bash -python scripts/validate_workspace.py -``` - -## Verification -1. Run the acceptance command. -2. Confirm every core claim in the draft maps to a source or a `검증 필요` note. -3. Confirm the draft includes risks, uncertainty, and next-decision guidance. -4. Update this step in `phases/0-report-generation/index.json`. - -## Do Not -- Do not include source-free statistics or current claims. -- Do not use persuasive language that exceeds the evidence. -- Do not remove feedback questions from the end of the draft. diff --git a/Document/phases/0-report-generation/step5.md b/Document/phases/0-report-generation/step5.md deleted file mode 100644 index 9b7e9ab..0000000 --- a/Document/phases/0-report-generation/step5.md +++ /dev/null @@ -1,35 +0,0 @@ -# Step 5: feedback-setup - -## Read First -- /AGENTS.md -- /docs/PRD.md -- /docs/ARCHITECTURE.md -- /docs/ADR.md -- /docs/UI_GUIDE.md -- /docs/RESEARCH_LOG.md -- /docs/REPORT_DRAFT.md -- /docs/FEEDBACK.md -- /phases/0-report-generation/index.json - -## Task -Prepare the report for user review. - -Update `docs/REPORT_DRAFT.md` with focused feedback questions that help the user decide the next iteration. Update `docs/FEEDBACK.md` with a new pending feedback entry that names the draft version, review focus, and next questions. - -If the report still has material evidence gaps, list them clearly in both `docs/REPORT_DRAFT.md` and `docs/FEEDBACK.md`. - -## Acceptance Criteria -```bash -python scripts/validate_workspace.py -``` - -## Verification -1. Run the acceptance command. -2. Confirm `docs/REPORT_DRAFT.md` is ready for user review. -3. Confirm `docs/FEEDBACK.md` has a pending entry for the next iteration. -4. Update this step in `phases/0-report-generation/index.json`. - -## Do Not -- Do not mark unresolved evidence gaps as finished. -- Do not ask generic feedback questions that do not help revise the report. -- Do not start a second revision phase in this step. diff --git a/Document/phases/index.json b/Document/phases/index.json deleted file mode 100644 index a5bf339..0000000 --- a/Document/phases/index.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "phases": [ - { - "dir": "0-report-generation", - "status": "pending" - } - ] -} diff --git a/Document/plugins/harness-engineering/.codex-plugin/plugin.json b/Document/plugins/harness-engineering/.codex-plugin/plugin.json deleted file mode 100644 index 70565bf..0000000 --- a/Document/plugins/harness-engineering/.codex-plugin/plugin.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "harness-engineering", - "version": "1.0.0", - "description": "Repo-local Report Harness slash commands for Codex.", - "interface": { - "displayName": "Report Harness", - "shortDescription": "Report planning and review prompts for this repo", - "longDescription": "Optional local plugin that exposes Report Harness slash commands while the core workflow remains in repo-native AGENTS, skills, custom agents, and hooks.", - "developerName": "Local Repository", - "category": "Productivity", - "capabilities": [ - "Interactive", - "Read", - "Write" - ], - "defaultPrompt": [ - "Use Report Harness to plan a new research, drafting, or revision phase for this repository.", - "Review my report artifacts against the Report Harness docs and rules." - ], - "brandColor": "#2563EB" - } -} diff --git a/Document/plugins/harness-engineering/agents/openai.yaml b/Document/plugins/harness-engineering/agents/openai.yaml deleted file mode 100644 index e0b0931..0000000 --- a/Document/plugins/harness-engineering/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Report Harness" - short_description: "Use report slash commands in this repository" - default_prompt: "Use Report Harness to plan a research phase or review report artifacts in this repository." diff --git a/Document/plugins/harness-engineering/commands/review.md b/Document/plugins/harness-engineering/commands/review.md deleted file mode 100644 index 193a3ba..0000000 --- a/Document/plugins/harness-engineering/commands/review.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -description: Review report changes against Report Harness repository rules and docs. ---- - -# /review - -## Preflight - -- Read `/AGENTS.md`, `/docs/PRD.md`, `/docs/ARCHITECTURE.md`, `/docs/ADR.md`, `/docs/UI_GUIDE.md`, `/docs/RESEARCH_LOG.md`, `/docs/REPORT_DRAFT.md`, and `/docs/FEEDBACK.md` if they exist. -- Identify the changed report files, research logs, feedback files, or generated `phases/` artifacts that need review. -- If the user wants a delegated review, use the read-only custom agent `harness_reviewer` only when they explicitly asked for subagents. - -## Plan - -- State what evidence will be checked: brief alignment, research coverage, source quality, unsupported claims, feedback handling, generated phase files, and validation output if available. -- Prioritize factual support, source freshness, CRITICAL rule violations, and missing counterarguments over style commentary. - -## Commands - -- Invoke `$harness-review`. -- Use Codex built-in `/review` when the user specifically wants a review-style pass over the working tree or generated report artifacts. -- If validation is relevant, run `python scripts/validate_workspace.py` or explain why it was not run. - -## Verification - -- Confirm that every finding is tied to a file and an actual rule or report-quality risk. -- If no findings remain, say so explicitly and mention residual risks or missing evidence. - -## Summary - -## Result -- **Action**: reviewed Report Harness changes -- **Status**: success | partial | failed -- **Details**: findings, docs checked, and validation status - -## Next Steps - -- Suggest the smallest follow-up: fix the top finding, rerun validation, or execute a pending phase. diff --git a/Document/scripts/__pycache__/execute.cpython-312.pyc b/Document/scripts/__pycache__/execute.cpython-312.pyc deleted file mode 100644 index af9aab9..0000000 Binary files a/Document/scripts/__pycache__/execute.cpython-312.pyc and /dev/null differ diff --git a/Document/scripts/__pycache__/validate_workspace.cpython-312.pyc b/Document/scripts/__pycache__/validate_workspace.cpython-312.pyc deleted file mode 100644 index 20dcb29..0000000 Binary files a/Document/scripts/__pycache__/validate_workspace.cpython-312.pyc and /dev/null differ diff --git a/Document/scripts/test_validate_workspace.py b/Document/scripts/test_validate_workspace.py deleted file mode 100644 index e2abdb1..0000000 --- a/Document/scripts/test_validate_workspace.py +++ /dev/null @@ -1,45 +0,0 @@ -"""validate_workspace.py Report Harness tests.""" - -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).parent)) -import validate_workspace as vw - - -def write_required_docs(root: Path): - docs = root / "docs" - docs.mkdir() - contents = { - "PRD.md": "# Brief\n\n## 보고서 주제\n\n## 용도\n\n## 핵심 키워드\n\n## 방향성\n", - "ARCHITECTURE.md": "# Architecture\n", - "ADR.md": "# ADR\n", - "UI_GUIDE.md": "# Style\n", - "RESEARCH_LOG.md": "# Research\n\n## 출처 목록\n\n## 검증 필요\n", - "REPORT_DRAFT.md": "# Draft\n\n## Executive Summary\n\n## 참고 출처\n\n## 사용자 피드백 질문\n", - "FEEDBACK.md": "# Feedback\n\n## 다음 반복에서 확인할 사항\n", - } - for filename, text in contents.items(): - (docs / filename).write_text(text, encoding="utf-8") - - -def test_validate_report_docs_success(tmp_path): - write_required_docs(tmp_path) - - assert vw.validate_report_docs(tmp_path) == [] - - -def test_validate_report_docs_reports_missing_doc(tmp_path): - write_required_docs(tmp_path) - (tmp_path / "docs" / "RESEARCH_LOG.md").unlink() - - assert "Missing docs/RESEARCH_LOG.md." in vw.validate_report_docs(tmp_path) - - -def test_validate_report_docs_reports_missing_section(tmp_path): - write_required_docs(tmp_path) - (tmp_path / "docs" / "PRD.md").write_text("# Brief\n\n## 보고서 주제\n", encoding="utf-8") - - errors = vw.validate_report_docs(tmp_path) - - assert "docs/PRD.md missing section: ## 용도" in errors diff --git a/Writing/Codex/.agents/skills/document-harness/SKILL.md b/Writing/Codex/.agents/skills/document-harness/SKILL.md new file mode 100644 index 0000000..1e153e8 --- /dev/null +++ b/Writing/Codex/.agents/skills/document-harness/SKILL.md @@ -0,0 +1,131 @@ +--- +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 new file mode 100644 index 0000000..3e849e5 --- /dev/null +++ b/Writing/Codex/.agents/skills/document-harness/references/phase-templates.md @@ -0,0 +1,85 @@ +# 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 new file mode 100644 index 0000000..c581c94 --- /dev/null +++ b/Writing/Codex/.agents/skills/document-review/SKILL.md @@ -0,0 +1,45 @@ +--- +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 new file mode 100644 index 0000000..9e6bf2e --- /dev/null +++ b/Writing/Codex/.codex/agents/doc_drafter.toml @@ -0,0 +1,16 @@ +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 new file mode 100644 index 0000000..19bbd46 --- /dev/null +++ b/Writing/Codex/.codex/agents/doc_researcher.toml @@ -0,0 +1,15 @@ +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 new file mode 100644 index 0000000..67d48dd --- /dev/null +++ b/Writing/Codex/.codex/agents/doc_reviewer.toml @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..513a1e3 --- /dev/null +++ b/Writing/Codex/.codex/agents/evidence_checker.toml @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..a6c03df --- /dev/null +++ b/Writing/Codex/.codex/config.toml @@ -0,0 +1,10 @@ +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 new file mode 100644 index 0000000..e9489fb --- /dev/null +++ b/Writing/Codex/.codex/hooks.json @@ -0,0 +1,30 @@ +{ + "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 new file mode 100644 index 0000000..e2a139a --- /dev/null +++ b/Writing/Codex/.codex/hooks/pre_tool_guard.py @@ -0,0 +1,67 @@ +#!/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 new file mode 100644 index 0000000..8719875 --- /dev/null +++ b/Writing/Codex/.codex/hooks/stop_validate.py @@ -0,0 +1,39 @@ +#!/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 new file mode 100644 index 0000000..ad8994f --- /dev/null +++ b/Writing/Codex/.gitignore @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..31f01c5 --- /dev/null +++ b/Writing/Codex/AGENTS.md @@ -0,0 +1,57 @@ +# 프로젝트: 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 new file mode 100644 index 0000000..00c7159 --- /dev/null +++ b/Writing/Codex/README.md @@ -0,0 +1,259 @@ +# 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 new file mode 100644 index 0000000..b9e580f --- /dev/null +++ b/Writing/Codex/docs/ADR.md @@ -0,0 +1,48 @@ +# 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 new file mode 100644 index 0000000..3f86a87 --- /dev/null +++ b/Writing/Codex/docs/ARCHITECTURE.md @@ -0,0 +1,74 @@ +# 문서 작성 하네스 아키텍처 + +## 디렉토리 구조 +```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 new file mode 100644 index 0000000..06c8544 --- /dev/null +++ b/Writing/Codex/docs/DraftFeedback.md @@ -0,0 +1,23 @@ +# Draft Feedback + +초안 검토 후 사용자가 피드백을 남기는 파일이다. AI Agent는 이 파일을 읽고 `final/` 문서에 반영한다. + +## 검토 대상 +- `drafts/{파일명}` + +## 전체 판단 +- {예: 방향 승인 / 구조 수정 필요 / 추가 조사 필요 / 톤 변경 필요} + +## 수정 요청 +| 위치 | 요청 | 이유 | +|------|------|------| +| {섹션 또는 파일명} | {수정 요청} | {왜 필요한지} | + +## 추가로 포함할 내용 +- {추가 내용} + +## 제외하거나 줄일 내용 +- {삭제/축소할 내용} + +## 승인 여부 +{예: 초안 방향 승인 / 아직 승인하지 않음} diff --git a/Writing/Codex/docs/FinalFeedback.md b/Writing/Codex/docs/FinalFeedback.md new file mode 100644 index 0000000..95c388c --- /dev/null +++ b/Writing/Codex/docs/FinalFeedback.md @@ -0,0 +1,17 @@ +# Final Feedback + +최종 문서 검토 후 사용자가 승인 또는 추가 수정 요청을 남기는 파일이다. + +## 검토 대상 +- `final/{파일명}` + +## 승인 여부 +{예: 승인 / 수정 후 승인 / 승인하지 않음} + +## 최종 수정 요청 +| 위치 | 요청 | 우선순위 | +|------|------|----------| +| {섹션 또는 파일명} | {수정 요청} | {높음/중간/낮음} | + +## 비고 +- {추가 의견} diff --git a/Writing/Codex/docs/PRD.md b/Writing/Codex/docs/PRD.md new file mode 100644 index 0000000..09fe5ee --- /dev/null +++ b/Writing/Codex/docs/PRD.md @@ -0,0 +1,68 @@ +# 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 new file mode 100644 index 0000000..d04849a --- /dev/null +++ b/Writing/Codex/docs/ResearchNote.md @@ -0,0 +1,54 @@ +# 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 new file mode 100644 index 0000000..d535648 --- /dev/null +++ b/Writing/Codex/docs/UI_GUIDE.md @@ -0,0 +1,54 @@ +# 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 new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Writing/Codex/drafts/.gitkeep @@ -0,0 +1 @@ + diff --git a/Writing/Codex/final/.gitkeep b/Writing/Codex/final/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Writing/Codex/final/.gitkeep @@ -0,0 +1 @@ + diff --git a/Writing/Codex/scripts/execute.py b/Writing/Codex/scripts/execute.py new file mode 100644 index 0000000..15410e3 --- /dev/null +++ b/Writing/Codex/scripts/execute.py @@ -0,0 +1,426 @@ +#!/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 new file mode 100644 index 0000000..ecb0f2d --- /dev/null +++ b/Writing/Codex/scripts/test_execute.py @@ -0,0 +1,560 @@ +""" +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 new file mode 100644 index 0000000..2bdfba1 --- /dev/null +++ b/Writing/Codex/scripts/validate_docs.py @@ -0,0 +1,196 @@ +#!/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 new file mode 100644 index 0000000..1e153e8 --- /dev/null +++ b/Writing/Gemini/.agents/skills/document-harness/SKILL.md @@ -0,0 +1,131 @@ +--- +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/Gemini/.agents/skills/document-harness/references/phase-templates.md b/Writing/Gemini/.agents/skills/document-harness/references/phase-templates.md new file mode 100644 index 0000000..3e849e5 --- /dev/null +++ b/Writing/Gemini/.agents/skills/document-harness/references/phase-templates.md @@ -0,0 +1,85 @@ +# 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/Gemini/.agents/skills/document-review/SKILL.md b/Writing/Gemini/.agents/skills/document-review/SKILL.md new file mode 100644 index 0000000..c581c94 --- /dev/null +++ b/Writing/Gemini/.agents/skills/document-review/SKILL.md @@ -0,0 +1,45 @@ +--- +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/Gemini/.codex/agents/doc_drafter.toml b/Writing/Gemini/.codex/agents/doc_drafter.toml new file mode 100644 index 0000000..9e6bf2e --- /dev/null +++ b/Writing/Gemini/.codex/agents/doc_drafter.toml @@ -0,0 +1,16 @@ +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/Gemini/.codex/agents/doc_researcher.toml b/Writing/Gemini/.codex/agents/doc_researcher.toml new file mode 100644 index 0000000..19bbd46 --- /dev/null +++ b/Writing/Gemini/.codex/agents/doc_researcher.toml @@ -0,0 +1,15 @@ +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/Gemini/.codex/agents/doc_reviewer.toml b/Writing/Gemini/.codex/agents/doc_reviewer.toml new file mode 100644 index 0000000..67d48dd --- /dev/null +++ b/Writing/Gemini/.codex/agents/doc_reviewer.toml @@ -0,0 +1,14 @@ +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/Gemini/.codex/agents/evidence_checker.toml b/Writing/Gemini/.codex/agents/evidence_checker.toml new file mode 100644 index 0000000..513a1e3 --- /dev/null +++ b/Writing/Gemini/.codex/agents/evidence_checker.toml @@ -0,0 +1,14 @@ +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/Gemini/.codex/config.toml b/Writing/Gemini/.codex/config.toml new file mode 100644 index 0000000..a6c03df --- /dev/null +++ b/Writing/Gemini/.codex/config.toml @@ -0,0 +1,10 @@ +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/Gemini/.codex/hooks.json b/Writing/Gemini/.codex/hooks.json new file mode 100644 index 0000000..e9489fb --- /dev/null +++ b/Writing/Gemini/.codex/hooks.json @@ -0,0 +1,30 @@ +{ + "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/Gemini/.codex/hooks/pre_tool_guard.py b/Writing/Gemini/.codex/hooks/pre_tool_guard.py new file mode 100644 index 0000000..e2a139a --- /dev/null +++ b/Writing/Gemini/.codex/hooks/pre_tool_guard.py @@ -0,0 +1,67 @@ +#!/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/Gemini/.codex/hooks/stop_validate.py b/Writing/Gemini/.codex/hooks/stop_validate.py new file mode 100644 index 0000000..8719875 --- /dev/null +++ b/Writing/Gemini/.codex/hooks/stop_validate.py @@ -0,0 +1,39 @@ +#!/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/Gemini/.gitignore b/Writing/Gemini/.gitignore new file mode 100644 index 0000000..ad8994f --- /dev/null +++ b/Writing/Gemini/.gitignore @@ -0,0 +1,14 @@ +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/AGENTS.md b/Writing/Gemini/AGENTS.md new file mode 100644 index 0000000..31f01c5 --- /dev/null +++ b/Writing/Gemini/AGENTS.md @@ -0,0 +1,57 @@ +# 프로젝트: 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/Gemini/README.md b/Writing/Gemini/README.md new file mode 100644 index 0000000..00c7159 --- /dev/null +++ b/Writing/Gemini/README.md @@ -0,0 +1,259 @@ +# 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/Gemini/docs/ADR.md b/Writing/Gemini/docs/ADR.md new file mode 100644 index 0000000..b9e580f --- /dev/null +++ b/Writing/Gemini/docs/ADR.md @@ -0,0 +1,48 @@ +# 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/Gemini/docs/ARCHITECTURE.md b/Writing/Gemini/docs/ARCHITECTURE.md new file mode 100644 index 0000000..3f86a87 --- /dev/null +++ b/Writing/Gemini/docs/ARCHITECTURE.md @@ -0,0 +1,74 @@ +# 문서 작성 하네스 아키텍처 + +## 디렉토리 구조 +```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/Gemini/docs/DraftFeedback.md b/Writing/Gemini/docs/DraftFeedback.md new file mode 100644 index 0000000..06c8544 --- /dev/null +++ b/Writing/Gemini/docs/DraftFeedback.md @@ -0,0 +1,23 @@ +# Draft Feedback + +초안 검토 후 사용자가 피드백을 남기는 파일이다. AI Agent는 이 파일을 읽고 `final/` 문서에 반영한다. + +## 검토 대상 +- `drafts/{파일명}` + +## 전체 판단 +- {예: 방향 승인 / 구조 수정 필요 / 추가 조사 필요 / 톤 변경 필요} + +## 수정 요청 +| 위치 | 요청 | 이유 | +|------|------|------| +| {섹션 또는 파일명} | {수정 요청} | {왜 필요한지} | + +## 추가로 포함할 내용 +- {추가 내용} + +## 제외하거나 줄일 내용 +- {삭제/축소할 내용} + +## 승인 여부 +{예: 초안 방향 승인 / 아직 승인하지 않음} diff --git a/Writing/Gemini/docs/FinalFeedback.md b/Writing/Gemini/docs/FinalFeedback.md new file mode 100644 index 0000000..95c388c --- /dev/null +++ b/Writing/Gemini/docs/FinalFeedback.md @@ -0,0 +1,17 @@ +# Final Feedback + +최종 문서 검토 후 사용자가 승인 또는 추가 수정 요청을 남기는 파일이다. + +## 검토 대상 +- `final/{파일명}` + +## 승인 여부 +{예: 승인 / 수정 후 승인 / 승인하지 않음} + +## 최종 수정 요청 +| 위치 | 요청 | 우선순위 | +|------|------|----------| +| {섹션 또는 파일명} | {수정 요청} | {높음/중간/낮음} | + +## 비고 +- {추가 의견} diff --git a/Writing/Gemini/docs/PRD.md b/Writing/Gemini/docs/PRD.md new file mode 100644 index 0000000..09fe5ee --- /dev/null +++ b/Writing/Gemini/docs/PRD.md @@ -0,0 +1,68 @@ +# 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/Gemini/docs/ResearchNote.md b/Writing/Gemini/docs/ResearchNote.md new file mode 100644 index 0000000..d04849a --- /dev/null +++ b/Writing/Gemini/docs/ResearchNote.md @@ -0,0 +1,54 @@ +# 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 new file mode 100644 index 0000000..d535648 --- /dev/null +++ b/Writing/Gemini/docs/UI_GUIDE.md @@ -0,0 +1,54 @@ +# 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/Gemini/drafts/.gitkeep b/Writing/Gemini/drafts/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Writing/Gemini/drafts/.gitkeep @@ -0,0 +1 @@ + diff --git a/Writing/Gemini/final/.gitkeep b/Writing/Gemini/final/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Writing/Gemini/final/.gitkeep @@ -0,0 +1 @@ + diff --git a/Writing/Gemini/scripts/execute.py b/Writing/Gemini/scripts/execute.py new file mode 100644 index 0000000..15410e3 --- /dev/null +++ b/Writing/Gemini/scripts/execute.py @@ -0,0 +1,426 @@ +#!/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/Gemini/scripts/test_execute.py b/Writing/Gemini/scripts/test_execute.py new file mode 100644 index 0000000..ecb0f2d --- /dev/null +++ b/Writing/Gemini/scripts/test_execute.py @@ -0,0 +1,560 @@ +""" +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/Gemini/scripts/validate_docs.py b/Writing/Gemini/scripts/validate_docs.py new file mode 100644 index 0000000..2bdfba1 --- /dev/null +++ b/Writing/Gemini/scripts/validate_docs.py @@ -0,0 +1,196 @@ +#!/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())