#!/usr/bin/env python3 """ Git Hologram + CAPT Analysis — Visual Overhaul Dark holographic theme with neural-git graph visualization. """ import subprocess, tempfile, shutil, os, hashlib, json, re from datetime import datetime from typing import List, Dict import gradio as gr from capt_backend import CAPTBackend, health_banner_html, BRAINS backend = CAPTBackend() CACHE_DIR = "/tmp/git-hologram-cache" os.makedirs(CACHE_DIR, exist_ok=True) # ── Helpers ──────────────────────────────────────────────────────────── def _repo_key(repo_url: str) -> str: return hashlib.sha256(repo_url.encode()).hexdigest()[:16] def _get_cached(repo_url: str): key = _repo_key(repo_url) meta_path = os.path.join(CACHE_DIR, f"{key}.json") if os.path.exists(meta_path): try: with open(meta_path) as f: meta = json.load(f) repo_path = os.path.join(CACHE_DIR, key) if os.path.isdir(repo_path): return repo_path, meta except Exception: pass return None, None def _set_cached(repo_url: str, repo_path: str, meta: dict): key = _repo_key(repo_url) dest = os.path.join(CACHE_DIR, key) if dest != repo_path and os.path.exists(repo_path): if os.path.exists(dest): shutil.rmtree(dest, ignore_errors=True) shutil.move(repo_path, dest) with open(os.path.join(CACHE_DIR, f"{key}.json"), "w") as f: json.dump(meta, f) return dest def normalize_url(repo_url: str) -> str: repo_url = repo_url.strip() if not repo_url: return "" if repo_url.startswith("http"): return repo_url.replace(".git", "") + ".git" if repo_url.startswith("git@"): return repo_url if "/" not in repo_url: repo_url = f"knowurknot/{repo_url}" return f"https://github.com/{repo_url}.git" def get_git_commits(repo_path: str, max_commits: int = 50) -> List[Dict]: cmd = ["git", "-C", repo_path, "log", f"--max-count={max_commits}", "--pretty=format:%H|%an|%ae|%ad|%s", "--date=iso"] result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) if result.returncode != 0: return [] commits = [] for line in result.stdout.strip().split("\n"): if "|" in line: parts = line.split("|", 4) if len(parts) == 5: commits.append({"hash": parts[0][:8], "author": parts[1], "email": parts[2], "date": parts[3], "message": parts[4]}) return commits def analyze_repo(repo_path: str) -> Dict: stats = {"files": 0, "languages": {}, "size_kb": 0} try: result = subprocess.run(["git", "-C", repo_path, "ls-files"], capture_output=True, text=True, timeout=10) files = [f for f in result.stdout.strip().split("\n") if f] stats["files"] = len(files) for f in files[:500]: ext = f.split(".")[-1].lower() if "." in f else "none" if ext in ["py", "js", "ts", "tsx", "jsx", "rs", "go", "java", "cpp", "c", "h", "rb", "php", "swift", "kt"]: stats["languages"][ext] = stats["languages"].get(ext, 0) + 1 result = subprocess.run(["du", "-sk", repo_path], capture_output=True, text=True, timeout=5) if result.returncode == 0: stats["size_kb"] = int(result.stdout.split()[0]) except Exception: pass return stats # ── Visual Renderers ─────────────────────────────────────────────────── LANGUAGE_COLORS = { "py": "#3b82f6", "js": "#fbbf24", "ts": "#60a5fa", "tsx": "#38bdf8", "jsx": "#fbbf24", "rs": "#f97316", "go": "#06b6d4", "java": "#ef4444", "cpp": "#a855f7", "c": "#6366f1", "h": "#8b5cf6", "rb": "#ef4444", "php": "#8b5cf6", "swift": "#f97316", "kt": "#a855f7", } def get_commit_color(msg: str) -> str: m = msg.lower() if any(w in m for w in ["feat", "feature", "add", "introduce", "implement"]): return "#22d3ee" # cyan if any(w in m for w in ["fix", "bugfix", "patch", "hotfix", "resolve"]): return "#34d399" # emerald if any(w in m for w in ["refactor", "clean", "restructure", "simplify"]): return "#fbbf24" # amber if any(w in m for w in ["test", "spec", "coverage"]): return "#a78bfa" # violet if any(w in m for w in ["doc", "readme", "comment", "guide"]): return "#60a5fa" # blue if any(w in m for w in ["chore", "ci", "build", "deps", "bump"]): return "#94a3b8" # slate if any(w in m for w in ["merge", "pull request", "pr "]): return "#f472b6" # pink return "#64748b" # gray def render_stats(stats: Dict) -> str: if not stats or not stats.get("languages"): return "
No language data
" langs = stats["languages"] total = sum(langs.values()) bars = [] for lang, count in sorted(langs.items(), key=lambda x: -x[1]): pct = count / total * 100 color = LANGUAGE_COLORS.get(lang, "#64748b") bars.append(f'''
{lang} {count} · {pct:.1f}%
''') size_mb = stats.get("size_kb", 0) / 1024 size_str = f"{size_mb:.1f} MB" if size_mb > 1 else f"{stats.get('size_kb', 0)} KB" return f'''
{stats.get('files', 0)}
Files
{size_str}
Size
Languages
{''.join(bars)}
''' def render_commits(commits: List[Dict]) -> str: if not commits: return "

No commits found.

" authors = {} for c in commits: authors[c["author"]] = authors.get(c["author"], 0) + 1 top_author = max(authors, key=authors.get) if authors else "Unknown" # Build a visual git graph rows = [] for i, c in enumerate(commits[:40]): msg = c["message"][:60] + ("..." if len(c["message"]) > 60 else "") date = c["date"][:10] if len(c["date"]) > 10 else c["date"] color = get_commit_color(c["message"]) initials = ''.join(p[0].upper() for p in c["author"].split() if p)[:2] # Alternate branch line offset for visual variety offset = (i % 3) * 8 rows.append(f'''
{initials}
{msg}
{c["hash"]} · {date}
{c["author"].split()[0]}
''') more = f'
+ {len(commits) - 40} more commits
' if len(commits) > 40 else "" return f'''
{len(commits)}
Commits
{len(authors)}
Authors
{top_author.split()[0]}
Top
{''.join(rows)} {more}
''' def render_analysis(result: Dict, analysis_type: str) -> str: if not result.get("success"): err = result.get("error", "Unknown error") return f'''
❌ Analysis Failed
{err}
''' resp = result.get("response", {}) if isinstance(resp, dict): text = resp.get("response") or resp.get("choices", [{}])[0].get("message", {}).get("content", "") else: text = str(resp) if not text or text.strip() == "": text = "*The backend returned an empty response. Try again.*" # Convert markdown to styled HTML text = text.replace("\n\n", "

").replace("\n", "
") text = f"

{text}

" text = re.sub(r'\*\*(.+?)\*\*', r'\1', text) text = re.sub(r'`(.+?)`', r'\1', text) text = re.sub(r'#{3,6}\s*(.+?)(?=<|$)', r'

\1

', text) brain = result.get("brain", "capt").upper() fallback = " · fallback" if result.get("fallback") else "" icons = {"codebase": "🔍", "commits": "📊", "security": "🛡️"} titles = {"codebase": "Codebase Analysis", "commits": "Commit Patterns", "security": "Security Audit"} return f'''
{icons.get(analysis_type, "🧠")} {titles.get(analysis_type, "Analysis")} ● {brain}{fallback}
{text}
''' # ── Core Logic ───────────────────────────────────────────────────────── def fetch_and_render(repo_url: str, max_commits: int): repo_url = normalize_url(repo_url) if not repo_url: return "

Please enter a repository URL.

", "", "" cached_path, meta = _get_cached(repo_url) if cached_path: commits = get_git_commits(cached_path, max_commits) if commits: stats = analyze_repo(cached_path) return render_commits(commits), repo_url, render_stats(stats) tmpdir = tempfile.mkdtemp(prefix="hologram_") try: depth = max(max_commits + 10, 50) result = subprocess.run( ["git", "clone", "--depth", str(depth), "--no-tags", repo_url, tmpdir], capture_output=True, text=True, timeout=60, ) if result.returncode != 0: err = result.stderr[:200] if result.stderr else "Unknown clone error" return f"

❌ Clone failed: {err}

", repo_url, "" commits = get_git_commits(tmpdir, max_commits) if not commits: return "

No commits found.

", repo_url, "" stats = analyze_repo(tmpdir) _set_cached(repo_url, tmpdir, {"url": repo_url, "commits": len(commits), "cached_at": datetime.now().isoformat()}) return render_commits(commits), repo_url, render_stats(stats) except subprocess.TimeoutExpired: return "

⏱️ Clone timed out — repo too large.

", repo_url, "" except Exception as e: return f"

💥 Error: {str(e)[:200]}

", repo_url, "" finally: if os.path.exists(tmpdir) and tmpdir != cached_path: shutil.rmtree(tmpdir, ignore_errors=True) def run_analysis(repo_url: str, analysis_type: str): if not repo_url: return "

Generate a hologram first.

" prompts = { "codebase": f"Analyze the codebase at {repo_url}. Provide: 1) Architecture overview, 2) Tech stack, 3) Code quality assessment, 4) Key files and their roles, 5) Suggestions for improvement. Be thorough but concise.", "commits": f"Analyze the commit history patterns for {repo_url}. What do the commit messages reveal about development velocity, team structure, and code maturity?", "security": f"Perform a security audit of {repo_url}. Check for: 1) Common vulnerabilities, 2) Dependency risks, 3) Secret leakage patterns, 4) Supply chain concerns. Be specific and actionable.", } fallback_prompts = { "codebase": f"Brief codebase overview of {repo_url}", "commits": f"Commit patterns in {repo_url}", "security": f"Security notes for {repo_url}", } prompt = prompts.get(analysis_type, prompts["codebase"]) for attempt, p in enumerate([prompt, fallback_prompts.get(analysis_type, "")], 1): if not p: continue try: result = backend.cogitate(p) if result and result.get("success"): return render_analysis(result, analysis_type) except Exception: pass return '''
⏳ The CAPT brains are warming up
The cognitive backend is experiencing high load or cold-start latency.
Please wait a moment and try again.
Short queries usually work immediately.
''' # ── CSS ──────────────────────────────────────────────────────────────── CUSTOM_CSS = """ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap'); .gradio-container { max-width: 1200px !important; font-family: 'Inter', system-ui, sans-serif !important; } /* Dark holographic background */ .gradio-container { background: linear-gradient(135deg, #0b0f1a 0%, #0f172a 40%, #1a103c 100%) !important; background-attachment: fixed !important; } /* Custom scrollbar */ ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: #334155; border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { background: #475569; } /* Primary button — cyan glow */ .gr-button-primary { background: linear-gradient(135deg, #0891b2, #06b6d4) !important; border: none !important; border-radius: 10px !important; box-shadow: 0 0 20px rgba(6,182,212,0.3), 0 4px 14px rgba(6,182,212,0.2) !important; color: #fff !important; font-weight: 600 !important; letter-spacing: 0.3px !important; transition: all 0.2s ease !important; } .gr-button-primary:hover { box-shadow: 0 0 30px rgba(6,182,212,0.5), 0 6px 20px rgba(6,182,212,0.3) !important; transform: translateY(-1px) !important; } /* Secondary elements */ .gr-box, .gr-form, .gr-panel { background: rgba(15, 23, 42, 0.5) !important; border: 1px solid rgba(51, 65, 85, 0.4) !important; border-radius: 14px !important; backdrop-filter: blur(12px) !important; } /* Inputs */ .gr-input, .gr-textbox textarea, .gr-textbox input { background: rgba(15, 23, 42, 0.6) !important; border: 1px solid rgba(51, 65, 85, 0.5) !important; border-radius: 10px !important; color: #e2e8f0 !important; font-family: 'JetBrains Mono', monospace !important; } .gr-input:focus, .gr-textbox textarea:focus { border-color: #22d3ee !important; box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.1) !important; } /* Labels */ .gr-form .gr-box > label, .gr-input-label { color: #94a3b8 !important; font-size: 11px !important; text-transform: uppercase !important; letter-spacing: 1px !important; font-weight: 600 !important; } /* Dropdown */ .gr-dropdown { background: rgba(15, 23, 42, 0.6) !important; border: 1px solid rgba(51, 65, 85, 0.5) !important; border-radius: 10px !important; color: #e2e8f0 !important; } /* Slider */ .gr-slider input[type="range"] { accent-color: #22d3ee !important; } /* JSON panel */ .gr-json { background: rgba(15, 23, 42, 0.4) !important; border: 1px solid rgba(51, 65, 85, 0.3) !important; border-radius: 12px !important; color: #e2e8f0 !important; font-family: 'JetBrains Mono', monospace !important; font-size: 12px !important; } /* Commit row hover */ .commit-row:hover { background: rgba(34, 211, 238, 0.05) !important; } """ # ── Gradio UI ────────────────────────────────────────────────────────── with gr.Blocks( title="Git Hologram", css=CUSTOM_CSS, theme=gr.themes.Base( primary_hue="cyan", secondary_hue="violet", neutral_hue="slate", ).set( body_background_fill="transparent", body_text_color="#e2e8f0", block_background_fill="rgba(15,23,42,0.5)", block_border_color="rgba(51,65,85,0.4)", block_border_width="1px", block_label_text_color="#94a3b8", input_background_fill="rgba(15,23,42,0.6)", input_border_color="rgba(51,65,85,0.5)", input_placeholder_color="#475569", button_primary_background_fill="linear-gradient(135deg, #0891b2, #06b6d4)", button_primary_text_color="#ffffff", ) ) as demo: # Hero header gr.HTML('''
🔮

Git Hologram

Visualize repository history as a living neural graph. Clone any repo and analyze it through the CAPT cognitive lens.

Git Clone Commit Graph CAPT Analysis
''') # Brain status gr.HTML(health_banner_html(backend)) # Input row with gr.Row(equal_height=True): with gr.Column(scale=4): repo_input = gr.Textbox( label="Repository", placeholder="owner/repo or https://github.com/owner/repo", lines=1, ) with gr.Column(scale=1): max_commits = gr.Slider(minimum=10, maximum=200, value=50, step=10, label="Max Commits") with gr.Column(scale=1): generate_btn = gr.Button("🔮 Generate Hologram", variant="primary", size="lg") # Results with gr.Row(): with gr.Column(scale=3): hologram_output = gr.HTML(label="Commit Neural Graph") with gr.Column(scale=2): stats_output = gr.HTML(label="Repository Telemetry") current_repo = gr.State("") # Analysis section with gr.Row(): with gr.Column(scale=3): analysis_type = gr.Dropdown( choices=[("🔍 Codebase Analysis", "codebase"), ("📊 Commit Patterns", "commits"), ("🛡️ Security Audit", "security")], value="codebase", label="Analysis Type", ) with gr.Column(scale=1): analyze_btn = gr.Button("🧠 Analyze with CAPT", variant="primary") analysis_output = gr.HTML(label="CAPT Analysis") # Footer gr.HTML('''
Git Hologram · Powered by CAPT · 5 Brain Instances
Real-time cognitive analysis through Cloudflare Workers
''') # Event wiring generate_btn.click( fn=fetch_and_render, inputs=[repo_input, max_commits], outputs=[hologram_output, current_repo, stats_output] ) repo_input.submit( fn=fetch_and_render, inputs=[repo_input, max_commits], outputs=[hologram_output, current_repo, stats_output] ) analyze_btn.click( fn=run_analysis, inputs=[current_repo, analysis_type], outputs=analysis_output ) if __name__ == "__main__": demo.launch(show_error=True, server_name="0.0.0.0", server_port=7860)