#!/usr/bin/env python3 """Verify implementation matches manifest specifications.""" import argparse import json import os import re import sys from dataclasses import dataclass from typing import Optional @dataclass class VerificationResult: entity_id: str entity_type: str file_path: str exists: bool issues: list[str] warnings: list[str] def load_manifest(manifest_path: str) -> dict: """Load manifest from file.""" with open(manifest_path) as f: return json.load(f) def check_file_exists(project_root: str, file_path: str) -> bool: """Check if implementation file exists.""" full_path = os.path.join(project_root, file_path) return os.path.exists(full_path) def read_file_content(project_root: str, file_path: str) -> Optional[str]: """Read file content if it exists.""" full_path = os.path.join(project_root, file_path) if not os.path.exists(full_path): return None with open(full_path, 'r') as f: return f.read() def verify_component(project_root: str, component: dict) -> VerificationResult: """Verify a component implementation matches manifest.""" issues = [] warnings = [] file_path = component.get("file_path", "") exists = check_file_exists(project_root, file_path) if not exists: issues.append(f"File not found: {file_path}") return VerificationResult( entity_id=component.get("id", "unknown"), entity_type="component", file_path=file_path, exists=False, issues=issues, warnings=warnings ) content = read_file_content(project_root, file_path) if not content: issues.append("Could not read file content") return VerificationResult( entity_id=component.get("id", "unknown"), entity_type="component", file_path=file_path, exists=True, issues=issues, warnings=warnings ) # Check component name exists name = component.get("name", "") if name: # Check for function/const declaration or export patterns = [ rf"export\s+(const|function)\s+{name}", rf"(const|function)\s+{name}", rf"export\s+\{{\s*{name}\s*\}}", ] found = any(re.search(p, content) for p in patterns) if not found: issues.append(f"Component '{name}' not found in file") # Check props interface props = component.get("props", {}) if props: # Check if props interface exists interface_pattern = rf"interface\s+{name}Props" if not re.search(interface_pattern, content): warnings.append(f"Props interface '{name}Props' not found") # Check each prop exists in the file for prop_name, prop_spec in props.items(): if prop_name not in content: warnings.append(f"Prop '{prop_name}' may not be implemented") return VerificationResult( entity_id=component.get("id", "unknown"), entity_type="component", file_path=file_path, exists=True, issues=issues, warnings=warnings ) def verify_page(project_root: str, page: dict) -> VerificationResult: """Verify a page implementation matches manifest.""" issues = [] warnings = [] file_path = page.get("file_path", "") exists = check_file_exists(project_root, file_path) if not exists: issues.append(f"File not found: {file_path}") return VerificationResult( entity_id=page.get("id", "unknown"), entity_type="page", file_path=file_path, exists=False, issues=issues, warnings=warnings ) content = read_file_content(project_root, file_path) if not content: issues.append("Could not read file content") return VerificationResult( entity_id=page.get("id", "unknown"), entity_type="page", file_path=file_path, exists=True, issues=issues, warnings=warnings ) # Check for default export (Next.js page requirement) if "export default" not in content: issues.append("Missing 'export default' (required for Next.js pages)") # Check component dependencies components = page.get("components", []) for comp_id in components: # Extract component name from ID (e.g., comp_header -> Header) comp_name = comp_id.replace("comp_", "").title().replace("_", "") if comp_name not in content: warnings.append(f"Component '{comp_name}' (from {comp_id}) may not be used") return VerificationResult( entity_id=page.get("id", "unknown"), entity_type="page", file_path=file_path, exists=True, issues=issues, warnings=warnings ) def verify_api_endpoint(project_root: str, endpoint: dict) -> VerificationResult: """Verify an API endpoint implementation matches manifest.""" issues = [] warnings = [] file_path = endpoint.get("file_path", "") exists = check_file_exists(project_root, file_path) if not exists: issues.append(f"File not found: {file_path}") return VerificationResult( entity_id=endpoint.get("id", "unknown"), entity_type="api_endpoint", file_path=file_path, exists=False, issues=issues, warnings=warnings ) content = read_file_content(project_root, file_path) if not content: issues.append("Could not read file content") return VerificationResult( entity_id=endpoint.get("id", "unknown"), entity_type="api_endpoint", file_path=file_path, exists=True, issues=issues, warnings=warnings ) # Check HTTP method handler exists method = endpoint.get("method", "").upper() method_patterns = [ rf"export\s+async\s+function\s+{method}\s*\(", rf"export\s+function\s+{method}\s*\(", rf"export\s+const\s+{method}\s*=", ] found = any(re.search(p, content) for p in method_patterns) if not found: issues.append(f"HTTP method handler '{method}' not found") # Check request body params if defined request = endpoint.get("request", {}) if request.get("body"): for param in request["body"].keys(): if param not in content: warnings.append(f"Request param '{param}' may not be handled") return VerificationResult( entity_id=endpoint.get("id", "unknown"), entity_type="api_endpoint", file_path=file_path, exists=True, issues=issues, warnings=warnings ) def verify_database_table(project_root: str, table: dict) -> VerificationResult: """Verify a database table implementation matches manifest.""" issues = [] warnings = [] file_path = table.get("file_path", "") exists = check_file_exists(project_root, file_path) if not exists: issues.append(f"File not found: {file_path}") return VerificationResult( entity_id=table.get("id", "unknown"), entity_type="database_table", file_path=file_path, exists=False, issues=issues, warnings=warnings ) content = read_file_content(project_root, file_path) if not content: issues.append("Could not read file content") return VerificationResult( entity_id=table.get("id", "unknown"), entity_type="database_table", file_path=file_path, exists=True, issues=issues, warnings=warnings ) # Check columns/fields are defined columns = table.get("columns", {}) for col_name in columns.keys(): if col_name not in content: warnings.append(f"Column '{col_name}' may not be defined") # Check for CRUD operations crud_ops = ["create", "get", "update", "delete", "find", "all"] found_ops = [op for op in crud_ops if op.lower() in content.lower()] if len(found_ops) < 2: warnings.append("May be missing CRUD operations") return VerificationResult( entity_id=table.get("id", "unknown"), entity_type="database_table", file_path=file_path, exists=True, issues=issues, warnings=warnings ) def print_result(result: VerificationResult, verbose: bool = False): """Print verification result.""" status = "✅" if result.exists and not result.issues else "❌" print(f"{status} [{result.entity_type}] {result.entity_id}") print(f" File: {result.file_path}") if result.issues: for issue in result.issues: print(f" ❌ ERROR: {issue}") if verbose and result.warnings: for warning in result.warnings: print(f" ⚠️ WARN: {warning}") def main(): parser = argparse.ArgumentParser(description="Verify implementation against manifest") parser.add_argument("--manifest", default="project_manifest.json", help="Path to manifest") parser.add_argument("--project-root", default=".", help="Project root directory") parser.add_argument("--verbose", "-v", action="store_true", help="Show warnings") parser.add_argument("--json", action="store_true", help="Output as JSON") args = parser.parse_args() manifest_path = args.manifest if not os.path.isabs(manifest_path): manifest_path = os.path.join(args.project_root, manifest_path) if not os.path.exists(manifest_path): print(f"Error: Manifest not found at {manifest_path}") return 1 manifest = load_manifest(manifest_path) entities = manifest.get("entities", {}) results = [] # Verify components for component in entities.get("components", []): result = verify_component(args.project_root, component) results.append(result) # Verify pages for page in entities.get("pages", []): result = verify_page(args.project_root, page) results.append(result) # Verify API endpoints for endpoint in entities.get("api_endpoints", []): result = verify_api_endpoint(args.project_root, endpoint) results.append(result) # Verify database tables for table in entities.get("database_tables", []): result = verify_database_table(args.project_root, table) results.append(result) # Output results if args.json: output = { "total": len(results), "passed": sum(1 for r in results if r.exists and not r.issues), "failed": sum(1 for r in results if not r.exists or r.issues), "results": [ { "entity_id": r.entity_id, "entity_type": r.entity_type, "file_path": r.file_path, "exists": r.exists, "issues": r.issues, "warnings": r.warnings, } for r in results ] } print(json.dumps(output, indent=2)) else: print("\n" + "=" * 60) print("IMPLEMENTATION VERIFICATION REPORT") print("=" * 60 + "\n") for result in results: print_result(result, args.verbose) print() # Summary passed = sum(1 for r in results if r.exists and not r.issues) failed = sum(1 for r in results if not r.exists or r.issues) warnings = sum(len(r.warnings) for r in results) print("=" * 60) print(f"SUMMARY: {passed}/{len(results)} passed, {failed} failed, {warnings} warnings") print("=" * 60) if failed > 0: return 1 return 0 if __name__ == "__main__": exit(main())