Add naming convention validator
Automated naming standard enforcement: - Validates lowercase-with-hyphens - Checks date formats (YYYY-MM-DD) - Detects spaces/underscores - Photo naming exceptions Per Fresh Claude Review Priority #2. Usage: python3 automation/validate-naming.py [--verbose] Date: 2026-02-16
This commit is contained in:
165
automation/validate-naming.py
Normal file
165
automation/validate-naming.py
Normal file
@@ -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()
|
||||
Reference in New Issue
Block a user