Files
gh-joshuaoliphant-claude-pl…/skills/adw-bootstrap/utils/validator.py
2025-11-30 08:28:42 +08:00

411 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Validation utilities for ADW setup.
Simple checks to ensure ADW infrastructure is correctly installed and functional.
These are mechanical validations that don't require AI reasoning.
"""
import os
import subprocess
import sys
from pathlib import Path
from typing import List, Tuple
def check_claude_installed() -> Tuple[bool, str]:
"""Check if Claude Code CLI is available.
Returns:
Tuple of (success, message)
"""
try:
result = subprocess.run(
["claude", "--version"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
version = result.stdout.strip()
return (True, f"✓ Claude Code CLI installed: {version}")
else:
return (False, "✗ Claude Code CLI not responding correctly")
except FileNotFoundError:
return (False, "✗ Claude Code CLI not found. Install from: https://claude.ai/code")
except subprocess.TimeoutExpired:
return (False, "✗ Claude Code CLI timeout")
except Exception as e:
return (False, f"✗ Error checking Claude Code: {e}")
def check_api_key_configured() -> Tuple[bool, str]:
"""Check if ANTHROPIC_API_KEY is configured (optional).
Returns:
Tuple of (configured, message)
"""
api_key = os.getenv("ANTHROPIC_API_KEY")
if api_key:
# Mask the key for display
masked = api_key[:10] + "..." + api_key[-4:] if len(api_key) > 14 else "***"
return (True, f"✓ API key configured: {masked}")
else:
return (False, " No API key (using subscription mode)")
def validate_directory_structure(project_root: str) -> List[str]:
"""Check that expected ADW directories exist.
Args:
project_root: Path to project root
Returns:
List of validation messages
"""
project_path = Path(project_root)
messages = []
# Required directories
required_dirs = [
"adws",
"adws/adw_modules",
".claude/commands",
"specs"
]
for dir_path in required_dirs:
full_path = project_path / dir_path
if full_path.exists() and full_path.is_dir():
messages.append(f"✓ Directory exists: {dir_path}")
else:
messages.append(f"✗ Directory missing: {dir_path}")
# Optional but expected directories
optional_dirs = [
"agents",
]
for dir_path in optional_dirs:
full_path = project_path / dir_path
if full_path.exists() and full_path.is_dir():
messages.append(f"✓ Directory exists: {dir_path}")
else:
messages.append(f" Directory not created yet: {dir_path} (will be created on first run)")
return messages
def validate_core_files(project_root: str) -> List[str]:
"""Check that core ADW files exist.
Args:
project_root: Path to project root
Returns:
List of validation messages
"""
project_path = Path(project_root)
messages = []
# Core files for minimal setup
core_files = [
"adws/adw_modules/agent.py",
"adws/adw_prompt.py",
".claude/commands/chore.md",
".claude/commands/implement.md",
]
for file_path in core_files:
full_path = project_path / file_path
if full_path.exists() and full_path.is_file():
messages.append(f"✓ File exists: {file_path}")
else:
messages.append(f"✗ File missing: {file_path}")
# Enhanced setup files (optional)
enhanced_files = [
"adws/adw_modules/agent_sdk.py",
"adws/adw_slash_command.py",
"adws/adw_chore_implement.py",
".claude/commands/feature.md",
]
enhanced_count = sum(1 for f in enhanced_files if (project_path / f).exists())
if enhanced_count > 0:
messages.append(f" Enhanced setup detected ({enhanced_count}/{len(enhanced_files)} files)")
for file_path in enhanced_files:
full_path = project_path / file_path
if full_path.exists() and full_path.is_file():
messages.append(f"{file_path}")
return messages
def check_scripts_executable(project_root: str) -> List[str]:
"""Verify ADW scripts have execute permissions.
Args:
project_root: Path to project root
Returns:
List of validation messages
"""
project_path = Path(project_root)
messages = []
# Find all adw_*.py scripts
adws_dir = project_path / "adws"
if not adws_dir.exists():
return ["✗ adws/ directory not found"]
scripts = list(adws_dir.glob("adw_*.py"))
if not scripts:
messages.append(" No ADW scripts found (adw_*.py)")
return messages
for script in scripts:
is_executable = os.access(script, os.X_OK)
if is_executable:
messages.append(f"✓ Executable: {script.name}")
else:
messages.append(f"⚠ Not executable: {script.name} (run: chmod +x {script})")
return messages
def test_prompt_execution(project_root: str, timeout: int = 30) -> Tuple[bool, str]:
"""Try a simple prompt execution to verify setup works.
Args:
project_root: Path to project root
timeout: Maximum seconds to wait
Returns:
Tuple of (success, message)
"""
project_path = Path(project_root)
prompt_script = project_path / "adws" / "adw_prompt.py"
if not prompt_script.exists():
return (False, "✗ adw_prompt.py not found")
try:
# Simple test prompt
result = subprocess.run(
[str(prompt_script), "What is 2 + 2?"],
cwd=project_root,
capture_output=True,
text=True,
timeout=timeout
)
if result.returncode == 0:
return (True, "✓ Test prompt executed successfully")
else:
error_msg = result.stderr[:200] if result.stderr else "Unknown error"
return (False, f"✗ Test prompt failed: {error_msg}")
except subprocess.TimeoutExpired:
return (False, f"✗ Test prompt timeout (>{timeout}s)")
except Exception as e:
return (False, f"✗ Error executing test prompt: {e}")
def validate_output_structure(project_root: str) -> List[str]:
"""Check if output directories are created correctly after execution.
Args:
project_root: Path to project root
Returns:
List of validation messages
"""
project_path = Path(project_root)
agents_dir = project_path / "agents"
if not agents_dir.exists():
return [" No agents/ directory yet (created on first execution)"]
messages = [f"✓ Output directory exists: agents/"]
# Check for any execution directories
execution_dirs = [d for d in agents_dir.iterdir() if d.is_dir()]
if not execution_dirs:
messages.append(" No execution outputs yet")
return messages
messages.append(f" Found {len(execution_dirs)} execution output(s)")
# Check structure of first execution
first_exec = execution_dirs[0]
agent_dirs = [d for d in first_exec.iterdir() if d.is_dir()]
if agent_dirs:
first_agent = agent_dirs[0]
expected_files = [
"cc_raw_output.jsonl",
"cc_raw_output.json",
"cc_final_object.json",
"custom_summary_output.json"
]
for filename in expected_files:
if (first_agent / filename).exists():
messages.append(f" ✓ Output file: {filename}")
else:
messages.append(f" ⚠ Missing output: {filename}")
return messages
def run_full_validation(project_root: str = ".", verbose: bool = True) -> bool:
"""Run complete validation suite.
Args:
project_root: Path to project root
verbose: Print detailed messages
Returns:
True if all critical checks pass
"""
project_root = os.path.abspath(project_root)
if verbose:
print(f"\n{'='*60}")
print(f"ADW Setup Validation")
print(f"{'='*60}")
print(f"Project: {project_root}\n")
all_passed = True
# Check 1: Claude Code CLI
if verbose:
print("1. Claude Code CLI")
print("-" * 60)
success, msg = check_claude_installed()
if verbose:
print(f" {msg}")
if not success:
all_passed = False
# Check 2: API Key (optional)
if verbose:
print("\n2. API Configuration")
print("-" * 60)
configured, msg = check_api_key_configured()
if verbose:
print(f" {msg}")
# Check 3: Directory structure
if verbose:
print("\n3. Directory Structure")
print("-" * 60)
dir_messages = validate_directory_structure(project_root)
for msg in dir_messages:
if verbose:
print(f" {msg}")
if msg.startswith(""):
all_passed = False
# Check 4: Core files
if verbose:
print("\n4. Core Files")
print("-" * 60)
file_messages = validate_core_files(project_root)
for msg in file_messages:
if verbose:
print(f" {msg}")
if msg.startswith(""):
all_passed = False
# Check 5: Script permissions
if verbose:
print("\n5. Script Permissions")
print("-" * 60)
perm_messages = check_scripts_executable(project_root)
for msg in perm_messages:
if verbose:
print(f" {msg}")
if msg.startswith(""):
# Warning but not critical failure
pass
# Check 6: Output structure (informational)
if verbose:
print("\n6. Output Structure")
print("-" * 60)
output_messages = validate_output_structure(project_root)
for msg in output_messages:
if verbose:
print(f" {msg}")
# Summary
if verbose:
print(f"\n{'='*60}")
if all_passed:
print("✓ Validation passed - ADW setup is ready!")
else:
print("✗ Validation failed - see errors above")
print(f"{'='*60}\n")
return all_passed
def main():
"""CLI entry point for validation."""
import argparse
parser = argparse.ArgumentParser(
description="Validate ADW setup",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
"project_root",
nargs="?",
default=".",
help="Project root directory (default: current directory)"
)
parser.add_argument(
"--quiet",
action="store_true",
help="Only show summary"
)
parser.add_argument(
"--test",
action="store_true",
help="Run test prompt execution (may take 30s)"
)
args = parser.parse_args()
# Run validation
passed = run_full_validation(args.project_root, verbose=not args.quiet)
# Optionally test execution
if args.test and passed:
print("\nRunning test prompt execution...")
print("-" * 60)
success, msg = test_prompt_execution(args.project_root)
print(f" {msg}")
if not success:
passed = False
# Exit with appropriate code
sys.exit(0 if passed else 1)
if __name__ == "__main__":
main()