Skip to main content

#!/usr/bin/env python3 """ Validate lowercase naming migration.

This script checks:

  1. No remaining uppercase files (except exceptions)
  2. All references are valid (no broken links)
  3. Git status is clean

Usage: python3 scripts/lowercase-migration/validate-migration.py python3 scripts/lowercase-migration/validate-migration.py --fix """

import os import re import sys from pathlib import Path from collections import defaultdict

Exception files that SHOULD be uppercase

EXCEPTIONS = { 'README.md', 'CLAUDE.md', 'SKILL.md', 'LICENSE', 'LICENSE.md', 'CHANGELOG.md', 'CONTRIBUTING.md', 'CODE_OF_CONDUCT.md', 'CODITECT.md', 'Makefile', 'Dockerfile', 'Cargo.toml', 'Cargo.lock', 'package.json', 'package-lock.json', 'pyproject.toml', 'setup.py', 'go.mod', 'go.sum', }

SKIP_DIRS = { '.git', '.venv', 'venv', 'node_modules', 'pycache', '.mypy_cache', '.pytest_cache', 'target', 'dist', 'build', }

PRESERVE_PATTERNS = [ r'^ISO-IEC\d+', r'^RFC\d+', ]

def has_uppercase(name: str) -> bool: """Check if a name contains uppercase letters.""" return bool(re.search(r'[A-Z]', name))

def is_exception(name: str) -> bool: """Check if a name is in the exceptions list.""" return name in EXCEPTIONS

def should_preserve(name: str) -> bool: """Check if a name matches external standard patterns.""" for pattern in PRESERVE_PATTERNS: if re.match(pattern, name): return True return False

def check_uppercase_violations(root_path: Path) -> list: """Find all files/directories that still have uppercase names.""" violations = []

for dirpath, dirnames, filenames in os.walk(root_path):
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]

rel_dirpath = Path(dirpath).relative_to(root_path)

# Check directories
for dirname in dirnames:
if has_uppercase(dirname) and not should_preserve(dirname):
violations.append({
'type': 'directory',
'path': str(rel_dirpath / dirname),
'name': dirname,
})

# Check files
for filename in filenames:
if has_uppercase(filename) and not is_exception(filename) and not should_preserve(filename):
violations.append({
'type': 'file',
'path': str(rel_dirpath / filename),
'name': filename,
})

return violations

def check_broken_links(root_path: Path) -> list: """Find broken markdown links.""" broken_links = [] link_pattern = re.compile(r'[([^]]*)](([^)]+))')

for dirpath, dirnames, filenames in os.walk(root_path):
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]

for filename in filenames:
if not filename.endswith('.md'):
continue

file_path = Path(dirpath) / filename
rel_file_path = file_path.relative_to(root_path)

try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
except Exception:
continue

for match in link_pattern.finditer(content):
link_text = match.group(1)
link_target = match.group(2)

# Skip external links and anchors
if link_target.startswith(('http://', 'https://', '#', 'mailto:')):
continue

# Remove anchor from target
if '#' in link_target:
link_target = link_target.split('#')[0]

if not link_target:
continue

# Resolve relative path
if link_target.startswith('/'):
target_path = root_path / link_target[1:]
else:
target_path = file_path.parent / link_target

target_path = target_path.resolve()

# Check if target exists
if not target_path.exists():
broken_links.append({
'file': str(rel_file_path),
'link_text': link_text,
'target': link_target,
'line': content[:match.start()].count('\n') + 1,
})

return broken_links

def main(): # Find root path script_path = Path(file).resolve() root_path = script_path.parent.parent.parent

if not (root_path / 'CLAUDE.md').exists():
print(f"Error: Could not find coditect-core root at {root_path}")
sys.exit(1)

print("=" * 70)
print(" LOWERCASE NAMING MIGRATION VALIDATOR")
print("=" * 70)
print(f"\nRoot: {root_path}")

all_passed = True

# Check 1: Uppercase violations
print("\n" + "-" * 70)
print(" CHECK 1: Uppercase Violations")
print("-" * 70)

violations = check_uppercase_violations(root_path)

if violations:
all_passed = False
dir_violations = [v for v in violations if v['type'] == 'directory']
file_violations = [v for v in violations if v['type'] == 'file']

print(f"\n[FAIL] Found {len(violations)} uppercase violations:")
print(f" - Directories: {len(dir_violations)}")
print(f" - Files: {len(file_violations)}")

if len(violations) <= 20:
print("\nViolations:")
for v in violations[:20]:
print(f" [{v['type'].upper()}] {v['path']}")
else:
print(f"\n(Showing first 20 of {len(violations)} violations)")
for v in violations[:20]:
print(f" [{v['type'].upper()}] {v['path']}")

# Save violations to file
output_dir = root_path / 'context-storage' / 'lowercase-migration'
output_dir.mkdir(parents=True, exist_ok=True)
with open(output_dir / 'violations.txt', 'w') as f:
for v in violations:
f.write(f"{v['type']}: {v['path']}\n")
print(f"\nFull list saved to: {output_dir / 'violations.txt'}")
else:
print("\n[PASS] No uppercase violations found (exceptions preserved)")

# Check 2: Broken links
print("\n" + "-" * 70)
print(" CHECK 2: Broken Links")
print("-" * 70)

broken_links = check_broken_links(root_path)

if broken_links:
# Only fail if there are many broken links (some may be expected)
if len(broken_links) > 50:
all_passed = False
print(f"\n[FAIL] Found {len(broken_links)} broken links")
else:
print(f"\n[WARN] Found {len(broken_links)} potentially broken links")

if len(broken_links) <= 10:
print("\nBroken links:")
for link in broken_links[:10]:
print(f" {link['file']}:{link['line']} -> {link['target']}")
else:
print(f"\n(Showing first 10 of {len(broken_links)} broken links)")
for link in broken_links[:10]:
print(f" {link['file']}:{link['line']} -> {link['target']}")

# Save broken links to file
output_dir = root_path / 'context-storage' / 'lowercase-migration'
output_dir.mkdir(parents=True, exist_ok=True)
with open(output_dir / 'broken-links.txt', 'w') as f:
for link in broken_links:
f.write(f"{link['file']}:{link['line']} -> {link['target']}\n")
print(f"\nFull list saved to: {output_dir / 'broken-links.txt'}")
else:
print("\n[PASS] No broken links found")

# Summary
print("\n" + "=" * 70)
print(" VALIDATION SUMMARY")
print("=" * 70)

if all_passed:
print("\n[SUCCESS] All validation checks passed!")
print("\nThe lowercase naming migration is complete.")
else:
print("\n[INCOMPLETE] Some validation checks failed.")
print("\nNext steps:")
print(" 1. Run: python3 scripts/lowercase-migration/orchestrate-migration.py")
print(" 2. Re-run this validator")

sys.exit(0 if all_passed else 1)

if name == 'main': main()