TradingAgents/.claude/hooks/validate_command_file_ops.py

235 lines
7.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Validate that slash commands with file operations use Python libraries.
This prevents the "sync doesn't work" bug where commands describe file operations
but rely on Claude interpretation instead of executing Python scripts.
Issue: GitHub #127 - /sync command doesn't execute Python dispatcher
File operations MUST use these libraries:
- sync_dispatcher.py - For sync operations
- copy_system.py - For file copying
- file_discovery.py - For file discovery
Run this as part of CI/CD or pre-commit to catch missing library usage.
"""
import sys
import re
from pathlib import Path
# Patterns that indicate DIRECT file operations (not agent-delegated)
# These patterns suggest the command directly manipulates files
FILE_OP_PATTERNS = [
r'copies\s+\S+\s+to\s+\.claude', # "Copies X to .claude/"
r'syncs\s+\S+\s+to\s+\.claude', # "Syncs X to .claude/"
r'copy\s+from\s+\S+\s+to\s+\.claude', # "Copy from X to .claude/"
r'sync\s+from\s+\S+\s+to\s+\.claude', # "Sync from X to .claude/"
r'plugins/autonomous-dev/\S+[`\s]*→[`\s]*\.claude/', # Direct path mapping (with optional backticks)
r'/commands/[`\s]*→[`\s]*[`]?\.claude/commands/', # Arrow mapping commands
r'/hooks/[`\s]*→[`\s]*[`]?\.claude/hooks/', # Arrow mapping hooks
r'/agents/[`\s]*→[`\s]*[`]?\.claude/agents/', # Arrow mapping agents
r'Copies.*commands.*from', # "Copies latest commands from"
]
# Patterns that indicate proper Python library EXECUTION (not just mentions)
# Must be in a bash block or explicit python execution
LIBRARY_EXECUTION_PATTERNS = [
r'```bash\n[^`]*python[^`]*sync_dispatcher', # Python execution in bash block
r'```bash\n[^`]*python[^`]*copy_system',
r'```bash\n[^`]*python[^`]*file_discovery',
r'```bash\n[^`]*python[^`]*install_orchestrator',
r'python\s+\S*sync_dispatcher\.py', # Direct python execution
r'python\s+\S*copy_system\.py',
r'python\s+\S*file_discovery\.py',
r'python\s+\S*install_orchestrator\.py',
r'python3\s+\S*sync_dispatcher\.py',
r'python3\s+\S*copy_system\.py',
]
# Fallback patterns - less strict, for commands that use agents
# which internally call the libraries
LIBRARY_MENTION_PATTERNS = [
r'sync_dispatcher',
r'copy_system',
r'file_discovery',
r'install_orchestrator',
]
# Commands that are exempt from this check
EXEMPT_COMMANDS = [
'test.md', # Testing, not file ops
'status.md', # Read-only
]
def has_file_operations(content: str) -> bool:
"""Check if content describes file operations."""
content_lower = content.lower()
for pattern in FILE_OP_PATTERNS:
if re.search(pattern, content_lower, re.IGNORECASE):
return True
return False
def uses_python_library_execution(impl_content: str) -> tuple[bool, str]:
"""Check if Implementation section EXECUTES Python libraries (not just mentions).
Returns:
(executes_library, warning_message)
"""
# Check for explicit execution patterns
for pattern in LIBRARY_EXECUTION_PATTERNS:
if re.search(pattern, impl_content, re.IGNORECASE | re.DOTALL):
return True, ""
# Check if it at least mentions the libraries (warning case)
for pattern in LIBRARY_MENTION_PATTERNS:
if re.search(pattern, impl_content, re.IGNORECASE):
return False, (
"Command mentions Python library but doesn't execute it. "
"Add explicit execution: python plugins/autonomous-dev/lib/sync_dispatcher.py"
)
# No library usage at all
return False, (
"Command performs file operations but doesn't use Python libraries. "
"Use sync_dispatcher.py, copy_system.py, or file_discovery.py. See Issue #127."
)
def get_implementation_section(content: str) -> str:
"""Extract the Implementation section from command content."""
match = re.search(r'## Implementation\n(.+?)(?=\n## |\Z)', content, re.DOTALL)
if match:
return match.group(1)
return ""
def validate_command_file_ops(filepath: Path) -> tuple[bool, str]:
"""
Validate a command file EXECUTES Python libraries for file operations.
Returns:
(is_valid, error_message)
"""
# Skip exempt commands
if filepath.name in EXEMPT_COMMANDS:
return True, ""
with open(filepath) as f:
content = f.read()
# Check if command describes file operations
if not has_file_operations(content):
return True, "" # No file operations, skip
# Has file operations - check if it EXECUTES Python libraries
impl_section = get_implementation_section(content)
if not impl_section:
# No implementation section - validate_commands.py handles this
return True, ""
# Check implementation section for Python library EXECUTION
executes, error_msg = uses_python_library_execution(impl_section)
if executes:
return True, ""
return False, error_msg
def main():
"""Validate all commands for proper file operation handling."""
# Find commands directory relative to this script
script_dir = Path(__file__).parent
plugin_dir = script_dir.parent
commands_dir = plugin_dir / "commands"
if not commands_dir.exists():
print(f"Commands directory not found: {commands_dir}")
sys.exit(1)
print("=" * 70)
print("COMMAND FILE OPERATIONS VALIDATION")
print("=" * 70)
print()
print("Checking that file operations use Python libraries...")
print("(sync_dispatcher.py, copy_system.py, file_discovery.py)")
print()
command_files = sorted(commands_dir.glob("*.md"))
if not command_files:
print(f"No command files found in {commands_dir}")
sys.exit(1)
valid = []
invalid = []
skipped = []
for filepath in command_files:
# Skip archive directory
if "archive" in str(filepath):
continue
is_valid, error = validate_command_file_ops(filepath)
if is_valid:
if has_file_operations(open(filepath).read()):
valid.append(filepath.name)
print(f" {filepath.name} - uses Python library")
else:
skipped.append(filepath.name)
else:
invalid.append((filepath.name, error))
print(f" {filepath.name} - MISSING Python library")
print()
print("=" * 70)
print(f"RESULTS: {len(valid)} valid, {len(invalid)} invalid, {len(skipped)} skipped (no file ops)")
print("=" * 70)
if invalid:
print()
print("FAILED COMMANDS:")
print()
for name, error in invalid:
print(f" {name}")
print(f" {error}")
print()
print("TO FIX:")
print()
print(" Commands with file operations MUST use Python libraries:")
print()
print(" 1. For sync operations:")
print(" python plugins/autonomous-dev/lib/sync_dispatcher.py --mode")
print()
print(" 2. For file copying:")
print(" Use copy_system.py or file_discovery.py")
print()
print(" 3. For installation:")
print(" Use install_orchestrator.py")
print()
print(" DO NOT rely on Claude interpretation for file operations!")
print(" See Issue #127 for details.")
print()
sys.exit(1)
print()
print("ALL COMMANDS WITH FILE OPS USE PYTHON LIBRARIES!")
print()
sys.exit(0)
if __name__ == "__main__":
main()