TradingAgents/.claude/hooks/unified_doc_validator.py

554 lines
17 KiB
Python
Executable File

#!/usr/bin/env python3
"""Unified Documentation Validator Hook
Consolidates 12 validation hooks into a single dispatcher:
- validate_project_alignment.py
- validate_claude_alignment.py
- validate_documentation_alignment.py
- validate_docs_consistency.py
- validate_readme_accuracy.py
- validate_readme_sync.py
- validate_readme_with_genai.py
- validate_command_file_ops.py
- validate_commands.py
- validate_hooks_documented.py
- validate_command_frontmatter_flags.py
- validate_manifest_doc_alignment.py (Issue #159)
Usage:
python unified_doc_validator.py
Environment Variables:
UNIFIED_DOC_VALIDATOR=false - Disable entire validator
VALIDATE_PROJECT_ALIGNMENT=false - Disable PROJECT.md validation
VALIDATE_CLAUDE_ALIGNMENT=false - Disable CLAUDE.md validation
VALIDATE_DOC_ALIGNMENT=false - Disable doc alignment checks
VALIDATE_DOCS_CONSISTENCY=false - Disable docs consistency checks
VALIDATE_README_ACCURACY=false - Disable README accuracy checks
VALIDATE_README_SYNC=false - Disable README sync checks
VALIDATE_README_GENAI=false - Disable README GenAI validation
VALIDATE_COMMAND_FILE_OPS=false - Disable command file ops validation
VALIDATE_COMMANDS=false - Disable command validation
VALIDATE_HOOKS_DOCS=false - Disable hooks documentation validation
VALIDATE_COMMAND_FRONTMATTER=false - Disable command frontmatter validation
VALIDATE_MANIFEST_DOC_ALIGNMENT=false - Disable manifest-doc alignment validation
Exit Codes:
0 = All validators passed or skipped
1 = One or more validators failed
"""
import os
import sys
from pathlib import Path
from typing import Callable, Dict, List, Tuple
def get_lib_directory() -> Path:
"""Dynamically discover lib directory (portable across environments)."""
current = Path(__file__).resolve().parent
# Try: hooks/../lib (sibling to hooks)
lib_dir = current.parent / "lib"
if lib_dir.exists():
return lib_dir
# Try: hooks/../../lib (for nested structures)
lib_dir = current.parent.parent / "lib"
if lib_dir.exists():
return lib_dir
# Try: ~/.autonomous-dev/lib (global installation)
global_lib = Path.home() / ".autonomous-dev" / "lib"
if global_lib.exists():
return global_lib
# Fallback: assume current parent has lib
return current.parent / "lib"
def setup_lib_path():
"""Add lib directory to Python path for imports."""
lib_dir = get_lib_directory()
if lib_dir.exists() and str(lib_dir) not in sys.path:
sys.path.insert(0, str(lib_dir))
def is_enabled(env_var: str, default: bool = True) -> bool:
"""Check if validator is enabled via environment variable.
Args:
env_var: Environment variable name to check
default: Default value if env var not set
Returns:
True if enabled, False if disabled
"""
value = os.environ.get(env_var, "").lower()
if value in ("false", "0", "no"):
return False
if value in ("true", "1", "yes"):
return True
return default
def log_result(validator_name: str, status: str, message: str = ""):
"""Log validator result with consistent formatting.
Args:
validator_name: Name of the validator
status: PASS, FAIL, SKIP, or ERROR
message: Optional message to display
"""
status_symbols = {
"PASS": "\u2713", # ✓
"FAIL": "\u2717", # ✗
"SKIP": "-",
"ERROR": "!"
}
symbol = status_symbols.get(status, "?")
status_str = f"[{status}]"
print(f"{symbol} {status_str:8} {validator_name:40} {message}")
class ValidatorDispatcher:
"""Dispatcher for running multiple validators with graceful degradation."""
def __init__(self):
self.validators: List[Tuple[str, str, Callable]] = []
self.results: Dict[str, bool] = {}
def register(self, name: str, env_var: str, validator_func: Callable):
"""Register a validator.
Args:
name: Display name for the validator
env_var: Environment variable to control this validator
validator_func: Function that returns True on pass, False on fail
"""
self.validators.append((name, env_var, validator_func))
def run_all(self) -> bool:
"""Run all registered validators.
Returns:
True if all validators passed or skipped, False if any failed
"""
# Check if entire dispatcher is disabled
if not is_enabled("UNIFIED_DOC_VALIDATOR", default=True):
log_result("Unified Doc Validator", "SKIP", "Disabled via UNIFIED_DOC_VALIDATOR=false")
return True
all_passed = True
for name, env_var, validator_func in self.validators:
# Check if this validator is enabled
if not is_enabled(env_var, default=True):
log_result(name, "SKIP", f"Disabled via {env_var}=false")
self.results[name] = True # Skipped = not a failure
continue
# Run validator with error handling
try:
result = validator_func()
if result:
log_result(name, "PASS")
self.results[name] = True
else:
log_result(name, "FAIL")
self.results[name] = False
all_passed = False
except Exception as e:
log_result(name, "ERROR", f"{type(e).__name__}: {str(e)[:50]}")
self.results[name] = False
all_passed = False
return all_passed
# Validator implementations
def validate_project_alignment() -> bool:
"""Validate PROJECT.md alignment."""
try:
from validate_project_alignment import main
return main() == 0
except ImportError:
# Try direct execution if module import fails
try:
hooks_dir = Path(__file__).parent
validator_path = hooks_dir / "validate_project_alignment.py"
if not validator_path.exists():
return True # Skip if not found
import subprocess
result = subprocess.run(
[sys.executable, str(validator_path)],
capture_output=True,
timeout=30
)
return result.returncode == 0
except Exception:
return True # Graceful skip on error
def validate_claude_alignment() -> bool:
"""Validate CLAUDE.md alignment."""
try:
from validate_claude_alignment import main
return main() == 0
except ImportError:
try:
hooks_dir = Path(__file__).parent
validator_path = hooks_dir / "validate_claude_alignment.py"
if not validator_path.exists():
return True
import subprocess
result = subprocess.run(
[sys.executable, str(validator_path)],
capture_output=True,
timeout=30
)
return result.returncode == 0
except Exception:
return True
def validate_documentation_alignment() -> bool:
"""Validate documentation alignment."""
try:
from validate_documentation_alignment import main
return main() == 0
except ImportError:
try:
hooks_dir = Path(__file__).parent
validator_path = hooks_dir / "validate_documentation_alignment.py"
if not validator_path.exists():
return True
import subprocess
result = subprocess.run(
[sys.executable, str(validator_path)],
capture_output=True,
timeout=30
)
return result.returncode == 0
except Exception:
return True
def validate_docs_consistency() -> bool:
"""Validate docs consistency."""
try:
from validate_docs_consistency import main
return main() == 0
except ImportError:
try:
hooks_dir = Path(__file__).parent
validator_path = hooks_dir / "validate_docs_consistency.py"
if not validator_path.exists():
return True
import subprocess
result = subprocess.run(
[sys.executable, str(validator_path)],
capture_output=True,
timeout=30
)
return result.returncode == 0
except Exception:
return True
def validate_readme_accuracy() -> bool:
"""Validate README accuracy."""
try:
from validate_readme_accuracy import main
return main() == 0
except ImportError:
try:
hooks_dir = Path(__file__).parent
validator_path = hooks_dir / "validate_readme_accuracy.py"
if not validator_path.exists():
return True
import subprocess
result = subprocess.run(
[sys.executable, str(validator_path)],
capture_output=True,
timeout=30
)
return result.returncode == 0
except Exception:
return True
def validate_readme_sync() -> bool:
"""Validate README sync."""
try:
from validate_readme_sync import main
return main() == 0
except ImportError:
try:
hooks_dir = Path(__file__).parent
validator_path = hooks_dir / "validate_readme_sync.py"
if not validator_path.exists():
return True
import subprocess
result = subprocess.run(
[sys.executable, str(validator_path)],
capture_output=True,
timeout=30
)
return result.returncode == 0
except Exception:
return True
def validate_readme_with_genai() -> bool:
"""Validate README with GenAI."""
try:
from validate_readme_with_genai import main
return main() == 0
except ImportError:
try:
hooks_dir = Path(__file__).parent
validator_path = hooks_dir / "validate_readme_with_genai.py"
if not validator_path.exists():
return True
import subprocess
result = subprocess.run(
[sys.executable, str(validator_path)],
capture_output=True,
timeout=30
)
return result.returncode == 0
except Exception:
return True
def validate_command_file_ops() -> bool:
"""Validate command file operations."""
try:
from validate_command_file_ops import main
return main() == 0
except ImportError:
try:
hooks_dir = Path(__file__).parent
validator_path = hooks_dir / "validate_command_file_ops.py"
if not validator_path.exists():
return True
import subprocess
result = subprocess.run(
[sys.executable, str(validator_path)],
capture_output=True,
timeout=30
)
return result.returncode == 0
except Exception:
return True
def validate_commands() -> bool:
"""Validate commands."""
try:
from validate_commands import main
return main() == 0
except ImportError:
try:
hooks_dir = Path(__file__).parent
validator_path = hooks_dir / "validate_commands.py"
if not validator_path.exists():
return True
import subprocess
result = subprocess.run(
[sys.executable, str(validator_path)],
capture_output=True,
timeout=30
)
return result.returncode == 0
except Exception:
return True
def validate_hooks_documented() -> bool:
"""Validate hooks documentation."""
try:
from validate_hooks_documented import main
return main() == 0
except ImportError:
try:
hooks_dir = Path(__file__).parent
validator_path = hooks_dir / "validate_hooks_documented.py"
if not validator_path.exists():
return True
import subprocess
result = subprocess.run(
[sys.executable, str(validator_path)],
capture_output=True,
timeout=30
)
return result.returncode == 0
except Exception:
return True
def validate_command_frontmatter_flags() -> bool:
"""Validate command frontmatter flags."""
try:
from validate_command_frontmatter_flags import main
return main() == 0
except ImportError:
try:
hooks_dir = Path(__file__).parent
validator_path = hooks_dir / "validate_command_frontmatter_flags.py"
if not validator_path.exists():
return True
import subprocess
result = subprocess.run(
[sys.executable, str(validator_path)],
capture_output=True,
timeout=30
)
return result.returncode == 0
except Exception:
return True
def validate_manifest_doc_alignment() -> bool:
"""Validate manifest-documentation alignment (Issue #159).
Ensures CLAUDE.md and PROJECT.md component counts match install_manifest.json.
CRITICAL: This validator fails LOUDLY. No graceful degradation.
If it can't run, it returns False (blocks commit).
"""
try:
from validate_manifest_doc_alignment import main
return main([]) == 0
except ImportError:
lib_dir = get_lib_directory()
validator_path = lib_dir / "validate_manifest_doc_alignment.py"
if not validator_path.exists():
# FAIL LOUD: If validator is missing, that's a problem
print(f"ERROR: Validator not found at {validator_path}")
return False
import subprocess
result = subprocess.run(
[sys.executable, str(validator_path)],
capture_output=True,
timeout=30
)
if result.returncode != 0:
print(result.stdout.decode() if result.stdout else "")
print(result.stderr.decode() if result.stderr else "")
return result.returncode == 0
except Exception as e:
# FAIL LOUD: Any error is a validation failure
print(f"ERROR: Manifest-doc alignment validation failed: {e}")
return False
def main() -> int:
"""Main entry point for unified documentation validator.
Returns:
0 if all validators passed or skipped, 1 if any failed
"""
# Setup lib path for imports
setup_lib_path()
# Create dispatcher
dispatcher = ValidatorDispatcher()
# Register all validators
dispatcher.register(
"PROJECT.md Alignment",
"VALIDATE_PROJECT_ALIGNMENT",
validate_project_alignment
)
dispatcher.register(
"CLAUDE.md Alignment",
"VALIDATE_CLAUDE_ALIGNMENT",
validate_claude_alignment
)
dispatcher.register(
"Documentation Alignment",
"VALIDATE_DOC_ALIGNMENT",
validate_documentation_alignment
)
dispatcher.register(
"Docs Consistency",
"VALIDATE_DOCS_CONSISTENCY",
validate_docs_consistency
)
dispatcher.register(
"README Accuracy",
"VALIDATE_README_ACCURACY",
validate_readme_accuracy
)
dispatcher.register(
"README Sync",
"VALIDATE_README_SYNC",
validate_readme_sync
)
dispatcher.register(
"README GenAI Validation",
"VALIDATE_README_GENAI",
validate_readme_with_genai
)
dispatcher.register(
"Command File Operations",
"VALIDATE_COMMAND_FILE_OPS",
validate_command_file_ops
)
dispatcher.register(
"Commands Validation",
"VALIDATE_COMMANDS",
validate_commands
)
dispatcher.register(
"Hooks Documentation",
"VALIDATE_HOOKS_DOCS",
validate_hooks_documented
)
dispatcher.register(
"Command Frontmatter Flags",
"VALIDATE_COMMAND_FRONTMATTER",
validate_command_frontmatter_flags
)
dispatcher.register(
"Manifest-Doc Alignment",
"VALIDATE_MANIFEST_DOC_ALIGNMENT",
validate_manifest_doc_alignment
)
# Run all validators
print("\n=== Unified Documentation Validator ===\n")
all_passed = dispatcher.run_all()
# Summary
print("\n=== Validation Summary ===")
passed = sum(1 for result in dispatcher.results.values() if result)
total = len(dispatcher.results)
print(f"Passed: {passed}/{total}")
if all_passed:
print("\nAll validators passed or skipped.")
return 0
else:
print("\nOne or more validators failed.")
return 1
if __name__ == "__main__":
sys.exit(main())