Across 15,181 functions in novuhq/novu, 922 land in the critical band — and the top two by activity-weighted risk score (21.23 each) sit inside a single file: packages/framework/src/jsonSchemaFaker.js. Both are vendored functions whose structural numbers are extreme by any measure, and both were touched within the last 30 days. I would start there not because the library code itself needs rewriting, but because the way those functions are surfacing in first-party analysis signals a packaging problem that can obscure real risk elsewhere in the repo.
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 |
|---|---|---|---|---|---|
node_modules/yaml/dist/resolveSeq-d03cb037.js | packages/framework/src/jsonSchemaFaker.js | 21.2 | 257 | 9 | 146 |
src/shared.js | packages/framework/src/jsonSchemaFaker.js | 21.2 | 468 | 9 | 382 |
processSdkMessage | packages/novu/src/commands/wizard/pipeline/steps/run-agent.ts | 18.9 | 49 | 6 | 36 |
objectType | packages/framework/src/jsonSchemaFaker.js | 18.6 | 70 | 5 | 47 |
detectPackageManagerType | packages/add-inbox/src/config/package-manager.ts | 18.4 | 25 | 8 | 8 |
Large Repo Analysis
novu 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.
Codemod / Tooling Files in Results
Both of the top-ranked functions — node_modules/yaml/dist/resolveSeq-d03cb037.js and src/shared.js — are third-party library internals that have been bundled or inlined into packages/framework/src/jsonSchemaFaker.js. Their extreme complexity metrics (CC 257 and CC 468 respectively) reflect the full internal logic of the yaml and json-schema-faker libraries, not application code written by the novu team. To exclude this file from future analyses, add the following to your .hotspotsrc.json: { "exclude": ["packages/framework/src/jsonSchemaFaker.js"] }. This will prevent vendored complexity from dominating the hotspot rankings and allow genuine first-party risk to surface.
node_modules/yaml/dist/resolveSeq-d03cb037.js — packages/framework/src/jsonSchemaFaker.js
This function is vendored YAML parser code — specifically a sequence-resolution routine from the yaml package — that has been bundled or inlined into jsonSchemaFaker.js rather than consumed as a proper node_modules dependency. At a cyclomatic complexity of 257, a max nesting depth of 9, and a fan-out of 146, it carries every structural anti-pattern Hotspots tracks: complex branching across hundreds of independent execution paths, deeply nested control structures that make local reasoning nearly impossible, and god-function coupling into 146 distinct callees. The exit-heavy and long-function patterns compound this — there are many return paths through an already vast surface area, each one a potential test gap.
The activity signal is what makes this urgent rather than merely ugly. It was touched 1 time in the last 30 days and shows 0 days since last change, landing it squarely in the fire quadrant. That single recent commit on a file where every recorded commit has been classified as a bug fix is a flag worth taking seriously. I would not rewrite the YAML parser. I would replace this inlined copy with a direct dependency on the yaml package, add jsonSchemaFaker.js to the .hotspotsrc.json exclude list, and re-run the analysis to see what first-party critical functions were previously obscured by this file’s dominance.
src/shared.js — packages/framework/src/jsonSchemaFaker.js
This is the more extreme of the two. A cyclomatic complexity of 468 is not a refactoring problem — it is a signal that an entire bundled library module has been concatenated into a single analyzed unit. The fan-out of 382 means this function (or function-shaped blob) calls into 382 distinct locations, making it the structural center of gravity for everything jsonSchemaFaker.js touches. Nesting depth of 9 places it firmly in the range where even experienced engineers need external tooling to reason about control flow.
Like its neighbor, this was touched 1 time in the last 30 days with 0 days since last change, and every recorded commit on the file has been classified as a bug fix. Both functions share identical external signal profiles: one total commit, one author active in the last 90 days, no reverts. That sparse history on a file of this structural magnitude suggests it isn’t being actively reviewed at the depth the complexity warrants. The immediate action is the same as above: unbundle this third-party code, exclude the file from first-party analysis, and treat the schema-faking logic itself as the unit of ownership.
processSdkMessage — packages/novu/src/commands/wizard/pipeline/steps/run-agent.ts
With CC 49, ND 6, and FO 36, processSdkMessage is the most structurally significant first-party function in this snapshot. Its location in a wizard pipeline step for running an AI agent suggests it handles message dispatch and response routing for novu’s agentic workflow feature — a path that likely branches on message type, role, streaming state, and error conditions. CC 49 means 49 independent execution paths through a single function; ND 6 puts it well into the range where nested conditionals become hard to reason about without tooling. The FO of 36 adds coupling risk: changes here propagate to 36 distinct callees. At Risk 18.9, this is the fire-quadrant function most worth addressing once the vendor noise in jsonSchemaFaker.js is excluded. I’d extract the message-type branching into a dispatch table or dedicated handler functions, targeting a post-refactor CC below 15.
objectType — packages/framework/src/jsonSchemaFaker.js
Unlike the two vendored functions above it, objectType is application-owned schema-generation logic — the code that constructs fake object values when given a JSON Schema object type. At CC 70 and ND 5, it carries the full weight of JSON Schema’s object constraints: required properties, additionalProperties, and allOf/anyOf/oneOf combinators each add branching paths, with FO 47 reflecting the range of schema-handling routines it delegates into. If the team excludes jsonSchemaFaker.js from hotspot tracking entirely, this function goes with it — but it’s worth examining first. The combinators and property-iteration logic are natural extraction points that would bring CC into testable range and clarify ownership of each schema constraint.
detectPackageManagerType — packages/add-inbox/src/config/package-manager.ts
detectPackageManagerType has CC 25 and ND 8 — a classic detection function that probes the filesystem and environment for lockfile presence, version manager config, and workspace conventions across npm, yarn, pnpm, bun, and likely others. ND 8 is the metric to act on here: at that nesting depth, a reader must hold eight levels of conditional state simultaneously to follow any single branch. FO is only 8, so coupling isn’t the concern. Flattening this with early-return guard clauses — one detector function per package manager, one top-level dispatcher — would halve the complexity and make adding a new package manager a self-contained change rather than a surgery inside a nested conditional tree.
Key Takeaways
- Unbundle jsonSchemaFaker.js first. Both critical-band functions are vendored code living inside
packages/framework/src/jsonSchemaFaker.js. Replacing them with proper package dependencies and excluding the file via.hotspotsrc.jsonwill surface the true first-party picture —processSdkMessage(Risk 18.9) andobjectType(Risk 18.6) become the top priorities once the vendor noise is cleared. processSdkMessageis the most urgent first-party refactor. At CC 49, ND 6, and FO 36, it is both structurally complex and actively changing. Extracting its message-type branching into a dispatch table is a tractable refactor that directly reduces live regression risk in novu’s agentic workflow path.detectPackageManagerTypeis a quick win. CC 25 with ND 8 is a straightforward flattening problem — early-return guard clauses per package manager will cut complexity in half and make future additions self-contained.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
complex_branching | 5 |
deeply_nested | 5 |
exit_heavy | 5 |
god_function | 4 |
long_function | 4 |
hub_function | 1 |
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/novuhq/novu
cd novu
git checkout 60720ba12c8120cf04000755eb6ced705be0e96c
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 →