#!/usr/bin/env python3 """ Design visualization for guardrail workflow. Generates ASCII art visualization of pages, components, and API endpoints from the project manifest. Usage: python3 visualize_design.py --manifest project_manifest.json """ import argparse import json import os import sys from pathlib import Path def load_manifest(manifest_path: str) -> dict | None: """Load manifest if it exists.""" if not os.path.exists(manifest_path): return None try: with open(manifest_path) as f: return json.load(f) except (json.JSONDecodeError, IOError): return None def get_status_icon(status: str) -> str: """Get icon for entity status.""" icons = { 'PENDING': '⏳', 'APPROVED': 'βœ…', 'IMPLEMENTED': '🟒', 'IN_PROGRESS': 'πŸ”„', 'REJECTED': '❌', } return icons.get(status, 'β—‹') def visualize_page(page: dict, components: list, indent: str = "") -> list: """Generate ASCII visualization for a page.""" lines = [] name = page.get('name', 'Unknown') status = page.get('status', 'PENDING') file_path = page.get('file_path', '') description = page.get('description', '') icon = get_status_icon(status) # Page header lines.append(f"{indent}β”Œ{'─' * 60}┐") lines.append(f"{indent}β”‚ {icon} PAGE: {name:<50} β”‚") lines.append(f"{indent}β”‚ {' ' * 3}Path: {file_path:<48} β”‚") if description: desc_short = description[:45] + '...' if len(description) > 45 else description lines.append(f"{indent}β”‚ {' ' * 3}Desc: {desc_short:<48} β”‚") lines.append(f"{indent}β”œ{'─' * 60}─") # Find components used by this page page_components = [] page_id = page.get('id', '') for comp in components: deps = comp.get('dependencies', []) used_by = comp.get('used_by', []) if page_id in deps or page_id in used_by or page.get('name', '').lower() in str(comp).lower(): page_components.append(comp) if page_components: lines.append(f"{indent}β”‚ COMPONENTS: β”‚") for comp in page_components: comp_name = comp.get('name', 'Unknown') comp_status = comp.get('status', 'PENDING') comp_icon = get_status_icon(comp_status) lines.append(f"{indent}β”‚ {comp_icon} {comp_name:<53} β”‚") else: lines.append(f"{indent}β”‚ (No components defined yet) β”‚") lines.append(f"{indent}β””{'─' * 60}β”˜") return lines def visualize_component_tree(components: list) -> list: """Generate ASCII tree of components.""" lines = [] if not components: return [" (No components defined)"] lines.append("β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”") lines.append("β”‚ 🧩 COMPONENTS β”‚") lines.append("β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€") for i, comp in enumerate(components): name = comp.get('name', 'Unknown') status = comp.get('status', 'PENDING') file_path = comp.get('file_path', '') icon = get_status_icon(status) is_last = i == len(components) - 1 prefix = "└──" if is_last else "β”œβ”€β”€" lines.append(f"β”‚ {prefix} {icon} {name:<50} β”‚") lines.append(f"β”‚ {' ' if is_last else 'β”‚ '} {file_path:<50} β”‚") lines.append("β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜") return lines def visualize_api_endpoints(endpoints: list) -> list: """Generate ASCII visualization of API endpoints.""" lines = [] if not endpoints: return [] lines.append("β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”") lines.append("β”‚ πŸ”Œ API ENDPOINTS β”‚") lines.append("β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€") for endpoint in endpoints: name = endpoint.get('name', 'Unknown') method = endpoint.get('method', 'GET') path = endpoint.get('path', endpoint.get('file_path', '')) status = endpoint.get('status', 'PENDING') icon = get_status_icon(status) method_colors = { 'GET': '🟒', 'POST': '🟑', 'PUT': '🟠', 'PATCH': '🟠', 'DELETE': 'πŸ”΄', } method_icon = method_colors.get(method.upper(), 'βšͺ') lines.append(f"β”‚ {icon} {method_icon} {method.upper():<6} {name:<45} β”‚") lines.append(f"β”‚ Path: {path:<47} β”‚") lines.append("β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜") return lines def visualize_page_flow(pages: list) -> list: """Generate ASCII flow diagram of pages.""" lines = [] if not pages: return [] lines.append("") lines.append("πŸ“± PAGE FLOW DIAGRAM") lines.append("═" * 65) lines.append("") # Simple flow visualization for i, page in enumerate(pages): name = page.get('name', 'Unknown') status = page.get('status', 'PENDING') icon = get_status_icon(status) # Page box box_width = max(len(name) + 4, 20) lines.append(f" β”Œ{'─' * box_width}┐") lines.append(f" β”‚ {icon} {name.center(box_width - 4)} β”‚") lines.append(f" β””{'─' * box_width}β”˜") # Arrow to next page (if not last) if i < len(pages) - 1: lines.append(f" {'β”‚'.center(box_width + 4)}") lines.append(f" {'β–Ό'.center(box_width + 4)}") lines.append("") return lines def visualize_data_flow(manifest: dict) -> list: """Generate data flow visualization.""" lines = [] pages = manifest.get('entities', {}).get('pages', []) components = manifest.get('entities', {}).get('components', []) endpoints = manifest.get('entities', {}).get('api_endpoints', []) if not any([pages, components, endpoints]): return [] lines.append("") lines.append("πŸ”„ DATA FLOW ARCHITECTURE") lines.append("═" * 65) lines.append("") lines.append(" β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”") lines.append(" β”‚ FRONTEND β”‚") lines.append(" β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚") lines.append(" β”‚ β”‚ Pages │───▢│ Components │───▢│ Hooks β”‚ β”‚") page_count = len(pages) comp_count = len(components) lines.append(f" β”‚ β”‚ ({page_count:^3}) β”‚ β”‚ ({comp_count:^3}) β”‚ β”‚ (state) β”‚ β”‚") lines.append(" β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚") lines.append(" β”‚ β”‚ β”‚") lines.append(" β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜") lines.append(" β”‚") lines.append(" β–Ό") lines.append(" β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”") lines.append(" β”‚ BACKEND β”‚") lines.append(" β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚") lines.append(" β”‚ β”‚ API Endpoints β”‚ β”‚") api_count = len(endpoints) lines.append(f" β”‚ β”‚ ({api_count:^3}) β”‚ β”‚") lines.append(" β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚") lines.append(" β”‚ β”‚ β”‚") lines.append(" β”‚ β–Ό β”‚") lines.append(" β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚") lines.append(" β”‚ β”‚ Database β”‚ β”‚") lines.append(" β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚") lines.append(" β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜") return lines def generate_full_visualization(manifest: dict) -> str: """Generate complete design visualization.""" lines = [] project_name = manifest.get('project', {}).get('name', 'Unknown Project') entities = manifest.get('entities', {}) pages = entities.get('pages', []) components = entities.get('components', []) api_endpoints = entities.get('api_endpoints', []) # Header lines.append("") lines.append("╔═══════════════════════════════════════════════════════════════╗") lines.append("β•‘ πŸ“ DESIGN VISUALIZATION β•‘") lines.append(f"β•‘ Project: {project_name:<51} β•‘") lines.append("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•") lines.append("") # Summary counts lines.append("πŸ“Š ENTITY SUMMARY") lines.append("─" * 65) lines.append(f" Pages: {len(pages):>3} β”‚ Components: {len(components):>3} β”‚ API Endpoints: {len(api_endpoints):>3}") lines.append("") # Page Flow if pages: lines.extend(visualize_page_flow(pages)) lines.append("") # Detailed Pages with Components if pages: lines.append("") lines.append("πŸ“„ PAGE DETAILS") lines.append("═" * 65) for page in pages: lines.extend(visualize_page(page, components)) lines.append("") # Component Tree if components: lines.append("") lines.append("🧩 COMPONENT HIERARCHY") lines.append("═" * 65) lines.extend(visualize_component_tree(components)) lines.append("") # API Endpoints if api_endpoints: lines.append("") lines.append("πŸ”Œ API LAYER") lines.append("═" * 65) lines.extend(visualize_api_endpoints(api_endpoints)) lines.append("") # Data Flow Architecture lines.extend(visualize_data_flow(manifest)) lines.append("") # Legend lines.append("") lines.append("πŸ“‹ LEGEND") lines.append("─" * 65) lines.append(" ⏳ PENDING - Designed, awaiting approval") lines.append(" βœ… APPROVED - Approved, ready for implementation") lines.append(" πŸ”„ IN_PROGRESS - Currently being implemented") lines.append(" 🟒 IMPLEMENTED - Implementation complete") lines.append("") return "\n".join(lines) def main(): parser = argparse.ArgumentParser(description="Visualize design from manifest") parser.add_argument("--manifest", required=True, help="Path to project_manifest.json") parser.add_argument("--format", choices=['full', 'pages', 'components', 'api', 'flow'], default='full', help="Visualization format") args = parser.parse_args() manifest = load_manifest(args.manifest) if manifest is None: print("❌ Error: Could not load manifest from", args.manifest) return 1 entities = manifest.get('entities', {}) if not any([ entities.get('pages'), entities.get('components'), entities.get('api_endpoints') ]): print("⚠️ No entities found in manifest. Design phase may not be complete.") return 0 if args.format == 'full': print(generate_full_visualization(manifest)) elif args.format == 'pages': pages = entities.get('pages', []) components = entities.get('components', []) for page in pages: print("\n".join(visualize_page(page, components))) elif args.format == 'components': components = entities.get('components', []) print("\n".join(visualize_component_tree(components))) elif args.format == 'api': endpoints = entities.get('api_endpoints', []) print("\n".join(visualize_api_endpoints(endpoints))) elif args.format == 'flow': pages = entities.get('pages', []) print("\n".join(visualize_page_flow(pages))) return 0 if __name__ == "__main__": sys.exit(main())