#!/usr/bin/env python3 """ Validation script for automation tracking issues. This script validates that automation tracking issues follow the standardized format defined in docs/development/automation-tracking.md. Usage: python scripts/validate_automation_tracking.py --repo owner/repo python scripts/validate_automation_tracking.py --title "[AUTO-SESSION] Checkpoint (Cycle 15)" python scripts/validate_automation_tracking.py --validate-all """ import re import argparse import sys from typing import List, Dict, Any, Optional, Tuple # Standard agent prefixes and their expected types AGENT_PREFIXES = { "SESSION": ["Checkpoint"], "IMP-POOL": ["Health Report", "Status Update"], "WATCHDOG": ["System Health", "Alert"], "GROOMER": ["Grooming Report", "Scope Alert"], "LIAISON": ["Status Update", "Human Activity Summary"], } # Title format patterns TRACKING_TITLE_PATTERN = re.compile(r'^\[AUTO-([A-Z-]+)\] (.+) \(Cycle (\d+)\)$') ANNOUNCEMENT_TITLE_PATTERN = re.compile(r'^\[AUTO-([A-Z-]+)\] Announce: (.+)$') def validate_tracking_title(title: str) -> Tuple[bool, str]: """ Validate a tracking issue title format. Returns: (is_valid, error_message) """ # Check for announcement format announce_match = ANNOUNCEMENT_TITLE_PATTERN.match(title) if announce_match: prefix, message = announce_match.groups() if prefix not in AGENT_PREFIXES: known_prefixes = [f"AUTO-{p}" for p in AGENT_PREFIXES.keys()] return False, f"Unknown agent prefix 'AUTO-{prefix}'. Known prefixes: {known_prefixes}" if not message.strip(): return False, "Announcement message cannot be empty" return True, "Valid announcement title" # Check for tracking format track_match = TRACKING_TITLE_PATTERN.match(title) if track_match: prefix, issue_type, cycle_str = track_match.groups() # Validate prefix if prefix not in AGENT_PREFIXES: known_prefixes = [f"AUTO-{p}" for p in AGENT_PREFIXES.keys()] return False, f"Unknown agent prefix 'AUTO-{prefix}'. Known prefixes: {known_prefixes}" # Validate type valid_types = AGENT_PREFIXES[prefix] if issue_type not in valid_types: return False, f"Invalid type '{issue_type}' for prefix 'AUTO-{prefix}'. Valid types: {valid_types}" # Validate cycle number try: cycle_num = int(cycle_str) if cycle_num < 1: return False, "Cycle number must be positive" except ValueError: return False, f"Invalid cycle number '{cycle_str}'" return True, "Valid tracking title" return False, "Title does not match expected format: '[AUTO-] (Cycle )' or '[AUTO-] Announce: '" def validate_automation_tracking_issue(issue_data: Dict[str, Any]) -> List[str]: """ Validate a complete automation tracking issue. Args: issue_data: Dictionary containing issue data (title, labels, etc.) Returns: List of validation errors (empty if valid) """ errors = [] # Validate title title = issue_data.get('title', '') is_valid, message = validate_tracking_title(title) if not is_valid: errors.append(f"Title format error: {message}") # Validate labels labels = issue_data.get('labels', []) label_names = [label.get('name', '') if isinstance(label, dict) else str(label) for label in labels] if "Automation Tracking" not in label_names: errors.append("Missing required 'Automation Tracking' label") # Validate body content (basic checks) body = issue_data.get('body', '') if not body.strip(): errors.append("Issue body cannot be empty") # Check for common required elements in body if '**Automated by CleverAgents Bot**' not in body: errors.append("Missing automation signature in body") return errors def get_tracking_issues_from_repo(owner: str, repo: str) -> List[Dict[str, Any]]: """ Fetch automation tracking issues from repository. Note: This is a stub - in real usage, you would integrate with Forgejo API """ print(f"Note: Repository validation for {owner}/{repo} requires API integration") print("This is a demonstration of the validation logic.") return [] def main(): parser = argparse.ArgumentParser(description='Validate automation tracking issues') parser.add_argument('--title', help='Validate a single title string') parser.add_argument('--repo', help='Validate issues from repository (owner/repo)') parser.add_argument('--validate-all', action='store_true', help='Run comprehensive validation examples') args = parser.parse_args() if args.title: is_valid, message = validate_tracking_title(args.title) print(f"Title: {args.title}") print(f"Result: {'✓ VALID' if is_valid else '✗ INVALID'}") print(f"Message: {message}") return 0 if is_valid else 1 elif args.repo: try: owner, repo_name = args.repo.split('/', 1) issues = get_tracking_issues_from_repo(owner, repo_name) # Validation logic would go here print(f"Repository validation for {args.repo} completed") return 0 except ValueError: print("Error: Repository must be in format 'owner/repo'") return 1 elif args.validate_all: print("Running comprehensive validation examples...") # Test cases test_cases = [ # Valid cases ("[AUTO-SESSION] Checkpoint (Cycle 15)", True), ("[AUTO-IMP-POOL] Health Report (Cycle 42)", True), ("[AUTO-WATCHDOG] System Health (Cycle 8)", True), ("[AUTO-GROOMER] Grooming Report (Cycle 23)", True), ("[AUTO-LIAISON] Status Update (Cycle 67)", True), ("[AUTO-SESSION] Announce: Emergency system restart required", True), # Invalid cases ("[AUTO-UNKNOWN] Test (Cycle 1)", False), # Unknown prefix ("[AUTO-SESSION] Invalid Type (Cycle 1)", False), # Invalid type ("[AUTO-SESSION] Checkpoint (Cycle 0)", False), # Invalid cycle ("[AUTO-SESSION] Checkpoint", False), # Missing cycle ("Regular issue title", False), # Wrong format ("[AUTO-SESSION] Announce:", False), # Empty announcement ] print("\nTitle Validation Test Results:") print("-" * 50) all_passed = True for title, expected_valid in test_cases: is_valid, message = validate_tracking_title(title) status = "✓" if is_valid == expected_valid else "✗" result = "VALID" if is_valid else "INVALID" print(f"{status} {title}") print(f" Expected: {'VALID' if expected_valid else 'INVALID'}, Got: {result}") print(f" Message: {message}") print() if is_valid != expected_valid: all_passed = False print(f"Overall test result: {'✓ ALL PASSED' if all_passed else '✗ SOME FAILED'}") return 0 if all_passed else 1 else: parser.print_help() return 1 if __name__ == '__main__': sys.exit(main())