At commit 8040dd6, pi-mono’s sharpest risk is concentrated in its AI provider layer: the top two hotspots, convertMessages and convertResponsesMessages, both sit in the ‘fire’ quadrant with activity-weighted risk scores of 20.1 and 19.7 respectively — meaning they are structurally complex and being actively changed right now, not just cleanup candidates. Across 3,495 total functions, 406 are rated critical, and the top five hotspots share a uniform pattern of complex branching, deep nesting, and god-function coupling that compounds the regression surface with every commit.
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 |
|---|---|---|---|---|---|
convertMessages | packages/ai/src/providers/openai-completions.ts | 20.1 | 40 | 8 | 31 |
convertResponsesMessages | packages/ai/src/providers/openai-responses-shared.ts | 19.7 | 29 | 8 | 27 |
loadModelsDevData | packages/ai/scripts/generate-models.ts | 19.3 | 203 | 7 | 14 |
getFileSuggestions | packages/tui/src/autocomplete.ts | 19.1 | 40 | 7 | 27 |
processResponsesStream | packages/ai/src/providers/openai-responses-shared.ts | 18.8 | 74 | 16 | 16 |
Hotspot Analysis
convertMessages — packages/ai/src/providers/openai-completions.ts
Based on its name and location, convertMessages almost certainly transforms an internal message representation into the format expected by the OpenAI Completions API — a translation layer that must handle every message role, content type, and edge case the API supports. A cyclomatic complexity of 40 means 40 independent execution paths, each a required test case and a potential regression site; a max nesting depth of 8 means reasoning about the logic requires tracking eight levels of conditional context simultaneously. With a fan-out of 31 and an activity-weighted risk score of 20.1, this is a god function that is actively coupling to a wide surface of the codebase while being changed right now — a live regression risk, not a backlog item.
Recommendation: Before the next commit touches this function, write characterization tests that cover its major message-type branches, then extract each content-type conversion into its own focused function to bring CC below 15 and ND below 4.
convertResponsesMessages — packages/ai/src/providers/openai-responses-shared.ts
Sharing a file with another top-five hotspot, convertResponsesMessages likely performs the same translation role for the OpenAI Responses API — a parallel but distinct message format. Its cyclomatic complexity of 29 and max nesting depth of 8 mirror the structural profile of convertMessages, and its fan-out of 27 means changes propagate broadly across the codebase. An activity-weighted risk score of 19.7 confirms this is firmly in the ‘fire’ quadrant: structural complexity is compounding with active development pressure right now.
Recommendation: Identify shared conversion logic between convertMessages and convertResponsesMessages and extract it into a shared utility module; this reduces duplication, lowers fan-out for both functions, and creates a single place to test common message-handling rules.
processResponsesStream — packages/ai/src/providers/openai-responses-shared.ts
Also in openai-responses-shared.ts, processResponsesStream likely handles the real-time streaming response pipeline from the OpenAI Responses API — one of the most complex control-flow scenarios in any API integration, involving partial chunks, event sequencing, and error conditions. Its cyclomatic complexity of 74 is severe, and its max nesting depth of 16 is an extreme outlier — eight levels beyond the already-concerning threshold of 8 — meaning the function’s control flow is nearly impossible to reason about in full. With an activity-weighted risk score of 18.8, this is another ‘fire’ quadrant function, making its 74-path complexity a live regression risk every time it is touched.
Recommendation: Apply extract-method refactoring to decompose processResponsesStream into a pipeline of single-responsibility handlers (e.g., one per stream event type), targeting a max nesting depth below 5; add integration tests against a mocked stream before any refactoring begins to preserve behavioral contracts.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
complex_branching | 5 |
deeply_nested | 5 |
god_function | 5 |
long_function | 5 |
exit_heavy | 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.
Key Takeaways
convertMessagesinopenai-completions.tshas a cyclomatic complexity of 40 and a fan-out of 31 — write characterization tests covering all major branches before the next commit lands, not after.processResponsesStreamhas a max nesting depth of 16, which is the most extreme structural signal in the top five; decomposing it into event-type handlers would eliminate the deepest nesting immediately.loadModelsDevDatainscripts/generate-models.tscarries a cyclomatic complexity of 203 — a strong signal that it encodes large amounts of data or decision logic inline that should be extracted into configuration or lookup tables.
Reproduce This Analysis
git clone https://github.com/badlogic/pi-mono
cd pi-mono
git checkout 8040dd6ded6bd52b2b6271ab3588c4474715a4dd
hotspots analyze . --mode snapshot --explain-patterns --force
To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.
Hotspots highlights structural and activity risk — not “bad code.” Findings are a prioritization aid, not a bug predictor. Editorial policy →