#!/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()