""" Hugging Face Space — Agent Entropy Scanner demo Accepts a GitHub repository URL, clones shallowly, runs the npm-published scanner, returns the citation entropy report. Backed by @n50/agent-entropy-scanner@0.1.0-alpha.0 https://github.com/Ilya0527/alef-pattern-catalog """ import gradio as gr import subprocess import tempfile import json import os import re import shutil REPO_URL_RE = re.compile(r"^https?://(www\.)?github\.com/([^/\s]+)/([^/\s#?.]+)/?$") def scan_repo(repo_url: str) -> tuple[str, dict]: """Clone the given GitHub repo (shallow) and run the scanner.""" if not repo_url or not repo_url.strip(): return "Please paste a GitHub repository URL.", {} m = REPO_URL_RE.match(repo_url.strip()) if not m: return ( "URL must be in the form `https://github.com/owner/repo` (no trailing path).", {}, ) owner = m.group(2) repo = m.group(3) with tempfile.TemporaryDirectory() as tmpdir: target = os.path.join(tmpdir, repo) try: subprocess.run( ["git", "clone", "--depth", "1", f"https://github.com/{owner}/{repo}", target], check=True, capture_output=True, timeout=60, ) except subprocess.CalledProcessError as e: return f"Clone failed: {e.stderr.decode()[:300]}", {} except subprocess.TimeoutExpired: return "Clone timed out (>60s). The repo might be too large for the free Space tier.", {} try: result = subprocess.run( ["npx", "--yes", "@n50/agent-entropy-scanner", target, "--json"], capture_output=True, text=True, timeout=120, ) except subprocess.TimeoutExpired: return "Scan timed out (>120s). The repo might be too large for the free Space tier.", {} if result.returncode != 0 and result.returncode != 1: return f"Scanner errored: {result.stderr[:500]}", {} try: report = json.loads(result.stdout) except json.JSONDecodeError: return f"Scanner returned non-JSON output: {result.stdout[:300]}", {} if report.get("verdict") == "NO_SOURCE_FILES_FOUND": return ( f"No scannable source files found in `{owner}/{repo}`. This usually means the repo doesn't have an `agents/`, `tools/`, or `skills/` directory and lacks generic source files — common in non-software repos.", report, ) mode = report.get("scan_mode", "?") files = report.get("agent_files_total", "?") no_cite = report.get("no_citation_count", "?") entropy = report.get("citation_entropy_pct", "?") duration = report.get("duration_ms", 0) top_dead = "\n".join(f" - {f}" for f in (report.get("top_no_citation") or [])[:5]) top_alive = "\n".join( f" - {a['file']} · {a['citations']} references" for a in (report.get("top_well_cited") or [])[:5] ) bar_count = min(50, entropy // 2) if isinstance(entropy, int) else 25 bar = "█" * bar_count + "░" * (50 - bar_count) interpretation = ( "healthy — agents are well-connected" if isinstance(entropy, int) and entropy < 30 else "moderate technical debt" if isinstance(entropy, int) and entropy < 60 else "high entropy — many 'dead' or undocumented agent files" ) summary = f"""## Result for `{owner}/{repo}` **Scan mode:** {mode} **Source files:** {files} **No-citation files:** {no_cite} **Citation entropy:** **{entropy}%** ``` {bar} ``` **Interpretation:** {interpretation} ### Top no-citation files (candidates for review) {top_dead or " (none)"} ### Top well-cited (architectural anchors) {top_alive or " (none)"} --- *Methodology: every agent / source file is counted as "no-citation" if no other file in the repo references its stem via import/require/spawn/from. The scanner is mechanical — dynamic imports and plugin loaders are not detected, so the number is an upper bound on dead code.* *Read more at [n50.io/patterns](https://n50.io/patterns) · [catalog repo](https://github.com/Ilya0527/alef-pattern-catalog) · CC-BY-4.0* """ return summary, report with gr.Blocks(title="Agent Entropy Scanner") as demo: gr.Markdown("""# Agent Entropy Scanner Paste any public GitHub repository URL. The scanner clones it shallowly and computes **citation entropy** — the percentage of source files with no downstream references in the same repo. The metric was developed for multi-agent codebases. Across a pilot survey of N=10 such repositories, the range was 49%-99% (median 70%). Specifications + caveats at the [catalog repo](https://github.com/Ilya0527/alef-pattern-catalog). This Space is a demo of `@n50/agent-entropy-scanner@0.1.0-alpha.0` (MIT). To use locally: `npx @n50/agent-entropy-scanner /path/to/repo`. """) with gr.Row(): url_input = gr.Textbox( label="GitHub repo URL", placeholder="https://github.com/microsoft/autogen", scale=4, ) scan_btn = gr.Button("Scan", variant="primary", scale=1) output_md = gr.Markdown() output_json = gr.JSON(label="Raw scanner output", visible=False) scan_btn.click(scan_repo, inputs=[url_input], outputs=[output_md, output_json]) gr.Examples( examples=[ ["https://github.com/microsoft/autogen"], ["https://github.com/langchain-ai/langgraph"], ["https://github.com/crewAIInc/crewAI"], ["https://github.com/huggingface/smolagents"], ], inputs=[url_input], ) gr.Markdown(""" --- **About the catalog.** The [ALEF Pattern Catalog](https://n50.io/patterns) (v2.5.0-alpha, 41 patterns) documents named failure modes in agentic-AI systems with explicit falsifiability clocks on every claim. CC-BY-4.0. **Submit instances.** If you find a `PAT-XXX` instance while exploring your own repo, post it at the [Discussion thread](https://github.com/Ilya0527/alef-pattern-catalog/discussions/1). **Cite as.** *ALEF Pattern Catalog. Agent Entropy Scanner v0.1.* `n50.io/patterns`. CC-BY-4.0. """) if __name__ == "__main__": demo.launch()