large-monorepo's benchmark.js carries the highest activity risk — 3 functions to watch first

Three of the top hotspots in vsavkin/large-monorepo live in a single file, benchmark.js, all touched in the last 30 days with no historical defect signal.

Stephen Collins ·
Generated by hotspots · free & open source
Activity Risk3.63Low
Hottest Functionmessage

Run this on your own codebase

Hotspots runs locally in under a minute — no account, no data leaves your machine.

pip
$ pip install hotspots-cli
npm
$ npm install -g @stephencollinstech/hotspots
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Key Points

What is fan-out and why does it matter in large-monorepo?

Fan-out is the count of distinct functions that a given function directly calls. A high fan-out means the function is broadly coupled to other parts of the codebase, so a change to any one of those callees — or a change to the function itself — can have ripple effects that are hard to predict without tracing each dependency. In large-monorepo, the highest fan-out in the top hotspots is 3, found in `spawnSync` in `benchmark.js`, which calls into the `path`, `os`, and `cp` modules. That is low enough to be unproblematic today, but it is a useful baseline: if `benchmark.js` grows and `spawnSync` starts absorbing more responsibilities, fan-out will climb and the function will become harder to test in isolation.

How do I reduce cyclomatic complexity in TypeScript?

The most direct technique is extract-method refactoring: identify individual branches or logical chunks and pull them into named functions with single responsibilities. In TypeScript specifically, complex conditional chains often benefit from early-return guards at the top of a function — this flattens nesting and reduces the number of paths a reader has to track simultaneously. A cyclomatic complexity above 10 is a reasonable threshold to start considering a split; above 30, it warrants immediate attention. For `benchmark.js`, complexity is not currently a problem — but if the script's `message` function (CC 3) or `spawnSync` (CC 1) grow as the benchmark expands to cover more tools, keeping each function's CC below 10 by extracting per-tool logic early will pay off.

Is large-monorepo actively maintained?

Based on this snapshot at commit 264da14, the repository shows a modest but real activity signal: all three top functions in `benchmark.js` — `message` (risk score 3.63), `spawnSync` (risk score 3.23), and `cleanFolders` (risk score 2.03) — were each touched once in the last 30 days, with days_since_changed of 0 for all three. The file has 13 total commits in its history. All three sit in the "watch" quadrant, meaning they carry recent activity but low structural complexity. The broader picture — zero "fire" and zero "debt" functions across 26,368 total — suggests neither intense active development nor accumulating ignored complexity. It reads more like a reference or demonstration repository that receives periodic maintenance rather than a continuously developed production system.

How do I reproduce this analysis?

The Hotspots CLI is available at github.com/hotspots-dev/hotspots. After checking out the repository at the analyzed commit with `git checkout 264da14`, run `hotspots analyze . --mode snapshot --explain-patterns --force` from the repository root to reproduce these results. The same command works on any local git repository without additional configuration.

What does activity-weighted risk mean?

Activity-weighted risk multiplies a function's structural complexity — derived from cyclomatic complexity, nesting depth, and fan-out — by how frequently the function has been touched in recent commits. The underlying intuition is that a structurally complex function nobody has touched in two years poses less near-term regression risk than a moderately complex function being changed every week, because the dormant function is not creating opportunities for bugs to be introduced right now. In large-monorepo, all three top functions score in the low band precisely because their structural complexity is minimal — the activity signal from one recent touch in the last 30 days is the only thing separating them from the 26,365 functions that scored below them.

The most striking finding from this analysis is not one problematic function but one problematic file: benchmark.js contributes all three top hotspots, each touched once in the last 30 days and sitting in the “watch” quadrant — active but structurally low-risk for now. Across 26,368 total functions analyzed, every single one lands in the “low” band, with no critical or high functions identified; the risk picture here is one of early signals rather than urgent fires. I would still start with message in benchmark.js, which carries the highest activity-weighted risk score of 3.63 — modest in absolute terms, but worth understanding before the benchmark script grows further.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top Hotspots

FunctionFileRiskCCNDFO
messagebenchmark.js3.631
spawnSyncbenchmark.js3.213
cleanFoldersbenchmark.js2.01

Large Repo Analysis

large-monorepo is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 5 or more commits in the last 30 days; other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.

Quadrant Distribution

Before getting into individual functions, here is the full triage picture across the repository.

Quadrant distribution across 26,368 functions — all risk is concentrated in watch, with zero fire or debt functions.
Watch3OK26365

26,368 functions analyzed

The shape of this distribution is unusual: 26,365 functions are inactive and structurally simple, and the three that surface are there purely because benchmark.js received a commit recently. There are no “fire” functions combining high complexity with active churn, and no “debt” functions hiding structural complexity under dormancy. The repository’s risk profile is genuinely low at this snapshot — but that makes the concentration in one file easier to see and worth paying attention to.


Top Hotspots

message — benchmark.js

message
benchmark.js
3.63
low
CC 3
ND 0
FO 1
touches/30d 1

message is a simple logging helper that wraps a string in separator lines and writes it to stdout. The source confirms exactly that: a console.log of dashes, then the message, then more dashes. With a cyclomatic complexity of 3, zero nesting depth, and fan-out of just 1, there is nothing structurally alarming here — the CC of 3 reflects the three execution paths implied by its small conditional context in the surrounding script, not internal branching within the function itself.

What makes it the top-ranked function is purely the activity signal: it was touched once in the last 30 days, and benchmark.js has accumulated 13 total commits across its lifetime. That commit history means the file is not frozen — people return to it. The external signals are clean: zero bug-linked commits, zero reverts, zero bug-fix fraction across those 13 commits, which is reassuring. There are also no other contributors recorded in the last 90 days, which suggests the recent commit may have come from a single maintainer working in a short burst rather than ongoing team-wide churn.

My recommendation here is not to refactor message itself — it is doing exactly one thing and doing it simply. The actionable note is to keep an eye on benchmark.js as a whole: if the script is being revisited frequently, it is worth asking whether the benchmark logic should be more formally structured or moved into a proper test harness that gets coverage rather than a standalone script.


spawnSync — benchmark.js

spawnSync
benchmark.js
3.23
low
CC 1
ND 0
FO 3
touches/30d 1

spawnSync is the script’s shell-execution wrapper. Looking at the source, it resolves a binary path through node_modules/.bin with platform detection for Windows (appending .cmd), then delegates to cp.spawnSync with inherited stdio and a custom environment — specifically injecting NX_TASKS_RUNNER_DYNAMIC_OUTPUT and NX_DAEMON flags. Three distinct functions are called: path.join, os.platform, and cp.spawnSync, which is where the fan-out of 3 comes from.

Cyclomatic complexity of 1 means there is effectively a single execution path — the Windows ternary is the only branch, and it is a simple substitution. Nesting depth is zero. This is a well-contained utility function, and the external signals back that up: no bug-linked commits, no reverts across the file’s 13-commit history.

The one thing I would flag is the environment injection pattern. Hardcoding NX_TASKS_RUNNER_DYNAMIC_OUTPUT: 'false' and deriving NX_DAEMON from a module-level flag means any future change to benchmark configuration has to touch this function. If the benchmark script grows — more tools to compare, more configuration knobs — consider pulling the environment object into a separate configuration structure so spawnSync stays a thin wrapper and the configuration lives somewhere more visible.


cleanFolders — benchmark.js

cleanFolders
benchmark.js
2.03
low
CC 1
ND 0
FO 0
touches/30d 1

cleanFolders is the most structurally minimal function in the list: cyclomatic complexity of 1, zero nesting, and a fan-out of 0 — meaning it calls nothing at all right now. The source makes this explicit: the actual cleanup command is entirely commented out, leaving the function as an empty stub that is called in each benchmark loop but does nothing.

This is worth a brief flag, not because of structural risk, but because of intent. The comment inside the function reads “uncomment this to remove all artifacts after every run”, which means benchmark results may currently be affected by build artifact state carrying over between runs. Whether that is intentional (to measure warm-cache behavior) or an oversight is something only the maintainer can answer — but it is the kind of silent behavioral flag that is easy to miss when scanning the script quickly.

With an activity-weighted risk of 2.03 and no historical defect signal on the file, I would not prioritize this for any refactoring. The concrete action is simply to add a comment clarifying whether the disabled cleanup is deliberate, so the next person reading the script does not have to guess.

Reproduce This Analysis

git clone https://github.com/vsavkin/large-monorepo
cd large-monorepo
git checkout 264da142a8696e3647216eb0525c15bd61ab9da8
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 5

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →