diff --git a/automation/validate-naming.py b/automation/validate-naming.py new file mode 100644 index 0000000..18a146f --- /dev/null +++ b/automation/validate-naming.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +""" +Naming Convention Validator for Firefrost Operations Manual + +Validates file and directory naming follows FFG standards: +- lowercase-with-hyphens for files and directories +- UPPERCASE for root-level critical docs +- Proper date formats (YYYY-MM-DD) +- No spaces, underscores (except photos), or special chars + +Usage: + python3 validate-naming.py [--fix] [--verbose] +""" + +import os +import re +import sys +from pathlib import Path +from typing import List, Tuple + +class NamingValidator: + def __init__(self, repo_root: str, fix: bool = False, verbose: bool = False): + self.repo_root = Path(repo_root) + self.fix = fix + self.verbose = verbose + self.violations: List[Tuple[str, str]] = [] + + # Naming rules + self.valid_root_uppercase = [ + 'README.md', + 'DOCUMENT-INDEX.md', + 'SESSION-HANDOFF-PROTOCOL.md', + 'DERP.md', + 'LICENSE', + '.gitignore' + ] + + def is_valid_lowercase_hyphen(self, name: str) -> bool: + """Check if name follows lowercase-with-hyphens convention""" + # Allow: lowercase letters, numbers, hyphens, dots (for extensions) + pattern = r'^[a-z0-9\-\.]+$' + return bool(re.match(pattern, name)) + + def is_valid_date_format(self, name: str) -> bool: + """Check if name contains properly formatted dates""" + # Look for YYYY-MM-DD patterns + date_pattern = r'\d{4}-\d{2}-\d{2}' + dates = re.findall(date_pattern, name) + for date in dates: + year, month, day = date.split('-') + # Basic validation + if not (1900 <= int(year) <= 2100): + return False + if not (1 <= int(month) <= 12): + return False + if not (1 <= int(day) <= 31): + return False + return True + + def check_path(self, path: Path): + """Check a single file or directory path""" + relative_path = path.relative_to(self.repo_root) + parts = relative_path.parts + + # Check each part of the path + for i, part in enumerate(parts): + is_root_level = (i == 0) + is_file = (i == len(parts) - 1 and path.is_file()) + + # Skip hidden files/dirs + if part.startswith('.'): + continue + + # Root level uppercase exceptions + if is_root_level and part in self.valid_root_uppercase: + continue + + # Photos directory exception (allows underscores) + if 'photos' in parts: + # Photos can use YYYY-MM-DD_subject_description_NN.jpg format + if is_file and part.endswith(('.jpg', '.jpeg', '.png', '.gif')): + photo_pattern = r'^\d{4}-\d{2}-\d{2}_[a-z0-9\-_]+\d*\.(jpg|jpeg|png|gif)$' + if re.match(photo_pattern, part): + continue + + # Check for violations + if ' ' in part: + self.violations.append((str(relative_path), "Contains spaces")) + elif part != part.lower() and not (is_root_level and part in self.valid_root_uppercase): + self.violations.append((str(relative_path), "Not lowercase")) + elif '_' in part and 'photos' not in parts: + self.violations.append((str(relative_path), "Contains underscore (use hyphens)")) + elif not self.is_valid_lowercase_hyphen(part) and 'photos' not in parts: + if self.verbose: + print(f" āš ļø {relative_path}: {part}") + + # Check date formats + if not self.is_valid_date_format(part): + self.violations.append((str(relative_path), "Invalid date format")) + + def run(self): + """Run naming validator on all files and directories""" + print("šŸ” Scanning repository structure...") + + for root, dirs, files in os.walk(self.repo_root): + # Skip hidden directories + dirs[:] = [d for d in dirs if not d.startswith('.')] + + root_path = Path(root) + + # Check directories + for dir_name in dirs: + self.check_path(root_path / dir_name) + + # Check files + for file_name in files: + if not file_name.startswith('.'): + self.check_path(root_path / file_name) + + self.report() + + def report(self): + """Generate report of naming violations""" + print("\n" + "="*80) + print("šŸ“Š NAMING CONVENTION REPORT") + print("="*80) + + print(f"\nViolations found: {len(self.violations)}\n") + + if self.violations: + print("āŒ NAMING VIOLATIONS:") + print("-" * 80) + for path, reason in self.violations: + print(f"\nšŸ“„ {path}") + print(f" Issue: {reason}") + print("\n" + "="*80) + print("\nšŸ’” Fix suggestions:") + print(" - Replace spaces with hyphens: 'My File.md' → 'my-file.md'") + print(" - Use lowercase: 'MyFile.md' → 'my-file.md'") + print(" - Replace underscores: 'my_file.md' → 'my-file.md'") + print(" - Date format: YYYY-MM-DD") + print("\n" + "="*80) + sys.exit(1) + else: + print("āœ… All naming conventions followed!") + print("="*80) + sys.exit(0) + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="Validate naming conventions") + parser.add_argument("--fix", action="store_true", help="Attempt to fix violations") + parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output") + parser.add_argument("--repo", default=".", help="Repository root path") + + args = parser.parse_args() + + validator = NamingValidator( + repo_root=args.repo, + fix=args.fix, + verbose=args.verbose + ) + + validator.run()