Skip to main content

#!/usr/bin/env python3 """ ADR-118 Migration: Remove Legacy context.db Fallbacks (J.1.4.0.8)

Scans Python files for legacy context.db fallback patterns and reports or optionally removes them.

Usage: python3 scripts/migrations/adr118_remove_legacy_fallbacks.py # Report only python3 scripts/migrations/adr118_remove_legacy_fallbacks.py --fix # Apply fixes python3 scripts/migrations/adr118_remove_legacy_fallbacks.py --file X # Check specific file

Task: J.1.4.0.8 ADR: ADR-118 Four-Tier Database Architecture Created: 2026-02-04 """

import argparse import re import sys from pathlib import Path from typing import List, Tuple, Optional

Add project root to path

SCRIPT_DIR = Path(file).resolve().parent CORE_ROOT = SCRIPT_DIR.parent.parent

Patterns that indicate legacy fallback (problems)

LEGACY_FALLBACK_PATTERNS = [ # Pattern: elif legacy_db.exists() or elif context_db.exists() ( r'elif\s+.*(?:legacy_db|context_db|CONTEXT_DB).exists()', "Legacy fallback to context.db detected" ), # Pattern: if not X then fallback to context.db ( r'(?:fallback|else).*context.db', "Fallback to context.db in error handling" ), # Pattern: Direct path to context.db being used (not in comments) ( r'(?<!#\s)(?<!#)(?<!")(?<!')sqlite3.connect.*context.db', "Direct connection to context.db" ), ]

Patterns that are OK (awareness, not usage)

ACCEPTABLE_PATTERNS = [ r'#.*context.db.*DEPRECATED', r'#.*NO FALLBACK', r'#.*ADR-118', r'"context.db".*DEPRECATED', r''context.db'.*DEPRECATED', ]

def find_python_files(root: Path, exclude_patterns: List[str] = None) -> List[Path]: """Find all Python files, excluding specified patterns.""" exclude_patterns = exclude_patterns or [ 'pycache', '.venv', '.git', 'node_modules', ]

files = []
for py_file in root.rglob('*.py'):
path_str = str(py_file)
if any(pattern in path_str for pattern in exclude_patterns):
continue
files.append(py_file)

return sorted(files)

def check_file_for_legacy_fallbacks(file_path: Path) -> List[Tuple[int, str, str]]: """ Check a file for legacy context.db fallback patterns.

Returns list of (line_number, line_content, issue_description).
"""
issues = []

try:
content = file_path.read_text(encoding='utf-8')
lines = content.split('\n')
except Exception as e:
return [(0, "", f"Could not read file: {e}")]

for line_num, line in enumerate(lines, 1):
# Skip if line matches acceptable patterns
is_acceptable = False
for pattern in ACCEPTABLE_PATTERNS:
if re.search(pattern, line, re.IGNORECASE):
is_acceptable = True
break

if is_acceptable:
continue

# Check for problematic patterns
for pattern, description in LEGACY_FALLBACK_PATTERNS:
if re.search(pattern, line, re.IGNORECASE):
issues.append((line_num, line.strip(), description))
break

return issues

def scan_all_files(root: Path) -> dict: """Scan all Python files for legacy fallback issues.""" results = { 'files_scanned': 0, 'files_with_issues': 0, 'total_issues': 0, 'issues_by_file': {} }

files = find_python_files(root)
results['files_scanned'] = len(files)

for file_path in files:
issues = check_file_for_legacy_fallbacks(file_path)
if issues:
results['files_with_issues'] += 1
results['total_issues'] += len(issues)
results['issues_by_file'][str(file_path)] = issues

return results

def print_report(results: dict): """Print scan results.""" print("=" * 70) print("ADR-118 Legacy Fallback Scan Report") print("=" * 70) print(f"\nFiles scanned: {results['files_scanned']}") print(f"Files with issues: {results['files_with_issues']}") print(f"Total issues: {results['total_issues']}")

if results['issues_by_file']:
print("\n" + "=" * 70)
print("Issues Found:")
print("=" * 70)

for file_path, issues in results['issues_by_file'].items():
rel_path = str(Path(file_path).relative_to(CORE_ROOT))
print(f"\n📁 {rel_path}")
for line_num, line_content, description in issues:
print(f" Line {line_num}: {description}")
print(f" > {line_content[:80]}...")
else:
print("\n✅ No legacy fallback issues found!")

def main(): parser = argparse.ArgumentParser( description="ADR-118 Legacy Fallback Scanner" ) parser.add_argument( '--file', '-f', help="Check a specific file" ) parser.add_argument( '--fix', action='store_true', help="Apply automatic fixes (not yet implemented)" ) parser.add_argument( '--json', action='store_true', help="Output as JSON" )

args = parser.parse_args()

if args.file:
file_path = Path(args.file)
if not file_path.is_absolute():
file_path = CORE_ROOT / file_path

issues = check_file_for_legacy_fallbacks(file_path)
if issues:
print(f"Issues in {file_path}:")
for line_num, line_content, description in issues:
print(f" Line {line_num}: {description}")
print(f" > {line_content}")
else:
print(f"✅ No legacy fallback issues in {file_path}")
else:
results = scan_all_files(CORE_ROOT)

if args.json:
import json
# Convert Path keys to strings for JSON serialization
output = {
'files_scanned': results['files_scanned'],
'files_with_issues': results['files_with_issues'],
'total_issues': results['total_issues'],
'issues': {
str(Path(k).relative_to(CORE_ROOT)): [
{'line': ln, 'content': c, 'issue': i}
for ln, c, i in v
]
for k, v in results['issues_by_file'].items()
}
}
print(json.dumps(output, indent=2))
else:
print_report(results)

if args.fix:
print("\n⚠️ --fix not yet implemented. Manual fixes required.")
print("See: scripts/migrations/README.md for migration guide")

if name == "main": main()