Files
The-Ouroboros/scripts/validate_docs_sync.py
agentson 4ca582b418
Some checks failed
Gitea CI / test (pull_request) Failing after 5s
Gitea CI / test (push) Failing after 6s
docs: SSOT 문서 허브 도입 및 동기화 자동 검증 게이트 (#350)
문서 중복·드리프트를 구조적으로 방지하기 위해 SSOT 원칙을 문서 체계에 적용한다.

신규:
- docs/README.md: 문서 라우팅/역할/읽기 순서/SSOT 정의 (상대경로 링크)
- scripts/validate_docs_sync.py: 가변 수치 하드코딩 금지 + 누락 엔드포인트 검사

수정:
- CLAUDE.md: 문서 진입점 추가, SmartScanner 세부 동작 → architecture.md 링크
- README.md: 문서 네비게이션 섹션 추가, 고정 수치/파일별 케이스 수 제거
- docs/commands.md: validate_docs_sync.py 명령 추가; 중복 엔드포인트 2행 제거
- docs/testing.md: 테스트 총량 고정값 → pytest --collect-only -q 동적 확인으로 전환
- docs/ouroboros/82_doc_restructure_plan.md: draft → active, 실행 현황으로 전환
- .gitea/PULL_REQUEST_TEMPLATE.md: Docs Sync 체크리스트 추가
- .gitea/workflows/ci.yml + .github/workflows/ci.yml: validate_docs_sync 단계 추가

검증:
- python3 scripts/validate_docs_sync.py: PASS

Closes #350
2026-03-01 17:07:59 +09:00

79 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""Validate lightweight documentation synchronization rules."""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(".")
def read_text(path: Path, errors: list[str]) -> str:
if not path.exists():
errors.append(f"missing file: {path}")
return ""
return path.read_text(encoding="utf-8")
def main() -> int:
errors: list[str] = []
docs_index = ROOT / "docs" / "README.md"
readme = ROOT / "README.md"
claude = ROOT / "CLAUDE.md"
commands = ROOT / "docs" / "commands.md"
docs_index_text = read_text(docs_index, errors)
readme_text = read_text(readme, errors)
claude_text = read_text(claude, errors)
commands_text = read_text(commands, errors)
if docs_index_text and "Single Source of Truth" not in docs_index_text:
errors.append("docs/README.md: missing 'Single Source of Truth' section")
if readme_text and "docs/README.md" not in readme_text:
errors.append("README.md: missing docs/README.md routing link")
if claude_text and "docs/README.md" not in claude_text:
errors.append("CLAUDE.md: missing docs/README.md routing link")
# Prevent volatile hard-coded scale numbers in summary docs.
volatile_patterns: list[tuple[str, re.Pattern[str]]] = [
("README.md", re.compile(r"\b\d+\s*개 테스트\b")),
("README.md", re.compile(r"\b\d+\s*tests\s+across\b", re.IGNORECASE)),
("README.md", re.compile(r"\(\d+\s*개 API\)")),
("README.md", re.compile(r"\(\d+\s*API endpoints?\)", re.IGNORECASE)),
("CLAUDE.md", re.compile(r"\b\d+\s*tests\s+across\b", re.IGNORECASE)),
("CLAUDE.md", re.compile(r"\(\d+\s*API endpoints?\)", re.IGNORECASE)),
("docs/commands.md", re.compile(r"Run full test suite with coverage\s*\(\d+", re.IGNORECASE)),
]
text_by_name = {
"README.md": readme_text,
"CLAUDE.md": claude_text,
"docs/commands.md": commands_text,
}
for name, pattern in volatile_patterns:
text = text_by_name.get(name, "")
if text and pattern.search(text):
errors.append(f"{name}: contains volatile hard-coded scale text ({pattern.pattern})")
# Command doc should list all dashboard endpoints exposed by app.py.
for endpoint in ("/api/pnl/history", "/api/positions"):
if commands_text and endpoint not in commands_text:
errors.append(f"docs/commands.md: missing dashboard endpoint {endpoint}")
if errors:
print("[FAIL] docs sync validation failed")
for err in errors:
print(f"- {err}")
return 1
print("[OK] docs sync validation passed")
return 0
if __name__ == "__main__":
sys.exit(main())