At commit b765b8b, worldmonitor has 13,005 analyzed functions — 1,613 of them in the critical band. Every one of the top five hotspots sits in the fire quadrant: all are both structurally complex and actively changing right now. The highest-scoring function, main in scripts/seed-digest-notifications.mjs, carries an activity-weighted risk score of 21.2 with a cyclomatic complexity of 103 and nesting depth of 8, and it was touched twice in the last 30 days. I would start there because any engineer modifying that function today is navigating 103 independent execution paths with no structural guardrails.
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 5 Hotspots
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
main | scripts/seed-digest-notifications.mjs | 21.2 | 103 | 8 | 59 |
processEvent | scripts/notification-relay.cjs | 20.9 | 65 | 9 | 37 |
handler | api/notification-channels.ts | 20.2 | 86 | 7 | 28 |
handleWidgetAgentRequest | scripts/ais-relay.cjs | 20.2 | 80 | 7 | 44 |
drainHeldForUser | scripts/notification-relay.cjs | 19.8 | 37 | 8 | 22 |
Large Repo Analysis
worldmonitor 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.
The distribution across quadrants tells an immediate story: 4,112 functions are in the fire quadrant — structurally complex and actively changing — while zero functions fall into the debt quadrant. There is no dormant complexity here. The risk is live.
13,005 functions analyzed
Every top hotspot carries the same cluster of antipatterns.
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.Deeply Nested×5Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.Exit Heavy×5Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.God Function×5God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×4Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
All five functions are flagged as god functions with complex branching, deep nesting, and multiple early-exit paths. That combination means each function is doing too many things, is hard to test in isolation, and has numerous paths that must all be covered independently to avoid regression.
main — scripts/seed-digest-notifications.mjs
This is the top-ranked function in the repository, and the numbers justify that position. A cyclomatic complexity of 103 means there are 103 independent execution paths through this function — each one a required test case and a potential bug surface. The nesting depth of 8 means some of those paths are buried eight levels deep inside control structures, which makes local reasoning extremely difficult. Fan-out of 59 means this single function directly calls 59 distinct callees — almost any internal change has the potential to ripple somewhere unexpected.
The source excerpt makes the scope tangible. The function fetches digest rules over HTTP with a timeout, filters them through an elaborate DIGEST_ONLY_USER environment variable mechanism — which itself has an expiry-parsing sub-protocol and multiple rejection modes — then fans out to per-user processing with counters and a deferred exit-code gate. The comment inside the code explicitly acknowledges that the complexity budget is under pressure: a sub-routine was extracted specifically to keep the linter score in bounds. That is a signal that the function has already hit a ceiling but has not been structurally decomposed.
Half of the 2 commits touching this file have been bug fixes. With 2 touches in the last 30 days and no days since last change, this function is being modified right now under those conditions.
My recommendation: extract the DIGEST_ONLY_USER parsing and validation into its own tested module (it already has a named helper parseDigestOnlyUser, which is a good start), and break the per-user fan-out loop into a separately callable function. Decomposing those two pieces alone would likely cut the CC substantially and make the remaining control flow in main legible.
processEvent — scripts/notification-relay.cjs
This function is the event dispatch core of worldmonitor’s notification relay. From the source excerpt, it handles at least six distinct event types, applies an importance score gate for RSS alerts, fetches and filters alert rules, performs a batch PRO subscription check, resolves quiet-hour actions, applies deduplication, and routes into a per-channel delivery loop — all within a single function body.
The nesting depth of 9 is the deepest in the top five, and at that level the cognitive load of tracking which conditions are in scope at any given line is genuinely high. The quiet-hours branching alone introduces at least three outcomes (suppress, hold, normal delivery), each with its own early-return path. The file-level signals are the strongest in the dataset: every commit on this file in the measured window was a bug fix (1 of 1 commits). That is not a proof of current defects, but it is context worth taking seriously when the function is also sitting at CC 65 with a nesting depth of 9.
With 1 touch in the last 30 days and no days since last change, this is active work. The most concrete first step is to extract the importance-score gate, the quiet-hours resolution, and the per-channel delivery block into separate, independently testable functions. The event-type dispatch at the top of the function (the early returns for channel_welcome and flush_quiet_held) already gestures toward a dispatch pattern — that could be formalized into a proper routing table to reduce the branching count.
handler — api/notification-channels.ts
This is the Edge API handler for notification channel management. From the excerpt it handles CORS preflight, bearer token validation, entitlement checks, JSON parsing, and then a multi-action POST dispatch that branches across operations including create-pairing-token, set-channel, and at least one URL validation path — all in a single exported async function.
CC 86 in a TypeScript async handler is particularly concerning because TypeScript’s type narrowing adds implicit control flow that cyclomatic complexity metrics do not fully capture. Every if/else branch that narrows a union type is an additional path that needs to be considered, even if it does not increment the counter. The fan-out of 28 includes calls to auth validation, entitlement lookup, CORS helpers, relay forwarding, and exception capture — meaning a change to any of those dependencies has a plausible path back through this handler.
The single commit touching this file was a bug fix, which parallels the pattern in processEvent. The actionable recommendation here is to split the GET and POST branches into separate handler functions and move each POST action (e.g. create-pairing-token, set-channel) into its own handler that accepts a pre-validated request object. That reduces the CC of the top-level handler to the auth/routing skeleton, which is far easier to test and reason about.
handleWidgetAgentRequest — scripts/ais-relay.cjs
This function handles incoming requests to an AI widget agent endpoint. Based on the source excerpt, it performs access control checks, body size enforcement, JSON parsing, tier resolution (basic vs. pro), a secondary PRO key verification gate, rate limiting with separate buckets per tier, injection/jailbreak detection, tier-specific model and token limit selection, and then initiates a streaming Server-Sent Events response — all sequentially, all in one function.
With 44 distinct callees, handleWidgetAgentRequest has the second-highest fan-out in the top five. A god function with this many dependencies is structurally fragile: adding a new tier, a new rate-limit bucket, or a new injection detection rule all require touching the same function body. The 3 touches in the last 30 days — the highest touch count in the top five — make this the most actively modified hotspot in the dataset right now.
This file has the only non-zero review comment density across the top hotspots (2.17 comments per commit on average), suggesting reviewers have flagged concerns here before. Combined with 2 of 3 commits being bug fixes, this function warrants extra scrutiny on any pending PR. The most impactful decomposition is to extract the auth-and-tier resolution into a dedicated function that returns a validated context object, so the streaming logic and model dispatch can operate on clean, pre-validated inputs rather than re-checking conditions inline.
drainHeldForUser — scripts/notification-relay.cjs
This function drains queued notifications for a user whose quiet hours have ended. From the source, it reads from a Redis list key, parses each stored event, builds a summary message, fetches the user’s verified channels, and then iterates through them dispatching to Telegram, Slack, Discord, email, webhook, and web push — each in its own conditional branch with individual error handling and an anyDelivered tracker that gates the final queue deletion.
At CC 37 this is the least complex of the five hotspots, but nesting depth of 8 ties it with main for the deepest nesting in the group. The delivery dispatch is a large if/else if chain across six channel types, each with its own parameter shape — that structure alone accounts for most of the complexity and all of the depth. Every channel type is an exit-heavy branch: if a delivery fails, the function logs and continues; if nothing is delivered, the queue is not cleared. Each of those conditional paths needs test coverage.
Sharing a file with processEvent, this function also carries the same file-level signals: every commit on this file in the measured window was a bug fix. The concrete recommendation is to replace the per-channel dispatch chain with a channel-type registry — a map from channelType to a send function — so adding or modifying a channel type does not require editing the nesting structure of drainHeldForUser itself. That refactoring would bring both the CC and the nesting depth down significantly.
One additional context note: five lower-ranked functions — isDesktopRuntime, getCSSColor, getCachedJson, escapeHtml, and h — are all in the watch quadrant with low structural complexity and a single touch each in the last 30 days. None of them warrant refactoring attention now; they are worth monitoring simply because they are active, but their CC values (all ≤ 6) present no structural risk.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
complex_branching | 5 |
deeply_nested | 5 |
exit_heavy | 5 |
god_function | 5 |
long_function | 4 |
These labels belong to two tiers — 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.
Reproduce This Analysis
git clone https://github.com/koala73/worldmonitor
cd worldmonitor
git checkout b765b8bc545bdc3d398ac474fae4919a35644afc
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 →