Created
April 18, 2026 20:46
-
-
Save opencoca/a9e904dea57722c10d75bdc91b272d0c to your computer and use it in GitHub Desktop.
With this UserScript and Violentmonkey of http://violentmonkey.github.io/ you can quickly see at a glance how organic the growth of a github repo likely is.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name GitHub Fork/Star Ratio | |
| // @author OpenCoCa | |
| // @namespace https://github.com/ | |
| // @version 1.0.0 | |
| // @description Shows fork/star ratio inline near the About section. Low ratios signal potential fake stars (CMU StarScout, ICSE 2026). | |
| // @match https://github.com/*/* | |
| // @exclude https://github.com/*/*/** | |
| // @grant none | |
| // @run-at document-idle | |
| // ==/UserScript== | |
| (function () { | |
| "use strict"; | |
| // Thresholds from the CMU paper + AwesomeAgents investigation | |
| // organic baseline ~0.08–0.23 (Flask, LangChain, AutoGPT) | |
| const WARN = 0.05; // "warrants scrutiny" | |
| const CRIT = 0.02; // Shardeum / FreeDomain territory | |
| function inject() { | |
| // Guard: only repo root pages | |
| const parts = location.pathname.split("/").filter(Boolean); | |
| if (parts.length !== 2) return; | |
| if (document.getElementById("gfsr-badge")) return; | |
| const starEl = document.getElementById("repo-stars-counter-star"); | |
| const forkEl = document.getElementById("repo-network-counter"); | |
| if (!starEl || !forkEl) return; | |
| const stars = parseInt(starEl.title.replace(/,/g, ""), 10); | |
| const forks = parseInt(forkEl.title.replace(/,/g, ""), 10); | |
| if (isNaN(stars) || isNaN(forks) || stars < 1) return; | |
| const ratio = forks / stars; | |
| const pct = (ratio * 100).toFixed(1); | |
| // Pick colour + label | |
| let color, label, tip; | |
| if (stars < 300) { | |
| color = "#768390"; label = `${pct}%`; | |
| tip = `Fork/star ratio ${pct}% · not enough stars to tell the story`; | |
| } else if (ratio <= CRIT) { | |
| color = "#f85149"; label = `⚠ ${pct}%`; | |
| tip = `Fork/star ratio ${pct}% — this looks like a fake star signal. Real repos have 8–23%.`; | |
| } else if (ratio <= WARN) { | |
| color = "#d29922"; label = `! ${pct}%`; | |
| tip = `Fork/star ratio ${pct}% — below 5%. Worth a closer look. Organic repos average 8–23%. `; | |
| } else { | |
| color = "#3fb950"; label = `✓ ${pct}%`; | |
| tip = `Fork/star ratio ${pct}% — looks organic. This is the baseline for real repos (8–23%).`; | |
| } | |
| // Build badge | |
| const badge = document.createElement("span"); | |
| badge.id = "gfsr-badge"; | |
| badge.title = tip; | |
| badge.setAttribute("aria-label", tip); | |
| badge.style.cssText = ` | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 4px; | |
| margin-left: 8px; | |
| padding: 1px 7px; | |
| border-radius: 2em; | |
| font-size: 11px; | |
| font-family: ui-monospace, 'SFMono-Regular', monospace; | |
| font-weight: 600; | |
| letter-spacing: 0.02em; | |
| color: ${color}; | |
| background: ${color}18; | |
| border: 1px solid ${color}44; | |
| cursor: default; | |
| vertical-align: middle; | |
| white-space: nowrap; | |
| `; | |
| badge.textContent = `⑂/★ ${label}`; | |
| // Inject: look for the "About" heading in the sidebar | |
| const aboutHeading = Array.from(document.querySelectorAll("h2")) | |
| .find(el => el.textContent.trim() === "About"); | |
| if (aboutHeading) { | |
| aboutHeading.appendChild(badge); | |
| } else { | |
| // Fallback: after the repo description element | |
| const desc = document.querySelector(".f4.my-3") || | |
| document.querySelector('[data-pjax="#repo-content-pjax-container"] p'); | |
| if (desc) desc.insertAdjacentElement("afterend", badge); | |
| } | |
| } | |
| // Run now, then re-run on GitHub's SPA navigations | |
| inject(); | |
| const _push = history.pushState.bind(history); | |
| history.pushState = function (...args) { | |
| _push(...args); | |
| setTimeout(inject, 800); | |
| }; | |
| window.addEventListener("popstate", () => setTimeout(inject, 800)); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment