DEV Community

Stephen Collins
Stephen Collins

Posted on

I Built a CLI to Find the Riskiest Code in Any Repo — Introducing Hotspots

Every team has those files. The ones everyone knows are dangerous. The ones where a "simple" change takes three days of careful testing. The ones that keep showing up in postmortems.

I spent a long time noticing that pattern—the same 10% of a codebase causing 80% of the pain—and wondering why our tooling didn't just show us where that 10% was. ESLint can tell you a function has high cyclomatic complexity. But that doesn't tell you whether it's actively being changed, who's touching it, or whether it's likely to bite you in the next sprint.

So I built Hotspots — a Rust CLI that finds risky code by combining structural complexity with real git activity.


The Core Idea: Complexity × Change

Raw complexity metrics are useful, but they're incomplete. A gnarly function that hasn't been touched in two years isn't your emergency today. The real danger is complexity plus active change — functions that are structurally hard to reason about and are being modified regularly.

Hotspots computes a risk score for every function in your codebase by combining:

  • Structural signals: cyclomatic complexity (CC), nesting depth (ND), fan-out (FO), non-structured exits (NS)
  • Activity signals: git churn in the last 30 days, touch frequency (commit count), recency, call-graph influence

The result is an activity-weighted risk score — a prioritized list of functions that are both dangerous and active. Not functions you should eventually clean up. Functions you should care about this sprint.


Getting Started

Install with a single curl:

curl -fsSL https://raw.githubusercontent.com/Stephen-Collins-tech/hotspots/main/install.sh | sh
Enter fullscreen mode Exit fullscreen mode

Point it at any repo and run a snapshot:

hotspots analyze . --mode snapshot --format text
Enter fullscreen mode Exit fullscreen mode

You'll get a ranked output grouped into risk bands:

Critical (risk ≥ 9.0):
  processPlanUpgrade    src/api/billing.ts:142    risk 12.4  CC 15  ND 4  FO 8

High (6.0 ≤ risk < 9.0):
  validateSession       src/auth/session.ts:67    risk 9.8   CC 11  ND 3  FO 7
  applySchema           src/db/migrations.ts:203  risk 8.1   CC 10  ND 2  FO 5
Enter fullscreen mode Exit fullscreen mode

For a shareable HTML report:

hotspots analyze . --mode snapshot --format html --output report.html
Enter fullscreen mode Exit fullscreen mode

Explain Mode: Why Is This Function Risky?

Once you have the list, you want to know why something ranked high — and what to do about it. Pass --explain-patterns and Hotspots annotates each function with antipattern labels:

hotspots analyze . --mode snapshot --format json --explain-patterns > snapshot.json
Enter fullscreen mode Exit fullscreen mode

In v1.2.0, Hotspots detects two tiers of patterns:

Tier 1 — Structural:
complex_branching, deeply_nested, exit_heavy, long_function, god_function

Tier 2 — Relational & Temporal:
hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god

A function tagged god_function + cyclic_hub is both monolithic and at the center of a dependency cycle — a very different refactoring situation than one tagged exit_heavy + long_function. The labels make the action obvious.


CI Policy Checks: Stop Regressions Before They Merge

Identifying hotspots is useful. Preventing new ones from landing is better. Hotspots has a delta mode that compares the current branch against the baseline and applies configurable policy checks:

hotspots analyze . --mode delta --policy
Enter fullscreen mode Exit fullscreen mode

Policies you can configure:

  • Critical Introduction — fail if a new function lands in the critical band
  • Excessive Regression — fail if a function's risk score jumps by a large delta
  • Rapid Growth — flag unusually fast complexity growth
  • Watch/Attention — warn when functions approach thresholds

Add it to CI and complexity regressions fail the PR before review. Start it in warn-only mode, get the team used to the signal, then flip to blocking when you're ready.

Here's a minimal GitHub Actions step:

- name: Hotspots policy check
  run: hotspots analyze . --mode delta --policy
Enter fullscreen mode Exit fullscreen mode

Exit code 1 if any blocking policy fails. Zero if clean.


hotspots.dev — Automated OSS Analysis

Alongside the CLI, I've been building hotspots.dev — a blog that runs automated Hotspots analyses on trending open-source repos every night.

The pipeline:

  1. A GitHub Actions crawl job selects a fresh trending repo
  2. Hotspots analyzes it and extracts the top functions and antipatterns
  3. An AI draft gets generated and opened as a PR for review
  4. Once merged, it deploys automatically

It's a real-world showcase of what the tool surfaces in popular codebases — eslint, Flowise, and more. The HTML reports are hosted at reports.hotspots.dev so you can drill into the full ranked analysis for any repo.

I find it genuinely interesting to run Hotspots on codebases I use every day and see where the structural risk actually lives. It's rarely where you'd guess.


Try It

# Install
curl -fsSL https://raw.githubusercontent.com/Stephen-Collins-tech/hotspots/main/install.sh | sh

# Snapshot your current repo
hotspots analyze . --mode snapshot --format text

# Full JSON with pattern labels
hotspots analyze . --mode snapshot --format json --explain-patterns > snapshot.json

# CI policy check
hotspots analyze . --mode delta --policy
Enter fullscreen mode Exit fullscreen mode

It's MIT licensed, written in Rust, and works on any language — since it operates on git history and AST-level metrics rather than language-specific rules.

If you've ever looked at a PR and thought "this feels risky but I can't explain why" — run Hotspots. It'll tell you why.


TL;DR:

Complexity metrics alone miss the point. Hotspots combines structural analysis with real git activity to surface the functions that are both hard to change and actively being changed. It's a Rust CLI, it works on any language, and it can gate PRs in CI. Try it.

Top comments (0)