ShipSleuthShipSleuthPublic GitHub diligence

Methodology

How ShipSleuth turns messy public GitHub activity into an honest DD read.

The goal is not to pretend public GitHub is the whole truth. The goal is to make the visible surface more legible, comparable, and harder to misuse.

What ShipSleuth measures

  • Public, owned GitHub repositories for an org or user
  • Default-branch commits inside the selected date window
  • Public PRs opened or merged inside the selected date window
  • Visible releases published in-window
  • Author-versus-bot-account activity, repo breadth, collaboration, and concentration
  • PR merge velocity and bus-factor concentration from the same data

What it does not know (by default)

  • Private repositories or private deployment activity — unless the user opts in to the private activity supplement
  • Internal engineering velocity outside GitHub.com
  • Whether a hidden org membership maps to an employee or contractor
  • Product quality; it only measures public shipping signal

How the read works

  • Author commits, merged PRs, releases, active contributors, active repos, active days, and lines changed are the primary reads
  • Concentration and bot-heavy patterns are called out so the visible activity shape is harder to misread
  • Confidence and caveats are shown with every result so partial samples stay obvious
  • Trend windows let you compare adjacent periods without pretending the tool knows the whole company
  • "View the Math" on any result shows the exact anchors, interpolation, and population estimates

Why caveats are first-class

  • Commit counts are easy to misuse when presented without context
  • Monorepos, squashes, mirrors, and bots distort the naive story
  • Some companies ship heavily in private and look quiet in public
  • This is a diligence signal, not a verdict

Optional private supplement

ShipSleuth defaults to public-only analysis, but users can optionally enter self-reported private activity — additional commits, PRs, repos, releases, active days, and lines changed — to produce a more complete picture. When supplemented:

Private supplement values are self-reported and not verified by ShipSleuth. They are meant to reduce the structural blind spot of private work, not to replace proper diligence. The analysis always indicates when private data has been included.

Calculator-first

ShipSleuth shows direct metrics with their context rather than a single score, to avoid false precision.

Author commitsVisible commit volume from real accounts (bot accounts excluded) inside the window. For a person, only the commits they wrote; for an organization or a single repo, every human author's.
Merged PRsA cleaner signal of integrated public work than commit count alone.
Contributor breadthHow many visible contributors and repos share the public activity footprint. For a person, the contributor count is everyone committing to their repos.
ReleasesVisible shipping artifacts that suggest something reached a public milestone. For a person, only releases they published.
Active daysDistinct days with at least one push event inside the window.
Lines changedWeekly code adds + deletes from GitHub's statistics. For a person, only the lines GitHub credits to them; for an organization or a single repo, everyone's. Noisy — includes generated code.
Merge velocityMedian hours from PR open to merge for PRs by human authors (for a person, their own PRs) merged in-window.
Bus factorMinimum contributors behind half of the human commits in the analyzed repos.
ConcentrationWhether one repo or one actor dominates the visible public output.
ConfidenceHow trustworthy the visible sample looks after caps, failures, and truncation.

Percentile anchor calibration

Where the threshold numbers come from

When ShipSleuth says “Top ~0.1%” for a metric, it interpolates your value against cutoffs measured from real GH Archive data for windows the same length as yours. Here is where each one comes from. The overall rank's recipe is further down.

Data source

Thresholds are measured from GH Archive, the public record of GitHub's event feed, queried on Google BigQuery. Each fixed-length preset in the analyzer (7D, 30D, QTR, 6M, 1Y, 2Y and 5Y) is measured directly, as its own window of 7, 30, 90, 180, 365, 730 or 1,825 days, and every one of them ends on the same day, May 31, 2025. The longest covers Jun 2, 2020 – May 31, 2025.

Merged pull requests are the one exception: they come from the free ClickHouse Playground mirror of the same archive, over Dec 1, 2025 – Feb 28, 2026, for windows of 7, 30 and 90 days. The merged PRs section below explains why.

Why windows ending May 31, 2025, and not the most recent ones? From June 2025 on, GitHub's public event feed records fewer events even as more accounts appear in it, so each account's activity is undercounted. Ranking against newer data would make everyone look more active than they are, and the feed later collapses outright. The stretch ending May 31, 2025 is the last complete one. Some archive days are missing or hold fewer than half the events of a typical day that month: 19 of the 5-year{" "}window's 1,825 (about 1%); the other windows have none.

Comparing your window against it. This is a dated reference distribution, not a live leaderboard: your activity is ranked against how GitHub's public population behaved in a window of the same length, the same way income percentiles are quoted against the latest published tax year. ShipSleuth ranks the activity it can see for the account you analyze, including private work when you grant access, against that public distribution, because public activity is the only population anyone can measure.

  • Thresholds: GH Archive on BigQuery (githubarchive.day), measured Oct 12, 2026.
  • Merged pull requests: ClickHouse playground mirror of GH Archive (github_events), measured Oct 12, 2026.

Why every window length is measured

ShipSleuth used to measure a single 90-day window and stretch or shrink its cutoffs in proportion to the window you picked. Activity does not grow in step with time, so that was wrong in both directions. The 90-day Top ~10% cutoff is 90 commits. Shrunk to fit a 7-day window it becomes 7, but a 7-day window measured directly puts it at 24, so short windows flattered everyone. Stretched to a year it becomes 365, but a measured year puts it at 148, so long windows undersold everyone. Even the median barely moves: 4 commits in 7 days, 18 in 5 years.

So every preset length is now measured on its own. A length in between, such as year to date or a custom range, blends the two nearest measured lengths, and a window shorter than 7 days shows totals without rankings.

Population baseline

Each window's population is every account with at least one public push in it: ~1.5M accounts in 7 days, ~7.6M (7,573,175) in 90 days and ~36.2M in 5 years. Bot and CI accounts are recognized the same way on both sides of every ranking, by a shared list of names (dependabot, renovate, github-actions and others) and by logins ending in “[bot]”. They are only ~0.1% of the 90-day pool, which leaves ~7.6M human accounts.

The human-only view lowers each window's cutoffs in proportion to its measured bot share. Bots are rare enough that only the 7-day cutoffs move, by at most 1%.

Commits (humanCommits)

Pushes per account in GitHub's public event archive, times 2 as a rough stand-in for commits (commit counts sit in a column too large to read affordably). Measured separately for each window length. The table is the 90-day row; the analyzer uses the row for your window's length.

12Top ~50%6 pushes ×2, about 0.9 a week. The median account.
34Top ~25%17 pushes ×2, about 2.6 a week.
90Top ~10%45 pushes ×2, about 7 a week.
158Top ~5%79 pushes ×2, about 12.3 a week.
474Top ~1%237 pushes ×2, about 36.9 a week. Among the most active public accounts.
2,400Top ~0.1%1,200 pushes ×2, about 187 a week. Often monorepo or many-project work.
21,362Top ~0.01%10,681 pushes ×2, about 1,661 a week. May include automated work credited to a person.

Commits per week. Commits divided by the window's weeks, ranked against the same per-window commit measurement.

Every measured length has its own row. Three of its cutoffs, in commits:

WindowTop ~1%Top ~10%Median
7 days102244
30 days262548
90 days4749012
180 days65211412
1 year90414814
2 years1,22819016
5 years1,63424018

Merged PRs (mergedPullRequests)

Merges per account from Dec 2025 to Feb 2026, the only stretch of the archive that labels merges. The recorded account is whoever performed the merge: the author for self-merged PRs, a maintainer otherwise. Ranked within accounts that merged at least one public PR, for windows up to 90 days. In the 90-day stretch, 908,125 accounts merged at least one public PR. Measuring merges on BigQuery instead would mean scanning the archive's largest column, and a single quarter of it is more than BigQuery's free monthly allowance, so they come from the free mirror. The table is the 90-day row.

2Top ~50%2 merged PRs in 90 days. The median account that merged anything.
4Top ~25%4 merged PRs in 90 days.
9Top ~10%9 merged PRs in 90 days.
15Top ~5%15 merged PRs in 90 days.
45Top ~1%45 merged PRs in 90 days. Among the busiest mergers.
193Top ~0.1%193 merged PRs in 90 days.
840Top ~0.01%840 merged PRs in 90 days. Near the ceiling for public merges.

Profile contributions (profileContributions)

Pushes, pull requests, issues and review comments per non-bot account, a stand-in for GitHub's own contribution count, measured separately for each window length. It is compared with the contribution count on the GitHub profile for the same dates, and ranked among the ~8.1M non-bot accounts with at least one such event in the 90-day window: a wider group than the ~7.6M human pushers, because it also counts accounts that never push but open pull requests or issues or leave review comments. The median is 6; 1 in 10 reaches 49 and 1 in 100 reaches 286. GitHub's own number counts individual commits rather than pushes, so the match is approximate.

Lines changed (linesChanged)

Not recorded in GitHub's public archive, so lines changed are shown without a rank. With no population to compare against, ShipSleuth does not guess one.

Active repos

Distinct repositories each account pushed to, measured separately for each window length. Most accounts push to just one. In the 90-day row, 1 account in 10 pushes to 5 or more and 1 in 100 to 14 or more.

Releases

Releases published per account, ranked within the accounts that published at least one in a window of the same length. In the 90-day window ~267k accounts published a release, against ~7.6M that pushed. Among publishers the median is 2 releases; 1 in 10 publishes 8 or more and 1 in 100, 43 or more.

Active days

Distinct days each account pushed, measured separately for each window length. In the 90-day row the median account pushes on 2 of the 90 days; 1 in 10 on 14 or more and 1 in 100 on 48 or more. Pushing on all 90 ranks Top ~0.02%.

Longest streak

The longest run of consecutive push days inside the window, measured separately for each window length. It used to be an estimate; it is now measured like every other metric. In the 90-day row the median account's longest run is 1 day; 1 in 10 reaches 4 days in a row and 1 in 100 reaches 12.

Contributors

Distinct non-bot accounts pushing to each owner's repositories, ranked within repo owners. Most owners have one contributor: themselves. In the 90-day window that is ~7.3M owners; 1 in 100 has 4 or more contributors, 1 in 1,000 has 12 or more and 1 in 10,000 has 57 or more.

How interpolation works

Your value is placed between the two nearest cutoffs for your window's length. The percentile axis is interpolated in log-space (not linearly) because developer activity follows a power-law distribution: a small number of accounts are orders of magnitude more active than the median, and log-interpolation respects that shape.

A window between two measured lengths blends their cutoffs on a log scale of window length. A 60-day custom range falls between the measured 30- and 90-day windows, so its Top ~10% commit cutoff is 75, between their 54 and 90. Rankings cover windows from 7 days to 5 years; a shorter window shows totals without rankings. The percentile axis stays the same: “Top ~1%” always means Top ~1% of the stated group.

Ties. When several cutoffs share a value, which is common for small whole numbers, the value ranks at the least flattering of them, and every count is ranked from half a step below itself, so a tie never beats the accounts it ties with: pushing on all 7 days of a 7-day{" "}window ranks Top ~1.5%, not Top ~0.01%.

Limitations and honesty

  • Thresholds are measured from real GH Archive data, but GH Archive only captures public GitHub events. Private repo activity is invisible.
  • GH Archive counts pushes, not individual commits. The ×2 multiplier for commits is an approximation; actual commits per push vary by workflow.
  • Merged PRs come from a later stretch (Dec 1, 2025 – Feb 28, 2026) than every other metric, and are ranked only for windows up to 90 days.
  • The reference ends May 31, 2025 on purpose. It is a dated snapshot, not a live census, and activity patterns may have shifted since.
  • Monorepo teams, squash-merge policies, and CI bot patterns can inflate or deflate raw counts.
  • The “Top ~X%” claim means, for a 90-day analysis: “among the ~7.6M accounts with a public push in a 90-day window ending May 31, 2025, we estimate your visible activity places you roughly in the top X%.” Every other length compares against its own window. It is not an exact ranking.
  • You can verify every step: click “View the math” on any analysis result to see your value, the cutoffs for your window, and the exact interpolation.

Overall rank

Alongside each metric’s own percentile, ShipSleuth shows one overall rank, such as Top ~10%. It comes from an activity index built only from what can be measured for every public account: commits per 30 days (39%), active repos (25%), the share of the window's days with at least one commit (23%) and releases (13%). Commits, repos and releases count on a log scale, so each extra one adds a little less, and they stop adding at 250 commits per 30 days, 12 repos and 8 releases.

The index is computed the same way for the account you analyze and for every account in the archive (where commits are the same pushes ×2 estimate), then ranked among the window’s push-active accounts: from ~1.5M in 7 days to ~36.2M in 5 years. The index itself is never shown, only the rank it lands on.

Measured, not estimated:earlier versions could only estimate where this rank’s cutoffs sat. Because the index now uses only parts the archive records, it is computed for every account in the measurement, and its cutoffs are measured for every window length like every other metric’s.

Builder · AI usage methodology

How Builder turns local AI-tool state into a private dashboard.

Builder is the AI agent usage layer of the analyzer, folded into the main page rather than living at a separate URL. It tracks your own AI coding usage across Claude Code, Codex, Gemini CLI, and Cursor. The web page never reaches into your machine. Instead, a local CLI scans on-disk state, emits a JSON blob, and you paste it into the "Add your AI agent usage" section that appears below the analyzer form. The methodology below is the privacy contract that lets us do this honestly.

Adapter pattern with a strict schema gate

Each supported tool has its own adapter under src/lib/ai-usage/adapters/ that knows where the tool stores its state and which fields are safe to read. Every adapter returns the same UsageRecord shape and nothing else. The shape is enforced by a .strict() zod schema, which means any unknown key — a leaked prompt, a stray cwd, a session UUID — causes the record to be rejected before it ever leaves the CLI. The same schema runs again in the browser before the blob is stored.

Day-level bucketing only

The date field is validated as YYYY-MM-DD(UTC). Anything finer — hours, minutes, timezone-local times — fails schema validation. This is a deliberate guard against work-schedule inference from sub-day timestamps. The CLI takes each event's ISO timestamp, truncates to its UTC date, and aggregates from there.

Allowlist columns for SQLite reads

Cursor stores chat history alongside usage metadata in SQLite at state.vscdb. The Cursor adapter unions two read-only sources via better-sqlite3. Source A globs User/workspaceStorage/*/state.vscdb for per-workspace databases and reads the legacy aiService.prompts and aiService.generations keys from each ItemTable. Source B opens the global User/globalStorage/state.vscdb and streams cursorDiskKV rows where the key matches bubbleId:% — this is where Cursor 0.43+ keeps current-format chat data, including per-call inputTokens/outputTokens. From every row the extractor pulls only the UTC day (from unixMs, createdAt, clientRpcSendTime, clientSettleTime, or clientEndTime), one of modelType / model / modelId / modelName / lastUsedModel (capped at 80 chars), and integer token counts. Every other column and JSON property — chat text, images, file paths, workspace names, conversation IDs — is discarded inside the extractor. The CLI is the only place that touches the file, and the strict schema is the second wall: even if the query were widened, the resulting object would be rejected before serialization.

Browser-local persistence, URL-carried sharing

The Builder section reads and writes a single localStorage key. The dashboard you see is rendered entirely from that key. Nothing is uploaded automatically, and there is no background telemetry. The one way Builder data leaves your browser is when you toggle AI agent metrics on the stats card and copy the share link: a trimmed copy of the aggregated stats (top 5 models, top 4 tools, last 30 daily entries) rides inside the URL itself, deflated and base64url-encoded under ?agent=v1.…. No server persists it — the URL is the data. To be honest about it, that URL ends up in the recipient's browser history, in Vercel's default request logs (no persistence beyond standard retention), and in any link-unfurler the recipient pastes it through. If that's uncomfortable for a given dashboard, leave the AI agent toggles off.

What this methodology cannot tell you

  • Token counts come from each tool's own telemetry. We don't re-tokenize anything — if a tool under-reports cached tokens, so do we.
  • Gemini CLI only writes persistent counts when telemetry is enabled in settings.json. Without it, the adapter detects the install but reports zero records.
  • Prompts and sessions are counted by event shape, not by intent. A retry, a continuation, or a tool-result turn can shift those counts in ways the underlying tools don't always document.
  • Day boundaries are UTC. A late-night session that crosses midnight in UTC will split across two day buckets.