#!/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())