worldmonitor's notification layer carries the highest activity risk — 5 functions to address first

Five god-functions in worldmonitor's notification scripts and API layer score critical activity-weighted risk, all in the fire quadrant — structurally complex and actively changing as of the latest commit.

Stephen Collins ·
Generated by hotspots · free & open source
Activity Risk21.2Low
Hottest Functionmain

Antipatterns Detected

complex_branching5deeply_nested5exit_heavy5god_function5long_function4

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 a god function and why does it matter in worldmonitor?

A god function is a single function that takes on so many distinct responsibilities that it becomes the de facto coordination point for a large slice of system behavior. In concrete terms it means the function has high cyclomatic complexity, calls a large number of distinct dependencies (fan-out), and grows longer every time a new requirement is added — because there is no clear seam to add it elsewhere. In worldmonitor, all five top hotspots are flagged as god functions: `main` in the digest script calls 59 distinct functions, and `handleWidgetAgentRequest` calls 44. Functions at that scale are hard to test in isolation because exercising any single behavior requires satisfying the full initialization surface of the function. They are also high blast-radius change targets: modifying one responsibility risks accidentally affecting an unrelated path several levels away in the same body.

How do I reduce cyclomatic complexity in TypeScript?

The most effective technique is extract-method refactoring: identify a cohesive cluster of branches inside the function that share a clear purpose, give that cluster a name, and move it into its own function with a well-typed signature. In TypeScript, introducing a discriminated union for the possible outcomes of a sub-process — rather than checking conditions inline — often collapses multiple branches into a single type-narrowed path. A CC above 15 is a reasonable threshold to start extracting; CC above 30 warrants immediate attention, and CC above 50 (as in all five of worldmonitor's top hotspots) makes the function nearly impossible to fully test. A concrete first step today: take `processEvent` in `scripts/notification-relay.cjs` (CC 65) and extract the quiet-hours resolution block — `resolveQuietAction` is already a named call, so promoting its surrounding conditional handling into a separate async function would visibly reduce the CC of `processEvent` and make the hold/suppress/deliver paths independently testable.

Is worldmonitor actively maintained?

Yes, and the data makes that clear. All five top hotspots are in the fire quadrant, meaning they combine high structural complexity with recent commit activity. `handleWidgetAgentRequest` in `scripts/ais-relay.cjs` was touched 3 times in the last 30 days; `main` in `scripts/seed-digest-notifications.mjs` was touched 2 times; the remaining three functions — `processEvent`, `handler`, and `drainHeldForUser` — each received 1 touch, all with `days_since_changed` at 0. The entire repository shows 4,112 functions in the fire quadrant and zero in the debt quadrant, which means all of the structurally complex code is in files that are receiving active changes. Active development and high structural complexity are not mutually exclusive, and in worldmonitor's case they are co-located in the most critical parts of the notification pipeline.

How do I reproduce this analysis?

The Hotspots CLI is available at github.com/hotspots-dev/hotspots. To reproduce this exact analysis, check out the repository at commit b765b8b with `git checkout b765b8b`, then run `hotspots analyze . --mode snapshot --explain-patterns --force` from the repository root. The same command works on any local git repository without additional configuration — no `.hotspotsrc.json` is required to get started.

What does activity-weighted risk mean?

Activity-weighted risk multiplies a function's structural complexity — derived from its cyclomatic complexity, maximum nesting depth, and fan-out — by a signal from recent commit frequency. The result is that a function with extreme structural complexity but no recent changes scores lower than a moderately complex function that is being actively modified, because the near-term regression probability is lower when no one is editing the code. For example, `main` in `scripts/seed-digest-notifications.mjs` has a cyclomatic complexity of 103 and was touched twice in the last 30 days, producing an activity-weighted risk score of 21.2 — the highest in the repository. That score means the structural risk is being realized right now, not just sitting as a cleanup backlog item. This prioritization helps focus refactoring effort where it reduces the probability of shipping a bug today.

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

FunctionFileRiskCCNDFO
mainscripts/seed-digest-notifications.mjs21.2103859
processEventscripts/notification-relay.cjs20.965937
handlerapi/notification-channels.ts20.286728
handleWidgetAgentRequestscripts/ais-relay.cjs20.280744
drainHeldForUserscripts/notification-relay.cjs19.837822

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.

Triage Band Distribution
Fire4112Watch8893

13,005 functions analyzed

Every top hotspot carries the same cluster of antipatterns.

Detected Antipatterns
Complex Branching×5Complex Branching
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

main
scripts/seed-digest-notifications.mjs
21.2
fire
CC 103
ND 8
FO 59
touches/30d 2

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.

Cyclomatic Complexity 103
threshold: 30
Max Nesting Depth 8
threshold: 4
Fan-Out 59
threshold: 15

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

processEvent
scripts/notification-relay.cjs
20.86
fire
CC 65
ND 9
FO 37
touches/30d 1

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.

Cyclomatic Complexity 65
threshold: 30
Max Nesting Depth 9
threshold: 4

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

handler
api/notification-channels.ts
20.22
fire
CC 86
ND 7
FO 28
touches/30d 1

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.

Cyclomatic Complexity 86
threshold: 30

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

handleWidgetAgentRequest
scripts/ais-relay.cjs
20.19
fire
CC 80
ND 7
FO 44
touches/30d 3

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.

Fan-Out 44
threshold: 15

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

drainHeldForUser
scripts/notification-relay.cjs
19.75
fire
CC 37
ND 8
FO 22
touches/30d 1

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.

Max Nesting Depth 8
threshold: 4

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:

PatternOccurrences
complex_branching5
deeply_nested5
exit_heavy5
god_function5
long_function4

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 →

Related Analyses