Skip to content

refactor(bot): move search and youtube error handler into services - #2354

Merged
LucasSantana-Dev merged 4 commits into
mainfrom
refactor/search-youtube-into-services
Sep 10, 2026
Merged

LucasSantana-Dev merged 4 commits into
mainfrom
refactor/search-youtube-into-services

Conversation

@LucasSantana-Dev

@LucasSantana-Dev LucasSantana-Dev commented Sep 10, 2026 •

Copy link
Copy Markdown
Owner

Second slice of #1991, stacked behind #2353. Does not close it: titleComparison/service remains.

Why these two move together

Both are stateful service trees living under utils/:

  • search/engineManager.ts — SearchEngineManager, holds a setTimeout
  • search/providerHealth.ts — ProviderHealthService, holds a mutable Map<MusicProvider, ProviderStatus>
  • youtubeErrorHandler/analyzer.ts — YouTubeErrorAnalyzer, behind a YouTubeErrorHandler facade

search/engineManager.ts and search/searchContentOnYoutube.ts both import youtubeErrorHandler. Moving either directory alone would leave utils/ importing services/, which is the inversion #1991 exists to remove, not relocate. So they move in one slice.

Surface is smaller than it first appears

My initial grep for utils/music/search reported 31 references. That was wrong: it also matches utils/music/searchQueryCleaner, an unrelated module that does not move. The real surface:

specifier count
deep imports of search/providerHealth 10
references to youtubeErrorHandler 7

Nothing outside imports the search barrel or engineManager at all. Every external reference to search/ is a deep import of providerHealth.

Path arithmetic

Both trees sit three levels under src/ at source and destination, so relative depth is unchanged:

  • the one outward relative import, '../../../types', still resolves to src/types
  • the sibling '../youtubeErrorHandler' still resolves, because both directories moved

One reference lived outside src/

tests/handlers/player/playerFactory.test.ts:45 holds a jest.mock() literal naming the old path. A src/-only scan missed it; the suite caught it as Test suite failed to run. Fixed. Worth noting for the remaining slice: packages/bot/tests/ is a second tree that must be scanned.

Deliberately not changed

decisions/2026-05-16-dependabot-batch-handling-policy.md:21 still names the old youtubeErrorHandler path. Left alone: that ADR records where the code was in May, and editing it to match a later move falsifies the record.

Verification

  • packages/bot full suite: 273 suites passed, 3588 tests passed, 1 skipped, exactly the pre-move baseline (pure move, no test added or removed)
  • npx tsc --noEmit: clean
  • npx madge --circular --extensions ts packages/bot/src: No circular dependency found!
  • npm run build --workspace=packages/bot: succeeds
  • repo-wide grep across ts/tsx/js/json/yml/md, both src/ and tests/: no remaining references outside the ADR noted above

🤖 Generated with Claude Code


Summary by cubic

Moves the stateful music search and YouTube error-handling trees from utils/music to services/musicManagement as the second slice of #1991, and adds missing test coverage for the moved barrels that SonarCloud was blocking on.

Written for commit cbfefa3. Summary will update on new commits.

Review in cubic

Both directories under utils/music/ are stateful service trees, not
utils. search/ holds SearchEngineManager (a timer) and
ProviderHealthService (a mutable Map keyed by provider);
youtubeErrorHandler/ holds YouTubeErrorAnalyzer behind a
YouTubeErrorHandler facade.

They move together on purpose. search/engineManager and
search/searchContentOnYoutube both import youtubeErrorHandler, so
moving either one alone would leave utils importing services, which is
the inversion #1991 exists to remove rather than relocate.

The external surface is smaller than it first looks. Nothing imports
the search barrel or engineManager from outside; only providerHealth is
reached, by deep path, from ten call sites. An early grep for
'utils/music/search' overcounted badly because it also matches
utils/music/searchQueryCleaner, an unrelated module that does not move.

Depth is unchanged, both trees sit three levels under src/, so the one
outward relative import ('../../../types') and the sibling
'../youtubeErrorHandler' both still resolve untouched.

One reference lived outside src/, a jest.mock() literal in
tests/handlers/player/playerFactory.test.ts, and a src-only scan missed
it until the suite caught it.

decisions/2026-05-16-dependabot-batch-handling-policy.md still names
the old path. Left alone deliberately: it records where the code was in
May, and editing an ADR to match a later move falsifies the record.

Part of #1991. Remaining: titleComparison/service.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pkt4D9yyHTrVhsvDYvw5an
@github-actions

Copy link
Copy Markdown

Failed to generate code suggestions for PR

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Warnings
⚠️

User-facing change without a CHANGELOG.md update. Add a line under ## [Unreleased] if this should appear in release notes. (Or apply the skip-changelog label if this PR does not affect end users.)

⚠️

This PR appears to remove a feature or route (detected in commit message). Please fill in the Feature-removal sweep checklist in the PR template to ensure no orphan code (models, tests, types, imports) is left behind. See decisions/ for context.

Generated by 🚫 dangerJS against cbfefa3

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).


Graphify review — findings

Relocates the music search and YouTube error-handling modules from utils/music to services/musicManagement, updating every importer (initializer, music command, errorEventHandlers, resilientStreamBridge, streamRecovery) and their spec mocks to the new paths. No behavioural change beyond the module layout; the moved SearchEngineManager, providerHealth, and enhancedSearch code is only reformatted.

No blocking issues surfaced. 5 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 261 functions depend on the 205 functions this change touches.

Health — this change adds coupling hotspots:

  • new: replenishQueue() — 19 callers, 9 callees
  • new: collectLastFmCandidates() — 5 callers, 13 callees
  • new: collectRecommendationCandidates() — 7 callers, 7 callees
  • new: collectSeedSimilarCandidates() — 5 callers, 8 callees
  • new: collectBroadFallbackCandidates() — 5 callers, 7 callees
  • new: setupLifecycleHandlers() — 3 callers, 10 callees
  • new: setupTrackHandlers() — 4 callers, 6 callees
  • new: handlePlayerSkip() — 2 callers, 11 callees
  • …and 19 more — each is listed as a finding

Verification — 261 functions in the blast radius were not formally verified this run (proofs are advisory here).

Health delta baseline: last indexed commit 89c847c, 1 commit(s) behind this PR's base.

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 244 function(s) in the blast radius were not formally verified this run

· 27 more finding(s) on lines outside this diff (see the check run).

LucasSantana-Dev and others added 2 commits September 10, 2026 15:13
SonarCloud failed this PR on new_coverage 63.4 percent against an
80 percent threshold. A pure move produces no new logic, but Sonar
counts moved files as new code, so coverage that was always missing
became gate-blocking here.

Three files sat at 0 percent and had done so before the move:
search/index.ts, search/searchContentOnYoutube.ts and
youtubeErrorHandler/index.ts. The other three moved files were already
above 94 percent.

The new tests target the behaviour that actually branches rather than
just executing the lines. For the search barrel that is engine
selection, since pinning the engine is the only thing separating its
four wrappers, including the playlist-vs-track choice. For the error
barrel it is one case per message branch in createYouTubeErrorMessage,
plus the error, warn and info routing in logYouTubeError, and the
import-time singleton, which holds a different analyzer instance than a
locally constructed handler and so needs stubbing separately. For
searchContentOnYoutube it is the fallback chain, covering both an
unsuccessful search and a success carrying no result, since both must
fall through, plus the recoverable and unrecoverable error routes.

Coverage on the moved set goes from 61.47 to 98.05 percent of lines.
No threshold, exclusion or gate configuration was changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pkt4D9yyHTrVhsvDYvw5an
@github-actions github-actions Bot added size/xl and removed size/m labels Sep 10, 2026

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.


Graphify review — findings

Moves the music search stack — providerHealthService, SearchEngineManager, analyzeYouTubeError, and the enhanced-search barrel — from utils/music to services/musicManagement, updating all importers (initializer, music command, player error/recovery/bridge handlers) and their test mocks accordingly. Adds index.spec.ts covering the EnhancedSearchService wrapper and the enhancedSearch/enhancedAutoSearch/enhancedYouTubeSearch/enhancedSpotifySearch helpers, asserting each delegates to the engine manager with the right preferred engine.

Worth a look

  • search types import path removed — packages/bot/src/utils/music/search/types.ts · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 277 functions depend on the 221 functions this change touches.

Health — this change adds coupling hotspots:

  • new: replenishQueue() — 19 callers, 9 callees
  • new: collectLastFmCandidates() — 5 callers, 13 callees
  • new: collectRecommendationCandidates() — 7 callers, 7 callees
  • new: collectSeedSimilarCandidates() — 5 callers, 8 callees
  • new: collectBroadFallbackCandidates() — 5 callers, 7 callees
  • new: setupLifecycleHandlers() — 3 callers, 10 callees
  • new: setupTrackHandlers() — 4 callers, 6 callees
  • new: handlePlayerSkip() — 2 callers, 11 callees
  • …and 19 more — each is listed as a finding

Verification — 277 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 260 function(s) in the blast radius were not formally verified this run

· 27 more finding(s) on lines outside this diff (see the check run).

@sonarqubecloud

Copy link
Copy Markdown

@LucasSantana-Dev
LucasSantana-Dev merged commit 6984e7f into main Sep 10, 2026
45 checks passed
@LucasSantana-Dev
LucasSantana-Dev deleted the refactor/search-youtube-into-services branch September 10, 2026 18:20
LucasSantana-Dev added a commit that referenced this pull request Sep 10, 2026
Third and final slice of #1991. Closes it, once #2354 also lands.

`TitleComparisonService` holds `private readonly cache: Map<string,
ArtistTitle>`, so it is a stateful service rather than a util. Last of
the five classes #1991 enumerated:

| class | slice |
|---|---|
| `TrackCacheManager` | #2353 |
| `SearchEngineManager`, `ProviderHealthService`, `YouTubeErrorAnalyzer`
| #2354 |
| `TitleComparisonService` | this PR |

## References rewritten

Eleven, across three shapes:

| shape | count | from |
|---|---|---|
| `'../../../../utils/music/titleComparison'` | 2 | `queueDisplay.ts` +
spec |
| `'../../../utils/music/titleComparison'` | 8 | `trackUtils` (3 files)
|
| `'../../../src/utils/music/titleComparison/service'` | 1 |
`tests/utils/music/titleComparison.test.ts` |

That last one is a `jest.mock()` literal in the **`tests/`** tree. #2354
hit exactly this and the suite caught it rather than my scan, so this
slice scanned both trees up front.

## The eight from trackUtils collapse back to `'../titleComparison'`

They were relative siblings originally. #2353 moved `trackUtils` into
services, which turned them into
`'../../../utils/music/titleComparison'`. Now that both live under
`services/musicManagement/`, they are siblings again and the specifier
returns to its original form. Worth noting so a reviewer does not read
it as an unrelated change.

## One outward dependency needed the reverse rewrite

The moved code sits one level deeper relative to `utils/`, so:

```
'../../misc/stringUtils'  ->  '../../../utils/misc/stringUtils'
```

in `service.ts` and in the `jest.mock()` literal in `service.spec.ts`.

## Independence from #2354

This slice touches neither `search/` nor `youtubeErrorHandler/`, and its
only prerequisite (#2353, for the `trackUtils` location) is already on
`main`. So it is branched off `main` rather than stacked on #2354, and
the two can merge in either order.

## Verification

- `packages/bot` full suite: 273 suites passed, 3588 tests passed, 1
skipped, exactly the pre-move baseline (pure move, no test added or
removed)
- `npx tsc --noEmit`: clean
- `npx madge --circular --extensions ts packages/bot/src`: `No circular
dependency found!`
- `npm run build --workspace=packages/bot`: succeeds
- repo-wide grep across ts/tsx/js/json/yml/md, both `src/` and `tests/`:
no remaining references

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Moves `TitleComparisonService` out of `utils` into `services` because it
maintains a private cache and behaves as a stateful service. This is the
last slice of the #1991 refactor and closes it; no behavior changes.

**Refactors**
- Updates 11 import references across `queueDisplay`, `trackUtils`, and
a `jest.mock()` in the test tree.
- The `trackUtils` imports revert to the sibling path
`../titleComparison` now that both live under
`services/musicManagement`.
- The outward dependency `../../misc/stringUtils` becomes
`../../../utils/misc/stringUtils` in `service.ts` and its spec.

**Tests**
- Adds a barrel spec raising moved-file coverage from 72.46% to 100% so
SonarCloud's 80% new-code gate passes.
- The spec runs against the real service and covers the facade,
per-instance cache, and custom threshold.
- `extractArtistTitle` doesn't split "artist - title" because pattern
lists ship empty; the gap is filed as #2356.

<sup>Written for commit 6bd85bd.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/LucasSantana-Dev/Lucky/pull/2355?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant