235 lines
6.3 KiB
Python
235 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Basic validation for the Gemini CLI Markdown document harness template.
|
|
|
|
This check is intentionally lightweight: it verifies that the template files
|
|
exist and keep the sections that later Harness steps depend on.
|
|
"""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
try:
|
|
import tomllib
|
|
except ModuleNotFoundError: # pragma: no cover - Python < 3.11 compatibility
|
|
tomllib = None
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
REQUIRED_FILES = [
|
|
"README.md",
|
|
"GEMINI.md",
|
|
"docs/PRD.md",
|
|
"docs/ResearchNote.md",
|
|
"docs/DraftFeedback.md",
|
|
"docs/FinalFeedback.md",
|
|
"docs/ARCHITECTURE.md",
|
|
"docs/ADR.md",
|
|
"docs/UI_GUIDE.md",
|
|
".agents/skills/document-harness/SKILL.md",
|
|
".agents/skills/document-harness/references/phase-templates.md",
|
|
".agents/skills/document-review/SKILL.md",
|
|
".gemini/settings.json",
|
|
".gemini/hooks/pre_tool_guard.py",
|
|
".gemini/hooks/validate_docs_after_agent.py",
|
|
".gemini/agents/doc-researcher.md",
|
|
".gemini/agents/doc-drafter.md",
|
|
".gemini/agents/doc-reviewer.md",
|
|
".gemini/agents/evidence-checker.md",
|
|
".gemini/commands/harness/plan.toml",
|
|
".gemini/commands/harness/research.toml",
|
|
".gemini/commands/harness/draft.toml",
|
|
".gemini/commands/harness/final.toml",
|
|
".gemini/commands/harness/review.toml",
|
|
".gemini/commands/harness/status.toml",
|
|
]
|
|
|
|
REQUIRED_DIRS = [
|
|
"docs",
|
|
"scripts",
|
|
".agents",
|
|
".agents/skills",
|
|
".agents/skills/document-harness",
|
|
".agents/skills/document-review",
|
|
".gemini",
|
|
".gemini/commands",
|
|
".gemini/commands/harness",
|
|
".gemini/hooks",
|
|
".gemini/agents",
|
|
]
|
|
|
|
REQUIRED_SECTIONS = {
|
|
"README.md": [
|
|
"## 핵심 아이디어",
|
|
"## Gemini CLI 구성",
|
|
"## 빠른 시작",
|
|
"## 자동 실행 방식",
|
|
"## 피드백 게이트",
|
|
"## Gemini CLI Skills",
|
|
"## Gemini CLI Subagents",
|
|
"## Hooks",
|
|
"## 검증",
|
|
],
|
|
"docs/PRD.md": [
|
|
"## 문서 목적",
|
|
"## 대상 독자",
|
|
"## 최종 산출물",
|
|
"## 문서 개요",
|
|
"## 중요 키워드",
|
|
"## 핵심 질문",
|
|
"## 범위",
|
|
"## 톤과 스타일",
|
|
"## 조사 요구사항",
|
|
"## 사용자 피드백 방식",
|
|
],
|
|
"docs/ResearchNote.md": [
|
|
"## 조사 범위",
|
|
"## 조사 일시",
|
|
"## 검색어",
|
|
"## 핵심 결론",
|
|
"## 출처 목록",
|
|
"## 쟁점과 상반된 주장",
|
|
"## 확인 필요",
|
|
],
|
|
"GEMINI.md": [
|
|
"## 목적",
|
|
"## Gemini CLI 구성",
|
|
"## 기본 산출물",
|
|
"## 문서 작성 규칙",
|
|
"## Gemini CLI 작업 규칙",
|
|
"## 권장 워크플로우",
|
|
"## 명령어",
|
|
],
|
|
"docs/ARCHITECTURE.md": [
|
|
"## 디렉토리 구조",
|
|
"## 데이터 흐름",
|
|
"## Step 설계 패턴",
|
|
"## Gemini CLI 구성 책임",
|
|
"## 상태 관리",
|
|
"## 파일 책임",
|
|
],
|
|
".agents/skills/document-harness/SKILL.md": [
|
|
"# Document Harness Skill",
|
|
"## Operating Rules",
|
|
"## Staged Workflow",
|
|
"## Validation",
|
|
],
|
|
".agents/skills/document-review/SKILL.md": [
|
|
"# Document Review Skill",
|
|
"## Read First",
|
|
"## Review Checklist",
|
|
"## Output Format",
|
|
],
|
|
}
|
|
|
|
REQUIRED_JSON_FILES = [
|
|
".gemini/settings.json",
|
|
]
|
|
|
|
REQUIRED_TOML_FILES = [
|
|
".gemini/commands/harness/plan.toml",
|
|
".gemini/commands/harness/research.toml",
|
|
".gemini/commands/harness/draft.toml",
|
|
".gemini/commands/harness/final.toml",
|
|
".gemini/commands/harness/review.toml",
|
|
".gemini/commands/harness/status.toml",
|
|
]
|
|
|
|
FORBIDDEN_FILES = [
|
|
"AGENTS.md",
|
|
".codex/config.toml",
|
|
".codex/hooks.json",
|
|
".codex/hooks/pre_tool_guard.py",
|
|
".codex/hooks/stop_validate.py",
|
|
".codex/agents/doc_researcher.toml",
|
|
".codex/agents/doc_drafter.toml",
|
|
".codex/agents/doc_reviewer.toml",
|
|
".codex/agents/evidence_checker.toml",
|
|
]
|
|
|
|
|
|
def first_nonempty_line(path: Path) -> str:
|
|
for line in path.read_text(encoding="utf-8").splitlines():
|
|
if line.strip():
|
|
return line.strip()
|
|
return ""
|
|
|
|
|
|
def markdown_file_has_valid_start(path: Path) -> bool:
|
|
first = first_nonempty_line(path)
|
|
if first.startswith("# "):
|
|
return True
|
|
if first == "---" and path.name == "SKILL.md":
|
|
return True
|
|
if first == "---" and path.parent.name == "agents":
|
|
return True
|
|
return False
|
|
|
|
|
|
def main() -> int:
|
|
errors: list[str] = []
|
|
|
|
for rel in REQUIRED_DIRS:
|
|
path = ROOT / rel
|
|
if not path.is_dir():
|
|
errors.append(f"missing directory: {rel}")
|
|
|
|
for rel in REQUIRED_FILES:
|
|
path = ROOT / rel
|
|
if not path.is_file():
|
|
errors.append(f"missing file: {rel}")
|
|
continue
|
|
|
|
if path.suffix == ".md":
|
|
if not markdown_file_has_valid_start(path):
|
|
errors.append(f"markdown file must start with a level-1 heading, Skill frontmatter, or agent frontmatter: {rel}")
|
|
|
|
for rel in FORBIDDEN_FILES:
|
|
path = ROOT / rel
|
|
if path.exists():
|
|
errors.append(f"forbidden legacy file remains: {rel}")
|
|
|
|
for rel, sections in REQUIRED_SECTIONS.items():
|
|
path = ROOT / rel
|
|
if not path.is_file():
|
|
continue
|
|
|
|
text = path.read_text(encoding="utf-8")
|
|
for section in sections:
|
|
if section not in text:
|
|
errors.append(f"missing section in {rel}: {section}")
|
|
|
|
for rel in REQUIRED_JSON_FILES:
|
|
path = ROOT / rel
|
|
if not path.is_file():
|
|
continue
|
|
try:
|
|
json.loads(path.read_text(encoding="utf-8"))
|
|
except json.JSONDecodeError as exc:
|
|
errors.append(f"invalid JSON in {rel}: {exc}")
|
|
|
|
if tomllib is not None:
|
|
for rel in REQUIRED_TOML_FILES:
|
|
path = ROOT / rel
|
|
if not path.is_file():
|
|
continue
|
|
try:
|
|
tomllib.loads(path.read_text(encoding="utf-8"))
|
|
except tomllib.TOMLDecodeError as exc:
|
|
errors.append(f"invalid TOML in {rel}: {exc}")
|
|
|
|
if errors:
|
|
print("Document harness validation failed:")
|
|
for error in errors:
|
|
print(f"- {error}")
|
|
return 1
|
|
|
|
print("Document harness validation passed.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|