feat(batch): batch-operation framework + bulk-move-messages flagship - #1564
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds batch-job infrastructure for Discord moderation across shared contracts, persistence, worker execution, commands, HTTP routes, and a frontend management page. ChangesDiscord batch operations
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90+ minutes Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
22 issues found across 47 files
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
Foundation for the batch/bulk-operation framework (see the batch-operations ADR): persisted resumable BatchJob model, shared batch services, an in-process bullmq queue and worker reusing the existing redis, and a confirmation gate. 35 unit tests; build:shared and bot tsc green. Migration created next with the dev db.
Phase 1 of the batch-operation framework (ADR 2026-06-23-batch-operations-bullmq): - add_batch_jobs migration (batch_jobs + batch_job_items). - ChannelMoveBatchExecutor: re-posts messages source->dest (reusing move-message helpers), checkpoints nextCursor BEFORE the delete (crash-safe), honors cancel, resumes from cursor. Registered lazily inside startBatchJobWorker. - /bulk-move-messages (scope all|count|user|date_range|contains + dry-run + confirmation gate) and /batch-resume. - backend batchJobs route (list/detail/progress/cancel) + frontend Batch Jobs page (live progress + cancel) with en + pt-BR i18n. - 57 new bot tests (executor 95%, commands 90/100%), 13 backend, 15 frontend. - Fixed eventHandler/initializer specs (mock the worker; resetMocks-safe). All gates green: build:shared, tsc x3, bot 2669 / backend 1169 / frontend 854 / shared 1312.
- batchWorker: re-throw checkpoint failures; early-return on executor registration failure - batchQueue: add removeOnComplete/removeOnFail to prevent jobId dedup stall on retry - batchResume: null-check enqueueBatchJob; roll back markInProgress on enqueue failure - BatchJobService: optional includeItems param; remove eager item load from listByGuild - batchJobs routes: use includeItems in detail endpoint; map redis progress fields - ScopeResolver: coerce date_range dates via new Date() for JSON roundtrip - types: widen dateRangeStart/End to Date|string for Prisma JSON column - confirmationGate: check deferred/replied state; use followUp when already responded
bf3b276 to
fb4e7af
Compare
|
🧪 Staging deploy triggered — building Once it's up (~2-3 min for the image build), review it at https://lucky-staging.lucassantana.tech. Single shared environment — the most-recently-labeled PR occupies it. The build runs locally on the homelab; watch the deploy log there if it doesn't come up. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
|
Size Change: +4.83 kB (+1.03%) Total Size: 472 kB 📦 View Changed
ℹ️ View Unchanged
|
- batchJobs.test.ts: update getById assertion to include {includeItems:true}
- batchJobs.test.ts: update progress assertions to match new Redis field mapping
- channelMoveExecutor.ts: lazy-import getClient() to break start.ts cycle
There was a problem hiding this comment.
0 issues found across 2 files (changes from recent commits).
You’re at about 99% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.
Requires human review: Auto-approval blocked by 14 unresolved issues from previous reviews.
Re-trigger cubic
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (10)
packages/shared/src/services/batch/ScopeResolver.spec.ts (1)
78-242: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a date-range test using ISO string bounds (JSON roundtrip path).
matchesScopesupportsdateRangeStart/dateRangeEndasDate | string, but current tests only useDate. Add one case with ISO strings to lock the intended persistence behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/shared/src/services/batch/ScopeResolver.spec.ts` around lines 78 - 242, `matchesScope` already handles `date_range` scopes with `Date | string` bounds, but `ScopeResolver.spec.ts` only covers `Date` inputs. Add a focused test in the existing `matchesScope - date_range type` block that builds a `ScopeConfig` with `dateRangeStart` and `dateRangeEnd` as ISO strings, then assert `matchesScope` still includes a message inside the range and excludes one outside it. Use the existing `matchesScope` helper and `ScopeConfig` type so the JSON roundtrip behavior is locked in without changing production code.packages/shared/src/services/batch/BatchJobService.spec.ts (1)
123-124: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicate
//@ts-ignore`` comment.Lines 123-124 repeat the same directive; remove the extra one.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/shared/src/services/batch/BatchJobService.spec.ts` around lines 123 - 124, The BatchJobService.spec.ts test contains a duplicated ts-ignore directive; remove the extra // `@ts-ignore` so only one suppression remains near the affected assertion/setup in BatchJobService.spec.ts. Keep the remaining directive only if it is still needed for the specific TypeScript issue, and ensure the duplicated comment is eliminated from the surrounding test block.packages/shared/src/services/batch/PermissionChecker.ts (1)
53-80: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePermission map keyed only by name can't disambiguate source vs target.
permissionsis a flatRecord<string, boolean>, so a permission name appearing in bothsourceandtarget(across two different channels) collapses to a single boolean. No currentBatchJobTypetriggers this, but if a future job type requires the same permission on both ends, the check would silently use one value for both. Consider keying inputs by scope (e.g.{ source: {...}, target: {...} }) when that case arises.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/shared/src/services/batch/PermissionChecker.ts` around lines 53 - 80, Permission checking in checkBatchPermissions currently uses a flat permissions map, so the same permission name cannot be distinguished between source and target scopes. Update the PermissionChecker flow to accept and evaluate scoped permissions by channel when needed, using the checkBatchPermissions function and PERMISSION_REQUIREMENTS as the main touchpoints, so source and target can be validated independently without collapsing shared permission names into one boolean.packages/frontend/src/locales/en.json (1)
1062-1062: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider i18next plural forms for
minutes.
"{{count}} minutes"renders "1 minutes" for singular. If you rely on i18next pluralization, useminutes_one/minutes_otherkeys so singular/plural resolve correctly across locales.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/frontend/src/locales/en.json` at line 1062, The minutes translation currently uses a single string, so i18next will render an incorrect singular form for count 1. Update the locale entries around the existing minutes key in en.json to use i18next pluralization keys such as minutes_one and minutes_other, and keep the count placeholder in each form so callers using the minutes translation resolve correctly across singular and plural cases.packages/backend/src/routes/batchJobs.ts (1)
42-52: 📐 Maintainability & Code Quality | 🔵 TrivialDrop the duplicate query parse and cast.
validateQuery(s.listQuery)already coerces and writes the validated values back toreq.query, andBatchJobStatusmatches the schema enum, sostatus as anycan becomestatus.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/backend/src/routes/batchJobs.ts` around lines 42 - 52, The batch job route is redundantly parsing the query and weakening the type for status. In the handler in batchJobs.ts, rely on validateQuery(s.listQuery) having already populated req.query instead of calling s.listQuery.parse(req.query) again, then read status, limit, and offset directly from the validated query. Also replace the unnecessary status as any cast with the typed status value since BatchJobStatus matches the schema enum.packages/frontend/src/pages/BatchJobs.tsx (3)
394-394: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueTypo in state name
cancelllingJobId(triplel).Harmless (used consistently) but worth fixing for readability.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/frontend/src/pages/BatchJobs.tsx` at line 394, The state name in BatchJobs should be corrected for readability: rename the typoed `cancelllingJobId` identifier to `cancellingJobId` and update the corresponding setter `setCancellingJobId` references in the same component so the `useState` variable naming is consistent and easy to scan.
212-217: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd an accessible label to the close button (and consider Esc-to-close).
The icon-only close button has no accessible name, and the panel can only be dismissed via overlay click. Screen-reader/keyboard users benefit from an
aria-labeland an Escape handler.♿ Suggested label
<button onClick={onClose} + aria-label={t('close')} className='text-lucky-text-tertiary hover:text-lucky-text-primary transition-colors p-1' >🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/frontend/src/pages/BatchJobs.tsx` around lines 212 - 217, The icon-only close button in BatchJobs needs an accessible name and better keyboard dismissal support. Update the close button in the BatchJobs panel to include an aria-label using the existing onClose/X button area, and add an Escape key handler so the panel can be dismissed without relying on overlay clicks. Use the existing onClose handler and the button with the X icon as the touchpoint for these changes.
90-110: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRelative-time and date strings bypass i18n.
formatDatehardcodes'en-US'andtimeAgoreturns literal English ('Just now',${mins}m ago, etc.), so pt-BR users still see English here despite thebatchJobsnamespace. ConsiderIntl/i18n with the active locale.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/frontend/src/pages/BatchJobs.tsx` around lines 90 - 110, The date helpers in BatchJobs bypass localization by hardcoding en-US and returning English relative-time strings. Update formatDate and timeAgo to use the active locale from the batchJobs i18n context (or Intl with the current locale) so the displayed timestamps are translated instead of always English. Keep the logic in these helper functions but replace literal strings and locale assumptions with localized equivalents.packages/bot/src/utils/batch/confirmationGate.ts (1)
88-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueTimeout leaves the Proceed/Cancel buttons live.
On timeout/error the catch returns
falsewithout disabling or removing the components, so the stale buttons remain clickable on the ephemeral message. Consider editing the message to clear components in the catch (best-effort) so a late click can't be misinterpreted.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/bot/src/utils/batch/confirmationGate.ts` around lines 88 - 95, The timeout/error path in confirmationGate’s catch block returns false but leaves the Proceed/Cancel components active on the message. Update the catch in confirm gate handling to best-effort edit the message and clear or disable its components before returning false, using the existing confirmationGate flow and debugLog context so late clicks on the ephemeral message can’t be treated as valid actions.packages/bot/src/utils/batch/batchQueue.ts (1)
10-39: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueNo way to close the queue on shutdown.
The module caches a
Queueinstance but exposes noclose()path. On graceful bot shutdown the underlying Redis connection/queue is left open. Consider exporting acloseBatchQueue()that callsqueue?.close()and resets the singleton, invoked from the shutdown hook.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/bot/src/utils/batch/batchQueue.ts` around lines 10 - 39, The batch queue singleton in getQueue() is never closed, leaving the Redis-backed Queue open on shutdown. Add an exported closeBatchQueue() in batchQueue.ts that calls queue?.close() and clears the cached queue reference, then invoke it from the bot’s shutdown hook so the cached Queue instance is released cleanly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/bot/src/functions/moderation/batch/channelMoveExecutor.ts`:
- Around line 113-124: Seed the progress counters from the persisted batch job
state when resuming a run, since channelMoveExecutor currently resets
processed/failed/skipped to zero even after loading dbJob and nextCursor. Update
the resume path in the executor to initialize processed, failed, and skipped
from dbJob’s stored absolute counts before entering the loop, so
BatchJobService.checkpoint() continues from the existing totals instead of
overwriting them with smaller values. Use the dbJob load and cursor resume logic
in channelMoveExecutor as the place to make this change.
- Around line 52-53: The dynamic import of bot/start in channelMoveExecutor
creates an extra runtime cycle and breaks the madge gate. Remove the await
import('../../../bot/start') usage from channelMoveExecutor and get the
CustomClient from the caller instead, or resolve it outside this executor before
invoking it. Update the channelMoveExecutor flow so it only uses the passed-in
client (or an equivalent injected dependency) and no longer reaches back into
bot/start.
In `@packages/bot/src/functions/moderation/commands/batchResume.spec.ts`:
- Around line 453-479: The current test in batchResume.spec.ts mocks
enqueueBatchJob as a rejection, but enqueueBatchJob in batchQueue.ts swallows
failures and resolves to null, so the test is covering an unrealistic path.
Update the test around batchResumeCommand.execute to mock enqueueBatchJobMock
with a null result instead, and assert the real rollback flow in batchResume.ts
by checking that markFailed is called and the user sees the “Failed to queue”
response. Keep the existing symbols batchResumeCommand, enqueueBatchJobMock, and
markFailed to locate the relevant branch.
In `@packages/bot/src/functions/moderation/commands/bulkMoveMessages.spec.ts`:
- Around line 522-534: The bulk move messages test is using an unrealistic fetch
mock that returns 500 messages, masking the fetch-limit issue in
bulkMoveMessages. Update the fixture in bulkMoveMessages.spec.ts around
sourceChannel.messages.fetch to return a realistic Discord-sized batch (100 or
fewer) so it matches the actual API behavior and validates the SAMPLE_SIZE logic
in bulkMoveMessages.ts correctly.
In `@packages/bot/src/functions/moderation/commands/bulkMoveMessages.ts`:
- Around line 312-314: The lazy import in bulkMoveMessages already avoids one
cycle, but the build still fails because batchJobWorker.ts statically imports
channelMoveExecutor through its registry/handler. Update batchJobWorker to use
the same dynamic-import pattern as ChannelMoveBatchExecutor usage here: load the
executor lazily inside the worker’s registry or handler instead of at module top
level, so the eventHandler.ts -> batchJobWorker.ts -> channelMoveExecutor.ts
cycle is removed and madge passes.
- Around line 255-259: The bulk message sampling in bulkMoveMessages currently
asks sourceChannel.messages.fetch for 500 at once, which exceeds Discord’s
per-request limit and will trigger the catch path. Fix this by either reducing
SAMPLE_SIZE to 100 and updating the estimate logic in bulkMoveMessages
accordingly, or by changing the sampling flow to paginate with before so larger
samples are gathered across multiple fetches without exceeding the limit.
In `@packages/bot/src/utils/batch/batchQueue.ts`:
- Around line 29-31: The worker is reusing the shared Redis client, which is not
BullMQ-safe because it inherits maxRetriesPerRequest: 3. Update the worker path
in batchJobWorker to use a dedicated Redis connection configured with
maxRetriesPerRequest: null instead of the shared client, and keep the shared
client only for non-worker uses. Use the existing Queue/Worker setup in
batchQueue and the Worker initialization in batchJobWorker to locate where the
connection is passed in.
In `@packages/bot/src/utils/batch/confirmationGate.ts`:
- Around line 66-71: The initial reply in confirmationGate.ts is using
deprecated interaction response options. Update the replyFn call in the
confirmation gate flow to use flags: MessageFlags.Ephemeral instead of
ephemeral: true, and withResponse: true instead of fetchReply: true for the
initial reply path. Keep followUp() unchanged since it already returns a Message
and does not need fetchReply.
In `@packages/bot/src/workers/batchJobWorker.ts`:
- Around line 171-175: The Worker initialization in processBatchJob is using
redisClient.getClient(), which is not BullMQ-compatible because it carries
maxRetriesPerRequest: 3. Update the new Worker(QUEUE_NAME, processBatchJob, ...)
setup to use a dedicated ioredis connection created for BullMQ, such as a
duplicated client configured with maxRetriesPerRequest set to null. Make this
change in the batchJobWorker worker startup path so the catch block no longer
prevents the worker from starting due to an incompatible Redis instance.
- Around line 157-169: The lazy import in batchJobWorker still triggers the
circular-dependency check because madge counts async imports by default. Update
the module around the dynamic import of ChannelMoveBatchExecutor in
batchJobWorker to either remove the remaining cycle between worker, executor,
and bot/start, or adjust the madge configuration to ignore async imports if that
is the intended fix. Keep the change focused on the worker’s executor
registration path and verify the circular import chain is eliminated for the
packages/bot/src graph.
In `@packages/frontend/src/pages/BatchJobs.tsx`:
- Around line 410-411: The pagination total is being derived from the current
page size in BatchJobs, which prevents the page controls from reflecting
additional pages. Update the /api/guilds/:guildId/batch-jobs response to include
the real total count, then change BatchJobs to use that returned total instead
of res.data.jobs.length when calling setTotal. Keep the fix aligned with the
existing jobs state handling in BatchJobs and the batch-jobs API response shape.
In `@packages/shared/src/services/batch/ProgressReporter.ts`:
- Around line 90-95: The ProgressReporter.progressBar helper can throw when
percent is outside the 0–100 range because empty may become negative and is
passed to String.repeat. Update progressBar to clamp the input percent before
calculating filled/empty, so the bar generation always stays within bounds even
when counters are inconsistent. Use the existing ProgressReporter.progressBar
method as the fix point and keep the return format unchanged.
- Around line 25-34: The progress calculation in ProgressReporter should use
attempted items rather than only successful ones, since processed excludes
failed and skipped work. Update the percentComplete and ETA logic in the
progress-reporting method to base completion on all completed attempts
(processed + failed + skipped, or an equivalent attempted count) and derive
remaining work from the total attempted items so progress reaches 100% when the
batch is fully done.
In `@packages/shared/src/services/batch/ScopeResolver.ts`:
- Around line 59-63: The `default` branch in `ScopeResolver`'s switch statement
declares `_exhaustive` directly in the clause, which violates switch-scope
linting. Update the `default` case to use its own block so the `switch` remains
lint-safe while preserving the exhaustive `never` check in `ScopeResolver`.
---
Nitpick comments:
In `@packages/backend/src/routes/batchJobs.ts`:
- Around line 42-52: The batch job route is redundantly parsing the query and
weakening the type for status. In the handler in batchJobs.ts, rely on
validateQuery(s.listQuery) having already populated req.query instead of calling
s.listQuery.parse(req.query) again, then read status, limit, and offset directly
from the validated query. Also replace the unnecessary status as any cast with
the typed status value since BatchJobStatus matches the schema enum.
In `@packages/bot/src/utils/batch/batchQueue.ts`:
- Around line 10-39: The batch queue singleton in getQueue() is never closed,
leaving the Redis-backed Queue open on shutdown. Add an exported
closeBatchQueue() in batchQueue.ts that calls queue?.close() and clears the
cached queue reference, then invoke it from the bot’s shutdown hook so the
cached Queue instance is released cleanly.
In `@packages/bot/src/utils/batch/confirmationGate.ts`:
- Around line 88-95: The timeout/error path in confirmationGate’s catch block
returns false but leaves the Proceed/Cancel components active on the message.
Update the catch in confirm gate handling to best-effort edit the message and
clear or disable its components before returning false, using the existing
confirmationGate flow and debugLog context so late clicks on the ephemeral
message can’t be treated as valid actions.
In `@packages/frontend/src/locales/en.json`:
- Line 1062: The minutes translation currently uses a single string, so i18next
will render an incorrect singular form for count 1. Update the locale entries
around the existing minutes key in en.json to use i18next pluralization keys
such as minutes_one and minutes_other, and keep the count placeholder in each
form so callers using the minutes translation resolve correctly across singular
and plural cases.
In `@packages/frontend/src/pages/BatchJobs.tsx`:
- Line 394: The state name in BatchJobs should be corrected for readability:
rename the typoed `cancelllingJobId` identifier to `cancellingJobId` and update
the corresponding setter `setCancellingJobId` references in the same component
so the `useState` variable naming is consistent and easy to scan.
- Around line 212-217: The icon-only close button in BatchJobs needs an
accessible name and better keyboard dismissal support. Update the close button
in the BatchJobs panel to include an aria-label using the existing onClose/X
button area, and add an Escape key handler so the panel can be dismissed without
relying on overlay clicks. Use the existing onClose handler and the button with
the X icon as the touchpoint for these changes.
- Around line 90-110: The date helpers in BatchJobs bypass localization by
hardcoding en-US and returning English relative-time strings. Update formatDate
and timeAgo to use the active locale from the batchJobs i18n context (or Intl
with the current locale) so the displayed timestamps are translated instead of
always English. Keep the logic in these helper functions but replace literal
strings and locale assumptions with localized equivalents.
In `@packages/shared/src/services/batch/BatchJobService.spec.ts`:
- Around line 123-124: The BatchJobService.spec.ts test contains a duplicated
ts-ignore directive; remove the extra // `@ts-ignore` so only one suppression
remains near the affected assertion/setup in BatchJobService.spec.ts. Keep the
remaining directive only if it is still needed for the specific TypeScript
issue, and ensure the duplicated comment is eliminated from the surrounding test
block.
In `@packages/shared/src/services/batch/PermissionChecker.ts`:
- Around line 53-80: Permission checking in checkBatchPermissions currently uses
a flat permissions map, so the same permission name cannot be distinguished
between source and target scopes. Update the PermissionChecker flow to accept
and evaluate scoped permissions by channel when needed, using the
checkBatchPermissions function and PERMISSION_REQUIREMENTS as the main
touchpoints, so source and target can be validated independently without
collapsing shared permission names into one boolean.
In `@packages/shared/src/services/batch/ScopeResolver.spec.ts`:
- Around line 78-242: `matchesScope` already handles `date_range` scopes with
`Date | string` bounds, but `ScopeResolver.spec.ts` only covers `Date` inputs.
Add a focused test in the existing `matchesScope - date_range type` block that
builds a `ScopeConfig` with `dateRangeStart` and `dateRangeEnd` as ISO strings,
then assert `matchesScope` still includes a message inside the range and
excludes one outside it. Use the existing `matchesScope` helper and
`ScopeConfig` type so the JSON roundtrip behavior is locked in without changing
production code.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a0401836-1510-48a0-a262-6f29a3c8b5e9
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (46)
decisions/2026-06-23-batch-operations-bullmq.mdpackage.jsonpackages/backend/src/routes/batchJobs.tspackages/backend/src/routes/index.tspackages/backend/src/schemas/batchJobs.tspackages/backend/tests/integration/routes/batchJobs.test.tspackages/backend/tests/unit/routes/index.test.tspackages/bot/package.jsonpackages/bot/src/bot/start/initializer.spec.tspackages/bot/src/bot/start/initializer.tspackages/bot/src/functions/moderation/batch/channelMoveExecutor.spec.tspackages/bot/src/functions/moderation/batch/channelMoveExecutor.tspackages/bot/src/functions/moderation/commands/batchResume.spec.tspackages/bot/src/functions/moderation/commands/batchResume.tspackages/bot/src/functions/moderation/commands/bulkMoveMessages.spec.tspackages/bot/src/functions/moderation/commands/bulkMoveMessages.tspackages/bot/src/handlers/eventHandler.spec.tspackages/bot/src/handlers/eventHandler.tspackages/bot/src/handlers/moveMessageHandler.tspackages/bot/src/utils/batch/batchQueue.tspackages/bot/src/utils/batch/confirmationGate.tspackages/bot/src/workers/batchJobWorker.tspackages/bot/src/workers/executorRegistry.spec.tspackages/bot/src/workers/executorRegistry.tspackages/frontend/src/App.tsxpackages/frontend/src/locales/en.jsonpackages/frontend/src/locales/pt-BR.jsonpackages/frontend/src/pages/BatchJobs.test.tsxpackages/frontend/src/pages/BatchJobs.tsxpackages/frontend/src/services/api.tspackages/frontend/src/services/batchJobsApi.tspackages/frontend/src/types/batchJobs.tspackages/frontend/src/types/index.tspackages/shared/package.jsonpackages/shared/src/services/batch/BatchJobService.spec.tspackages/shared/src/services/batch/BatchJobService.tspackages/shared/src/services/batch/PermissionChecker.tspackages/shared/src/services/batch/ProgressReporter.tspackages/shared/src/services/batch/ScopeResolver.spec.tspackages/shared/src/services/batch/ScopeResolver.tspackages/shared/src/services/batch/index.tspackages/shared/src/services/batch/types.tspackages/shared/src/services/index.tspackages/shared/src/services/redis/client.tsprisma/migrations/20260623231522_add_batch_jobs/migration.sqlprisma/schema.prisma
📜 Review details
⏰ Context from checks skipped due to timeout. (14)
- GitHub Check: Test — shared
- GitHub Check: Checks
- GitHub Check: Test — bot
- GitHub Check: Test — backend
- GitHub Check: Test — frontend
- GitHub Check: cubic · AI code reviewer
- GitHub Check: danger / danger
- GitHub Check: quality / Dead code (knip)
- GitHub Check: quality / SAST (CodeQL) (javascript-typescript)
- GitHub Check: quality / Lint (lint)
- GitHub Check: Build — bot
- GitHub Check: Build — backend
- GitHub Check: Build — frontend
- GitHub Check: compressed-size
🧰 Additional context used
🪛 ast-grep (0.44.0)
packages/backend/tests/integration/routes/batchJobs.test.ts
[warning] 80-80: Express application should use Helmet
Context: express()
Note: [CWE-693] Protection Mechanism Failure (Express app without Helmet security headers).
(missing-helmet-typescript)
[error] 125-127: Avoid SQL injection
Context: request(app)
.get(/api/guilds/${MOCK_GUILD_ID}/batch-jobs)
.query({ status: 'in_progress', limit: 50, offset: 10 })
Note: [CWE-89] Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').
(sql-injection-typescript)
🪛 Biome (2.5.0)
packages/shared/src/services/batch/ScopeResolver.ts
[error] 61-61: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
(lint/correctness/noSwitchDeclarations)
🪛 GitHub Actions: Circular Deps (madge) / 0_madge _ packages_bot.txt
packages/bot/src/workers/executorRegistry.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/bot/start/initializer.spec.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/handlers/eventHandler.spec.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/handlers/eventHandler.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/functions/moderation/commands/batchResume.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/utils/batch/confirmationGate.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/workers/executorRegistry.spec.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/utils/batch/batchQueue.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/bot/start/initializer.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/handlers/moveMessageHandler.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/functions/moderation/commands/batchResume.spec.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/functions/moderation/batch/channelMoveExecutor.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/functions/moderation/batch/channelMoveExecutor.spec.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/functions/moderation/commands/bulkMoveMessages.spec.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/functions/moderation/commands/bulkMoveMessages.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
packages/bot/src/workers/batchJobWorker.ts
[error] 1-1: madge circular deps check failed. Found 2 circular dependencies. Steps: 'npx madge --circular --extensions ts packages/bot/src' and 'if [ "$COUNT" -gt 1 ]; then ... exit 1'. Cycles: (1) types/CustomClient.ts > models/Command.ts > types/CommandData.ts; (2) bot/start.ts > bot/start/index.ts > bot/start/initializer.ts > handlers/eventHandler.ts > workers/batchJobWorker.ts > functions/moderation/batch/channelMoveExecutor.ts.
🪛 GitHub Actions: Circular Deps (madge) / madge _ packages_bot
packages/bot/src/workers/executorRegistry.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/bot/start/initializer.spec.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/handlers/eventHandler.spec.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/handlers/eventHandler.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/functions/moderation/commands/batchResume.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/utils/batch/confirmationGate.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/workers/executorRegistry.spec.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/utils/batch/batchQueue.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/bot/start/initializer.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/handlers/moveMessageHandler.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/functions/moderation/commands/batchResume.spec.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/functions/moderation/batch/channelMoveExecutor.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/functions/moderation/batch/channelMoveExecutor.spec.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/functions/moderation/commands/bulkMoveMessages.spec.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/functions/moderation/commands/bulkMoveMessages.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
packages/bot/src/workers/batchJobWorker.ts
[error] 1-1: Cycle count exceeds baseline (> 1). Step failed after detecting 2 circular dependencies.
🔇 Additional comments (33)
packages/shared/src/services/redis/client.ts (1)
81-88: LGTM!prisma/schema.prisma (1)
100-100: LGTM!Also applies to: 1108-1170
prisma/migrations/20260623231522_add_batch_jobs/migration.sql (1)
1-61: LGTM!packages/shared/src/services/batch/BatchJobService.ts (1)
12-215: LGTM!packages/shared/src/services/batch/index.ts (1)
1-12: LGTM!packages/backend/src/routes/batchJobs.ts (1)
4-8: 📐 Maintainability & Code Quality | ⚡ Quick winUnused import
validateBody.
validateBodyis imported but never used in any route here. Remove it.♻️ Proposed fix
import { - validateBody, validateParams, validateQuery, } from '../middleware/validate'Source: Linters/SAST tools
packages/backend/tests/integration/routes/batchJobs.test.ts (2)
80-92: The ast-grep hints for missing Helmet and SQL injection here are false positives: this is a test app with fully mocked services and no SQL execution. No action needed.
94-350: LGTM!packages/backend/src/schemas/batchJobs.ts (1)
1-27: LGTM!packages/backend/src/routes/index.ts (1)
37-37: LGTM!Also applies to: 74-74, 99-99
packages/backend/tests/unit/routes/index.test.ts (1)
32-32: LGTM!Also applies to: 150-153, 177-184, 218-218
packages/frontend/src/locales/pt-BR.json (1)
1018-1071: LGTM!packages/frontend/src/types/batchJobs.ts (1)
1-51: LGTM!packages/frontend/src/types/index.ts (1)
9-9: LGTM!packages/frontend/src/services/batchJobsApi.ts (1)
4-30: LGTM!packages/frontend/src/services/api.ts (1)
35-35: LGTM!Also applies to: 446-446
packages/frontend/src/App.tsx (1)
50-50: LGTM!Also applies to: 232-235
packages/frontend/src/pages/BatchJobs.test.tsx (1)
1-460: LGTM!packages/bot/src/functions/moderation/batch/channelMoveExecutor.ts (3)
5-5:GuildPremiumTierimport is unused.Source: Linters/SAST tools
63-63: Destructuredoptionsis never used.Source: Linters/SAST tools
130-164:hasMoreis effectively redundant — it is alwaystrueat thewhilecheck and thefalseassignment beforebreakis unused.Source: Linters/SAST tools
packages/bot/package.json (1)
26-26: LGTM!packages/bot/src/handlers/eventHandler.spec.ts (1)
82-89: LGTM!packages/bot/src/handlers/moveMessageHandler.ts (1)
106-106: LGTM!packages/bot/src/functions/moderation/batch/channelMoveExecutor.spec.ts (1)
40-622: LGTM!packages/bot/src/functions/moderation/commands/bulkMoveMessages.ts (1)
252-252: 📐 Maintainability & Code Quality
deferReply({ ephemeral: true })uses the deprecated option.Same deprecation as in
confirmationGate.ts: preferflags: MessageFlags.Ephemeraloverephemeral: trueon discord.js v14.26.packages/bot/src/functions/moderation/commands/batchResume.ts (1)
88-106: LGTM!packages/bot/src/handlers/eventHandler.ts (1)
70-75: TheclientReadywiring with fire-and-forget.catchis fine. Note the static import on Line 48 is the edge that completes the madge cycle flagged inbatchJobWorker.ts; addressing it there resolves this file's contribution.packages/bot/src/workers/executorRegistry.ts (1)
11-35: LGTM!packages/bot/src/workers/executorRegistry.spec.ts (1)
8-37: LGTM!packages/bot/src/workers/batchJobWorker.ts (1)
20-140: LGTM!packages/bot/src/bot/start/initializer.spec.ts (1)
58-64: LGTM!packages/bot/src/bot/start/initializer.ts (1)
235-242: LGTM!
## Summary Fixes the 3 CI blockers on PR #1564 (`feat/batch-operations`): **Circular dependency (madge)** - Introduced `clientStore.ts` — a lightweight singleton that holds the Discord client reference - `BotInitializer.createDiscordClient()` calls `setClient()` after creating the client - `ChannelMoveBatchExecutor.execute()` now calls `getStoredClient()` from `clientStore` instead of dynamically importing from `bot/start` - Result: `start.ts → … → batchJobWorker → channelMoveExecutor → start.ts` cycle is broken; only the pre-existing baseline cycle remains **ESLint `any` errors (6 errors across 4 files)** - `channelMoveExecutor.ts` — `scope as any` → `scope as ScopeConfig`; import `ScopeConfig` type - `bulkMoveMessages.ts` — `{ type, config } as any` → `as ScopeConfig`; import `ScopeConfig` type - `batchJobWorker.ts` — typed `Job` generic: `Job<BatchJobData>` to fix `no-unsafe-member-access` on `.jobId` - `BatchJobService.ts` — cast `JSON.parse(...)` results to proper types to fix `no-unsafe-assignment` **Still requires manual action from operator:** - Destructive Interaction Gate: run `/bulk-move-messages` on a test guild (happy path + cancellation path), then tick `- [x] Live smoke performed` in the PR #1564 body. Closes the fixable CI blockers on #1564. Does not address SonarCloud coverage (needs investigation after lint/madge pass). <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Fixes a circular dependency and ESLint errors blocking CI for batch operations. Adds a small `clientStore` and type-safe updates to stabilize the batch worker and executor. - **Bug Fixes** - Broke the `madge`-reported cycle by adding `packages/bot/src/bot/clientStore.ts`; `BotInitializer.createDiscordClient()` calls `setClient()`, and `ChannelMoveBatchExecutor` uses `getStoredClient()` instead of a dynamic import. - Replaced `any` casts with `ScopeConfig` from `@lucky/shared/services/batch` in `channelMoveExecutor.ts` and `bulkMoveMessages.ts`. - Typed `bullmq` `Job` as `Job<BatchJobData>` in `batchJobWorker.ts` and accessed `job.data.jobId` safely. - Cast results of `JSON.parse(...)` in `BatchJobService` to typed objects to satisfy `no-unsafe-*` ESLint rules. <sup>Written for commit f35e2cf. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/LucasSantana-Dev/Lucky/pull/1610?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. -->
## Problem
Two test failures on `feat/batch-operations`:
**`Test — shared: FAIL BatchJobService.spec.ts`** — Jest couldn't parse
the file because `BatchJobService.ts` imported `{ Prisma }` (value
import) from `../../generated/prisma/client.js`. The generated Prisma
client uses ESM syntax that Jest can't transform, causing "Jest
encountered an unexpected token" at line 9.
**`Test — bot: FAIL channelMoveExecutor.spec.ts`** — All executor tests
failed with "Discord client not available" instead of their intended
scenario. The spec was still mocking `bot/start` (`getClient`), but PR
#1610 changed the executor to use `getStoredClient()` from
`clientStore.ts` to break the circular dependency.
## Fix
`BatchJobService.ts`: change `import { Prisma }` → `import type { Prisma
}`. Type-only imports are erased at compile time — Jest never sees the
ESM module. Replace `Prisma.DbNull` (value usage) with `null as unknown
as Prisma.InputJsonValue` (Prisma accepts null for nullable JSON fields
at runtime).
`channelMoveExecutor.spec.ts`: mock `../../../bot/clientStore` (with
`getStoredClient`) instead of `../../../bot/start` (with `getClient`) to
match the executor's actual import after the circular-dep fix.
Covers three previously untested files (batchQueue, confirmationGate, batchJobWorker) driving the SonarCloud new-coverage gap on PR #1564. Uses isolateModules to reset module-level singletons and beforeEach to re-apply discord.js mocks after resetMocks:true wipes them.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/bot/src/functions/moderation/batch/channelMoveExecutor.ts (1)
105-111: 🎯 Functional Correctness | 🟠 MajorUse
SendMessagesInThreadsfor thread destinations.PermissionFlagsBits.SendMessagesdoesn’t cover threads, so this preflight rejects thread targets that can still receive messages. Switch to the thread-specific send permission whendestChannel.isThread(); keep theEmbedLinkscheck unchanged.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/bot/src/functions/moderation/batch/channelMoveExecutor.ts` around lines 105 - 111, The preflight permission check in channelMoveExecutor should use the thread-specific send permission for thread destinations instead of always checking PermissionFlagsBits.SendMessages. Update the logic around destPerms/targetChannelId so that when destChannel.isThread() is true it validates PermissionFlagsBits.SendMessagesInThreads, while keeping the existing PermissionFlagsBits.EmbedLinks check unchanged; otherwise continue using SendMessages for non-thread channels.
♻️ Duplicate comments (1)
packages/bot/src/functions/moderation/batch/channelMoveExecutor.ts (1)
120-125: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winCarry persisted progress forward on resume.
Line 121 only restores
nextCursor; Lines 122-125 resetprocessed/failed/skipped/messageIndex, and Lines 140-145 and 277-280 return only this run’s array lengths. On a resumed job that both corrupts the absolute checkpoint totals and makescountscopes re-evaluate older messages from index0, so the job can move more messages than requested after a crash.Suggested fix
const scope = dbJob.scope as unknown as ScopeConfig let cursor = dbJob.nextCursor || undefined - let processed = 0 - let failed = 0 - let skipped = 0 - let messageIndex = 0 + let processed = dbJob.processedItems + let failed = dbJob.failedItems + let skipped = dbJob.skippedItems + let messageIndex = processed + failed + skipped const movedMessages: string[] = [] const failedIds: string[] = [] @@ return { - moved: movedMessages.length, - failed: failedIds.length, + moved: processed, + failed, skipped, cancelled: true, } @@ return { - moved: movedMessages.length, - failed: failedIds.length, + moved: processed, + failed, skipped, movedUrls: movedMessages, failedIds, }Also applies to: 140-145, 277-280
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/bot/src/functions/moderation/batch/channelMoveExecutor.ts` around lines 120 - 125, The resume path in channelMoveExecutor is only restoring nextCursor, while processed, failed, skipped, and messageIndex are reset and the progress returned from the run uses only per-run lengths. Update the job resume logic in channelMoveExecutor to hydrate persisted counters and starting index from the saved dbJob state, and make the checkpoint/progress calculations use cumulative totals rather than just the current batch’s array lengths so count-based scopes continue from the correct message position after a crash.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/bot/src/utils/batch/confirmationGate.spec.ts`:
- Around line 13-18: The confirmationGate tests are only asserting the boolean
outcome and the discord.js stubs are hiding regressions in the payload shape.
Update confirmationGate.spec to assert the actual confirmation payload for the
warning path, and make the mocked builders preserve inputs for addFields,
setDescription, and setStyle so the embed copy and button style can be verified.
Also correct the ButtonStyle.Success mock in the discord.js stub used by this
spec, and use the confirmationGate helper/test cases as the primary location for
these assertions.
---
Outside diff comments:
In `@packages/bot/src/functions/moderation/batch/channelMoveExecutor.ts`:
- Around line 105-111: The preflight permission check in channelMoveExecutor
should use the thread-specific send permission for thread destinations instead
of always checking PermissionFlagsBits.SendMessages. Update the logic around
destPerms/targetChannelId so that when destChannel.isThread() is true it
validates PermissionFlagsBits.SendMessagesInThreads, while keeping the existing
PermissionFlagsBits.EmbedLinks check unchanged; otherwise continue using
SendMessages for non-thread channels.
---
Duplicate comments:
In `@packages/bot/src/functions/moderation/batch/channelMoveExecutor.ts`:
- Around line 120-125: The resume path in channelMoveExecutor is only restoring
nextCursor, while processed, failed, skipped, and messageIndex are reset and the
progress returned from the run uses only per-run lengths. Update the job resume
logic in channelMoveExecutor to hydrate persisted counters and starting index
from the saved dbJob state, and make the checkpoint/progress calculations use
cumulative totals rather than just the current batch’s array lengths so
count-based scopes continue from the correct message position after a crash.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 19281f1b-b183-4ca4-9ff5-e9a6aa4f1e30
📒 Files selected for processing (14)
packages/bot/src/bot/clientStore.tspackages/bot/src/bot/start/initializer.tspackages/bot/src/functions/moderation/batch/channelMoveExecutor.spec.tspackages/bot/src/functions/moderation/batch/channelMoveExecutor.tspackages/bot/src/functions/moderation/commands/bulkMoveMessages.tspackages/bot/src/handlers/moveMessageHandler.tspackages/bot/src/utils/batch/batchQueue.spec.tspackages/bot/src/utils/batch/confirmationGate.spec.tspackages/bot/src/workers/batchJobWorker.spec.tspackages/bot/src/workers/batchJobWorker.tspackages/frontend/src/App.tsxpackages/frontend/src/services/api.tspackages/shared/src/services/batch/BatchJobService.tsprisma/schema.prisma
💤 Files with no reviewable changes (1)
- packages/frontend/src/App.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
- packages/bot/src/bot/start/initializer.ts
- prisma/schema.prisma
- packages/frontend/src/services/api.ts
- packages/bot/src/functions/moderation/commands/bulkMoveMessages.ts
- packages/bot/src/functions/moderation/batch/channelMoveExecutor.spec.ts
- packages/bot/src/workers/batchJobWorker.ts
Bot review superseded by subsequent commits
|
🤖 I have created a release *beep* *boop* --- <details><summary>2.26.0</summary> ## [2.26.0](v2.25.0...v2.26.0) (2026-07-01) ### Features * **batch:** batch-operation framework + bulk-move-messages flagship ([#1564](#1564)) ([9d11bf5](9d11bf5)) * **bot:** add RSS bridge service for Criativaria guides ([#1608](#1608)) ([2807cad](2807cad)) * **bot:** criativaria live twitch notification (poll every 2 min) ([#1613](#1613)) ([e1d10b6](e1d10b6)) ### Bug Fixes * add missing fetch timeouts to GuildService Discord API calls ([#1641](#1641)) ([a8a57d5](a8a57d5)) * auth loop between web dashboard and api subdomains ([572e320](572e320)) * **autoplay:** prevent over-queueing; ensure evicted recs get terminal events ([#1589](#1589)) ([815d763](815d763)) * **backend:** add validateparams to forums route guildid and slug ([#1602](#1602)) ([a7102f4](a7102f4)) * **backend:** propagate db errors from deleteReactionRoleMessage ([#1604](#1604)) ([b36fad0](b36fad0)) * **bot:** process threadcreate regardless of newlycreated flag ([#1606](#1606)) ([be08786](be08786)) * bound music queue params, type rolegroup mapping, harden ci lint ([#1588](#1588)) ([309d5d9](309d5d9)) * **deploy:** derive require_running_containers from docker compose ([#1601](#1601)) ([5715249](5715249)) * **deploy:** wire GITHUB_DEPLOY_STATUS_TOKEN into lucky-webhook container ([#1597](#1597)) ([ad4b7ed](ad4b7ed)) * **frontend:** fix CF Pages API routing and remove Vercel Analytics ([#1596](#1596)) ([2902984](2902984)) * **middleware:** resolve guildAccess non-atomic session+context staleness window ([5a64dd1](5a64dd1)) * **reaction-roles:** validate roleIds, fix update rollback, serialize concurrent appends ([#1587](#1587)) ([6873ac8](6873ac8)) * **release:** tag-guard reconciles autorelease label ([#1561](#1561)) ([#1583](#1583)) ([6505f44](6505f44)) * **schema:** add unique guild+thread constraint to GuildForumThread ([#1607](#1607)) ([6c4c8ab](6c4c8ab)) * **security:** pass staging webhook secret via env not argv ([#1600](#1600)) ([d12efc5](d12efc5)) * **security:** verify bot authorship before trusting slug marker ([#1599](#1599)) ([23bf73a](23bf73a)) * **test:** close open handles causing jest force-exit in bot suite ([#1605](#1605)) ([cf2a026](cf2a026)) </details> --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Release 2.26.0 adds a batch-operation framework and new Criativaria integrations (RSS and Twitch). It also ships stability and security fixes across the bot, backend, and frontend. - **New Features** - Batch-operation framework with bulk move of messages. - RSS bridge for Criativaria guides. - Twitch live notifications (poll every 2 minutes). - **Bug Fixes** - Add fetch timeouts to Discord API calls. - Fix auth loop between web dashboard and API. - Reaction roles: validate IDs and serialize updates to prevent conflicts. - Middleware: resolve guildAccess staleness race. - Security: pass staging webhook secret via env and verify bot authorship before trusting slug markers. <sup>Written for commit a1f2e49. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/LucasSantana-Dev/Lucky/pull/1650?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. -->
:robot: I have created a release *beep* *boop*
---
<details><summary>2.33.0</summary>
##
[2.33.0](https://github.com/LucasSantana-Dev/Lucky/compare/v2.32.3...v2.33.0)
(2026-07-09)
### Features
* **autoplay:** add implicit-dislike-penalty signal
([#1374](https://github.com/LucasSantana-Dev/Lucky/issues/1374))
([593c0ad](https://github.com/LucasSantana-Dev/Lucky/commit/593c0ada5b732b4a48d63204c8e849c4b5057677))
* **autoplay:** add recency-decay signal for queue diversity
([#1376](https://github.com/LucasSantana-Dev/Lucky/issues/1376))
([b85e2a0](https://github.com/LucasSantana-Dev/Lucky/commit/b85e2a0f5d79efd04590d17db58faee5f5a50d38))
* **autoplay:** boost candidates for frequently replayed tracks
([#1370](https://github.com/LucasSantana-Dev/Lucky/issues/1370))
([215edea](https://github.com/LucasSantana-Dev/Lucky/commit/215edea22b8e99ab114f3450323a5f5de61ce6fb))
* **autoplay:** guild opt-out toggle for sertanejo veto
([#1087](https://github.com/LucasSantana-Dev/Lucky/issues/1087))
([#1373](https://github.com/LucasSantana-Dev/Lucky/issues/1373))
([6cb5588](https://github.com/LucasSantana-Dev/Lucky/commit/6cb55883f850aca68f4a6d5c2d5a3e5b492df8d5))
* **autoplay:** guild-scope implicit dislike for autoplay skips
([#1578](https://github.com/LucasSantana-Dev/Lucky/issues/1578))
([70b8596](https://github.com/LucasSantana-Dev/Lucky/commit/70b8596e7d4a0de5468e701f111c288aef9abe35))
* **autoplay:** hit@k eval harness for recommendation scoring
([#1577](https://github.com/LucasSantana-Dev/Lucky/issues/1577))
([2a0c6c4](https://github.com/LucasSantana-Dev/Lucky/commit/2a0c6c43f83fc5bf2f552549f3dfc832aeb8a95f))
* **autoplay:** instrument outcome eval to disambiguate
[#1275](https://github.com/LucasSantana-Dev/Lucky/issues/1275)
([#1491](https://github.com/LucasSantana-Dev/Lucky/issues/1491))
([1921ab5](https://github.com/LucasSantana-Dev/Lucky/commit/1921ab58ac8de1826fe73a931a02a9a5bf9c7541))
* **autoplay:** quick-wins batch — mood-cache clear, provider telemetry,
accept-rate
([#1090](https://github.com/LucasSantana-Dev/Lucky/issues/1090)
[#1083](https://github.com/LucasSantana-Dev/Lucky/issues/1083)
[#1086](https://github.com/LucasSantana-Dev/Lucky/issues/1086))
([#1102](https://github.com/LucasSantana-Dev/Lucky/issues/1102))
([7d7e4f5](https://github.com/LucasSantana-Dev/Lucky/commit/7d7e4f5451a67eac311c72270e9fe59c7aa4ff98))
* **backend:** add zod validation to artists and toggles routes
([#1189](https://github.com/LucasSantana-Dev/Lucky/issues/1189))
([#1334](https://github.com/LucasSantana-Dev/Lucky/issues/1334))
([b59fb35](https://github.com/LucasSantana-Dev/Lucky/commit/b59fb35244d9f1712d0730035c154ba471e34544))
* **backend:** dedup key for support-report intake
([#1319](https://github.com/LucasSantana-Dev/Lucky/issues/1319))
([#1328](https://github.com/LucasSantana-Dev/Lucky/issues/1328))
([4d95307](https://github.com/LucasSantana-Dev/Lucky/commit/4d9530762e37c97440e2e6a6957886ff41fcc1b6))
* **backend:** move session store from Redis to Postgres
([#1111](https://github.com/LucasSantana-Dev/Lucky/issues/1111))
([#1396](https://github.com/LucasSantana-Dev/Lucky/issues/1396))
([ff5e0b6](https://github.com/LucasSantana-Dev/Lucky/commit/ff5e0b684aa369a208a3e01d9c9a8c4cc53e4308))
* **backend:** read-only guild members/roles service endpoints
([#1691](https://github.com/LucasSantana-Dev/Lucky/issues/1691))
([fd04b6e](https://github.com/LucasSantana-Dev/Lucky/commit/fd04b6ef5f8a1609a468bb97f43213df488af8a8))
* **backend:** request-id correlation middleware for
[#1286](https://github.com/LucasSantana-Dev/Lucky/issues/1286)
([#1417](https://github.com/LucasSantana-Dev/Lucky/issues/1417))
([671ed4c](https://github.com/LucasSantana-Dev/Lucky/commit/671ed4c90471670f68346b493eade85d55a92ee9))
* **backend:** support intake + admin routes + staff notification
([#1241](https://github.com/LucasSantana-Dev/Lucky/issues/1241))
([280faeb](https://github.com/LucasSantana-Dev/Lucky/commit/280faeb983e02ae498960cb98b1ca10e7f44af2f))
* **backend:** wire moderation executor into
GuildAutomationExecutionService
([#1066](https://github.com/LucasSantana-Dev/Lucky/issues/1066))
([43ed8db](https://github.com/LucasSantana-Dev/Lucky/commit/43ed8db3906c826d8f01738fc5a49052c7f94862))
* **batch:** batch-operation framework + bulk-move-messages flagship
([#1564](https://github.com/LucasSantana-Dev/Lucky/issues/1564))
([9d11bf5](https://github.com/LucasSantana-Dev/Lucky/commit/9d11bf5d985dc9765b75eecb0bc798e2fe0c6443))
* **bot:** /vaga command builds job posts with auto-tagged roles
([#1682](https://github.com/LucasSantana-Dev/Lucky/issues/1682))
([963e457](https://github.com/LucasSantana-Dev/Lucky/commit/963e457af6402437b10138124052df524af37a99))
* **bot:** add RSS bridge service for Criativaria guides
([#1608](https://github.com/LucasSantana-Dev/Lucky/issues/1608))
([2807cad](https://github.com/LucasSantana-Dev/Lucky/commit/2807cada542424d3e4b15eaf76ec56d6b7250c8a))
* **bot:** add weekly community digest service
([#1609](https://github.com/LucasSantana-Dev/Lucky/issues/1609))
([2a653bf](https://github.com/LucasSantana-Dev/Lucky/commit/2a653bff32f3e5afa15cb73aae4d41e0476da859))
* **bot:** afk status with mention replies
([#1689](https://github.com/LucasSantana-Dev/Lucky/issues/1689))
([d8b5ba1](https://github.com/LucasSantana-Dev/Lucky/commit/d8b5ba101ea69033f18d9236481c9f8225867f08))
* **bot:** criativaria live twitch notification (poll every 2 min)
([#1613](https://github.com/LucasSantana-Dev/Lucky/issues/1613))
([e1d10b6](https://github.com/LucasSantana-Dev/Lucky/commit/e1d10b6efa7dd570867f4e678e9d0f6e30368bf7))
* **bot:** extend mod-log posting, fix twitch startup silence
([#1698](https://github.com/LucasSantana-Dev/Lucky/issues/1698))
([fb48065](https://github.com/LucasSantana-Dev/Lucky/commit/fb4806554220e604094aee1e27a498376ad566ff))
* **bot:** instrument serversetup criativaria invocations
([#1288](https://github.com/LucasSantana-Dev/Lucky/issues/1288))
([#1390](https://github.com/LucasSantana-Dev/Lucky/issues/1390))
([c37f021](https://github.com/LucasSantana-Dev/Lucky/commit/c37f021f3c28a13a6a58ed2529dcc5e3269892b1))
* **bot:** persistent giveaways with reaction entry
([#1690](https://github.com/LucasSantana-Dev/Lucky/issues/1690))
([b715af9](https://github.com/LucasSantana-Dev/Lucky/commit/b715af9df16ad016eaa7feda5f6e287d2b441933))
* **bot:** post moderation case embeds to the mod-log channel
([#1696](https://github.com/LucasSantana-Dev/Lucky/issues/1696))
([884da0f](https://github.com/LucasSantana-Dev/Lucky/commit/884da0fc5d8d689751c02ab4546911d11dc11fb8))
* **bot:** reminders with /remind and delivery scheduler
([#1686](https://github.com/LucasSantana-Dev/Lucky/issues/1686))
([1228290](https://github.com/LucasSantana-Dev/Lucky/commit/12282903a07538c75869c5eabb24f2823fb7411d))
* **bot:** smart custom commands via generic command-kind seam (ADR
2026-07-03)
([#1684](https://github.com/LucasSantana-Dev/Lucky/issues/1684))
([f0c446c](https://github.com/LucasSantana-Dev/Lucky/commit/f0c446c5dad1ab343b9a8df57f7b89ea3eaccaa2))
* **bot:** starboard seeding and one-time first-star dm
([#1685](https://github.com/LucasSantana-Dev/Lucky/issues/1685))
([e12e2e4](https://github.com/LucasSantana-Dev/Lucky/commit/e12e2e4e18a1d5fc256c1c83b818384c8366fe60))
* **bot:** surface support url + correlation id in command error embeds
([#1240](https://github.com/LucasSantana-Dev/Lucky/issues/1240))
([1cc004b](https://github.com/LucasSantana-Dev/Lucky/commit/1cc004bcd4d14a36dc7c6d31442c6264972cdc24))
* **bot:** utility join-onboarding message + in-bot growth adr
([#1506](https://github.com/LucasSantana-Dev/Lucky/issues/1506))
([0a23775](https://github.com/LucasSantana-Dev/Lucky/commit/0a23775cdb458479e0e1b23ad1e42679d6036d57))
* **dashboard:** add role groups management page
([#1678](https://github.com/LucasSantana-Dev/Lucky/issues/1678))
([bb8bc2c](https://github.com/LucasSantana-Dev/Lucky/commit/bb8bc2c9d2e2a0dd2cccd3ff690c16531a576016))
* **dashboard:** reaction roles create and delete
([c5c351c](https://github.com/LucasSantana-Dev/Lucky/commit/c5c351cddeb494cbcebce5448f96538bd8f97954))
* **dashboard:** refresh, single server switcher, i18n, avatar+cursor
([#1546](https://github.com/LucasSantana-Dev/Lucky/issues/1546))
([abda321](https://github.com/LucasSantana-Dev/Lucky/commit/abda3219eb4e5fdbb376b619cf3ab99f390fdefc))
* **db:** add check constraints on guild_settings bounds
([#1124](https://github.com/LucasSantana-Dev/Lucky/issues/1124))
([#1338](https://github.com/LucasSantana-Dev/Lucky/issues/1338))
([a7c7400](https://github.com/LucasSantana-Dev/Lucky/commit/a7c74007bbb0df43e45e88de52971e3858ee729a))
* **deploy:** SHA-pinned deploys + auto-rollback on health failure
([#1230](https://github.com/LucasSantana-Dev/Lucky/issues/1230))
([e24f128](https://github.com/LucasSantana-Dev/Lucky/commit/e24f1284d89edc9a8a72cb2135df580c8f7467a7))
* **frontend:** add music surface pages and components
([bb40e9c](https://github.com/LucasSantana-Dev/Lucky/commit/bb40e9c667a79bfd588b1fc13a61b55c457c2fd8))
* **frontend:** add ServerLogs + ServerSettings UI pages
([#965](https://github.com/LucasSantana-Dev/Lucky/issues/965))
([89961d3](https://github.com/LucasSantana-Dev/Lucky/commit/89961d324aed39001737e1f9873b7def200aaa65))
* growth surfaces — /invite, landing SEO + CTA, guild telemetry
([#1494](https://github.com/LucasSantana-Dev/Lucky/issues/1494))
([205876d](https://github.com/LucasSantana-Dev/Lucky/commit/205876d4ad9ba415840abc4207ca9ac98a99e7f4))
* guild integrations pack
([#1669](https://github.com/LucasSantana-Dev/Lucky/issues/1669))
([64a41a4](https://github.com/LucasSantana-Dev/Lucky/commit/64a41a48a1147b06ca18e36cbd5f9a4551321d23))
* **guild-automation:** wire AutoMessages executor into execution
service ([#906](https://github.com/LucasSantana-Dev/Lucky/issues/906))
([#950](https://github.com/LucasSantana-Dev/Lucky/issues/950))
([b5e444b](https://github.com/LucasSantana-Dev/Lucky/commit/b5e444b20dc836cf2fc29c6d70ccb67c7ac2ae9e))
* **infra:** homelab staging environment for visual PR review
([#1547](https://github.com/LucasSantana-Dev/Lucky/issues/1547))
([c15fa38](https://github.com/LucasSantana-Dev/Lucky/commit/c15fa388a61db9d1c3cfe880885f32d6020a629d))
* **levels:** show member display names on leaderboard, not raw ids
([eb0d700](https://github.com/LucasSantana-Dev/Lucky/commit/eb0d7009a0519bb6c00f6dcab2865c7c571779ce))
* **logs:** async context propagation, discord alerts, noise filtering
([#1510](https://github.com/LucasSantana-Dev/Lucky/issues/1510))
([a952c54](https://github.com/LucasSantana-Dev/Lucky/commit/a952c5400518f29c650abb286fada80caf34f2cd))
* **moderation:** move a message to another channel via right-click
([#1516](https://github.com/LucasSantana-Dev/Lucky/issues/1516))
([9822893](https://github.com/LucasSantana-Dev/Lucky/commit/98228930177479a078016c1c20e1ba43d393b7fd))
* **music:** add previous-track command end to end
([#1239](https://github.com/LucasSantana-Dev/Lucky/issues/1239))
([#1347](https://github.com/LucasSantana-Dev/Lucky/issues/1347))
([7771167](https://github.com/LucasSantana-Dev/Lucky/commit/7771167e7cc1534c561d6bad3cfbd2bcf85e261f))
* **observability:** alert on redis control publish failures
([#1401](https://github.com/LucasSantana-Dev/Lucky/issues/1401))
([6e46cd7](https://github.com/LucasSantana-Dev/Lucky/commit/6e46cd7ab193dedbff4a7b62ac0c6060489a4607))
* **observability:** capture escaping errors to Sentry at chokepoints
([#1229](https://github.com/LucasSantana-Dev/Lucky/issues/1229))
([9448de4](https://github.com/LucasSantana-Dev/Lucky/commit/9448de4b2a4c41145a3db443faff3df1e5dae1b1))
* **observability:** deploy markers, heartbeat, alerts (Layers 1-3)
([#1103](https://github.com/LucasSantana-Dev/Lucky/issues/1103))
([24568c0](https://github.com/LucasSantana-Dev/Lucky/commit/24568c02af1b802624d08f184fa64dcadcc413b3))
* **queue:** queueResolver telemetry pilot
([#1084](https://github.com/LucasSantana-Dev/Lucky/issues/1084))
([#1100](https://github.com/LucasSantana-Dev/Lucky/issues/1100))
([527609a](https://github.com/LucasSantana-Dev/Lucky/commit/527609a86a3f1b67bda3dbf8b62b371213dc77ff))
* **reaction-roles:** editable form, emoji picker, formatting, media,
export/import
([#1544](https://github.com/LucasSantana-Dev/Lucky/issues/1544))
([ac5e0e9](https://github.com/LucasSantana-Dev/Lucky/commit/ac5e0e988a27468eb75ace887795ed80c2d75b5f))
* **role-groups:** composite add-styled-role v1
([#1557](https://github.com/LucasSantana-Dev/Lucky/issues/1557))
([c869811](https://github.com/LucasSantana-Dev/Lucky/commit/c8698117666b066f4f7aa5ad5bc38602321e6cd7))
* **security:** add security headers + csp report-only
([#1283](https://github.com/LucasSantana-Dev/Lucky/issues/1283))
([#1315](https://github.com/LucasSantana-Dev/Lucky/issues/1315))
([7413a1c](https://github.com/LucasSantana-Dev/Lucky/commit/7413a1cebe733cd62f583ed197cd3bba50428e82))
* **security:** collect CSP violations via report-uri sink
([#1283](https://github.com/LucasSantana-Dev/Lucky/issues/1283))
([#1415](https://github.com/LucasSantana-Dev/Lucky/issues/1415))
([6f68aa3](https://github.com/LucasSantana-Dev/Lucky/commit/6f68aa31b8d9a9582e36de85c77e32d58d8adfd5))
* service announce endpoint with timing-safe key + channel allowlist
([#1681](https://github.com/LucasSantana-Dev/Lucky/issues/1681))
([3fbf022](https://github.com/LucasSantana-Dev/Lucky/commit/3fbf02256d8aed9fb4df067dcba7d87389317acb))
* **settings:** add Discord role management page (CRUD + bulk-delete)
([#1524](https://github.com/LucasSantana-Dev/Lucky/issues/1524))
([7db3ca2](https://github.com/LucasSantana-Dev/Lucky/commit/7db3ca240dba8906cb2247266c806c1a178c58fe))
* **shared:** add reactionroles executor (capture/diff/apply)
([142882c](https://github.com/LucasSantana-Dev/Lucky/commit/142882cbe8cc23e12c6c183abd965d25231e1d4c))
* **shared:** support report foundation
([#1223](https://github.com/LucasSantana-Dev/Lucky/issues/1223))
([#1228](https://github.com/LucasSantana-Dev/Lucky/issues/1228))
([77258bc](https://github.com/LucasSantana-Dev/Lucky/commit/77258bc47c26dd58978112882a2793944d0b78e4))
* skip-reason telemetry via emoji reactions on now-playing
([#1377](https://github.com/LucasSantana-Dev/Lucky/issues/1377))
([5b1959f](https://github.com/LucasSantana-Dev/Lucky/commit/5b1959fd96057c396baf17f9929e6a32f9737eed))
* **staging:** opt-in test bot for pre-merge live smoke
([#1692](https://github.com/LucasSantana-Dev/Lucky/issues/1692))
([c54eaba](https://github.com/LucasSantana-Dev/Lucky/commit/c54eabab1525d04297b6241eba7ea979826159b1))
* **twitch:** add stream.offline, channel.update and channel.raid
EventSub events
([#1531](https://github.com/LucasSantana-Dev/Lucky/issues/1531))
([b05a9cf](https://github.com/LucasSantana-Dev/Lucky/commit/b05a9cfeab0fb6e389a70171e5e2264ae8a7577f))
* **twitch:** follower and subscriber role sync
([#1509](https://github.com/LucasSantana-Dev/Lucky/issues/1509))
([d353bc0](https://github.com/LucasSantana-Dev/Lucky/commit/d353bc068614da2310bf50393a3b321842bb8044))
* **ui:** community pages — Starboard and Levels as connected components
([fafa2ac](https://github.com/LucasSantana-Dev/Lucky/commit/fafa2ac225ba6af8c903acfda8bf45abed865606))
* **web:** per-route seo metadata + sitemap, robots, og-image
([79a5f0d](https://github.com/LucasSantana-Dev/Lucky/commit/79a5f0d88e2e9e5102822e9ff34222b280e082c2)),
closes [#1131](https://github.com/LucasSantana-Dev/Lucky/issues/1131)
[#1132](https://github.com/LucasSantana-Dev/Lucky/issues/1132)
* **web:** public /support form + admin report view + error-state wiring
([#1245](https://github.com/LucasSantana-Dev/Lucky/issues/1245))
([19d855e](https://github.com/LucasSantana-Dev/Lucky/commit/19d855e50ce7332280a7ae3e91f9bf8650c5ea21))
### Bug Fixes
* add missing fetch timeouts to GuildService Discord API calls
([#1641](https://github.com/LucasSantana-Dev/Lucky/issues/1641))
([a8a57d5](https://github.com/LucasSantana-Dev/Lucky/commit/a8a57d5b780e900e0fa856804910ef8e3ed67d7a))
* add timeouts to unbounded external fetch calls
([#1333](https://github.com/LucasSantana-Dev/Lucky/issues/1333))
([38dde55](https://github.com/LucasSantana-Dev/Lucky/commit/38dde55fb8631185fed7e3e025d94362fef68e13))
* **api:** wrap automod + moderation settings responses as { settings }
([#1142](https://github.com/LucasSantana-Dev/Lucky/issues/1142))
([04893fd](https://github.com/LucasSantana-Dev/Lucky/commit/04893fd55ef570eca5e8a0682798639a9b1b40e7))
* auth loop between web dashboard and api subdomains
([572e320](https://github.com/LucasSantana-Dev/Lucky/commit/572e320ef803d195a96eb0f66ec42445f73a1931))
* **auth:** log session lookup failures in optional auth
([#1286](https://github.com/LucasSantana-Dev/Lucky/issues/1286))
([ff2b3ab](https://github.com/LucasSantana-Dev/Lucky/commit/ff2b3ab9f3f06dd3b81194f4e3e3f8a113f523f5))
* **automod:** remove dead warn/mute/kick/ban switch cases
([#1511](https://github.com/LucasSantana-Dev/Lucky/issues/1511))
([8ec8ed7](https://github.com/LucasSantana-Dev/Lucky/commit/8ec8ed76cbba1e30442fe17c9f15aa9ccf494dff))
* **autoplay:** capture skip rejections (symmetric completion threshold)
([#1276](https://github.com/LucasSantana-Dev/Lucky/issues/1276))
([c282414](https://github.com/LucasSantana-Dev/Lucky/commit/c282414346bf6c2247ed5d8a22c0c9bfcc5fdeb2))
* **autoplay:** key track start-time per track, not per guild
([#1275](https://github.com/LucasSantana-Dev/Lucky/issues/1275))
([#1483](https://github.com/LucasSantana-Dev/Lucky/issues/1483))
([0853a90](https://github.com/LucasSantana-Dev/Lucky/commit/0853a90412ed64c6e9cec7732311e5648cdb49f1))
* **autoplay:** prevent over-queueing; ensure evicted recs get terminal
events ([#1589](https://github.com/LucasSantana-Dev/Lucky/issues/1589))
([815d763](https://github.com/LucasSantana-Dev/Lucky/commit/815d763329d60580f8034232b606d7d0b2814684))
* **autoplay:** provenance-aware genre guards open the seed neighborhood
([#1272](https://github.com/LucasSantana-Dev/Lucky/issues/1272))
([405af1e](https://github.com/LucasSantana-Dev/Lucky/commit/405af1eae055f661a42310717c39adab6aa220a4))
* **autoplay:** weight popularity over name similarity in similar mode
([#1273](https://github.com/LucasSantana-Dev/Lucky/issues/1273))
([cb24a7e](https://github.com/LucasSantana-Dev/Lucky/commit/cb24a7e9c6993b72be780c72710c524459f591d6))
* **backend:** add validateparams to forums route guildid and slug
([#1602](https://github.com/LucasSantana-Dev/Lucky/issues/1602))
([a7102f4](https://github.com/LucasSantana-Dev/Lucky/commit/a7102f4c5f54405af37d631bfd45506b8cef4615))
* **backend:** assert required env vars at startup and fail fast
([#1169](https://github.com/LucasSantana-Dev/Lucky/issues/1169))
([107e235](https://github.com/LucasSantana-Dev/Lucky/commit/107e235d6b22d7097dd67980b5944ad1bc138c65))
* **backend:** bound pagination limit on leaderboard + starboard entries
([#1307](https://github.com/LucasSantana-Dev/Lucky/issues/1307))
([c2b5cbe](https://github.com/LucasSantana-Dev/Lucky/commit/c2b5cbe2beceb2dc5761f74fb63eda03790de5d7))
* **backend:** degrade gracefully on external fetch timeouts
([#1342](https://github.com/LucasSantana-Dev/Lucky/issues/1342))
([#1345](https://github.com/LucasSantana-Dev/Lucky/issues/1345))
([5de7b69](https://github.com/LucasSantana-Dev/Lucky/commit/5de7b694e7215fd979ef506f66cb7d1f91067249))
* **backend:** enforce discord snowflake validation on all guild routes
([#1172](https://github.com/LucasSantana-Dev/Lucky/issues/1172))
([ec25670](https://github.com/LucasSantana-Dev/Lucky/commit/ec25670ea76a571c96ad20fc4e7df72d9ecf8255))
* **backend:** guard timingsafeequal against length mismatch in lastfm
route ([#1719](https://github.com/LucasSantana-Dev/Lucky/issues/1719))
([6d58671](https://github.com/LucasSantana-Dev/Lucky/commit/6d586711c804be9f66199338330ca0746c4e8fe5))
* **backend:** harden role + reaction-role write-path error handling
([#1543](https://github.com/LucasSantana-Dev/Lucky/issues/1543))
([18a36ba](https://github.com/LucasSantana-Dev/Lucky/commit/18a36ba673ffaa6e5a0f1d35f28bcd62a1c9c64c))
* **backend:** log swallowed spotify search errors
([#1285](https://github.com/LucasSantana-Dev/Lucky/issues/1285))
([#1318](https://github.com/LucasSantana-Dev/Lucky/issues/1318))
([72a7431](https://github.com/LucasSantana-Dev/Lucky/commit/72a74317105f6ae6aedb817344f79bcf0a16b373))
* **backend:** propagate db errors from deleteReactionRoleMessage
([#1604](https://github.com/LucasSantana-Dev/Lucky/issues/1604))
([b36fad0](https://github.com/LucasSantana-Dev/Lucky/commit/b36fad097822dd8013b02e5fd21d2cb536bab317))
* **backend:** replayed named creates return existing row
([#1320](https://github.com/LucasSantana-Dev/Lucky/issues/1320))
([#1326](https://github.com/LucasSantana-Dev/Lucky/issues/1326))
([be2b30c](https://github.com/LucasSantana-Dev/Lucky/commit/be2b30cdb69ad8d94665eb8ae2afb93c325bd5ce))
* **backend:** restrict cors allowlist to first-party hosts
([#1247](https://github.com/LucasSantana-Dev/Lucky/issues/1247))
([6021120](https://github.com/LucasSantana-Dev/Lucky/commit/602112012a2e42b0a0cbb7e5d1d2aa4299d9d7c1))
* **backend:** validate guildId snowflake on all 18 music routes
([#1297](https://github.com/LucasSantana-Dev/Lucky/issues/1297))
([b93cfb5](https://github.com/LucasSantana-Dev/Lucky/commit/b93cfb565288a36bb9c0cbb36640eadb874505d6))
* **backend:** wrap Spotify routes in asyncHandler
([#1184](https://github.com/LucasSantana-Dev/Lucky/issues/1184))
([#1219](https://github.com/LucasSantana-Dev/Lucky/issues/1219))
([a3be33b](https://github.com/LucasSantana-Dev/Lucky/commit/a3be33bceca656ff76325b3a7a10e53e4af83058))
* **batch:** bullmq worker requires maxretriesperrequest null redis
([#1665](https://github.com/LucasSantana-Dev/Lucky/issues/1665))
([f5adffe](https://github.com/LucasSantana-Dev/Lucky/commit/f5adffe8f17df02362ac34d619ad35836706e614))
* **bot:** accurate reply when previous button has no history
([#1191](https://github.com/LucasSantana-Dev/Lucky/issues/1191))
([#1331](https://github.com/LucasSantana-Dev/Lucky/issues/1331))
([eb9b2ea](https://github.com/LucasSantana-Dev/Lucky/commit/eb9b2ea28b1f0581910f73833e09caec41f50f34))
* **bot:** bound all Spotify API fetches with an 8s abort deadline
([#1302](https://github.com/LucasSantana-Dev/Lucky/issues/1302))
([b283159](https://github.com/LucasSantana-Dev/Lucky/commit/b2831594ecc1d456d6f683e89a2b22a996b9beb7))
* **bot:** capture failed error-replies to Sentry in interaction handler
([#1175](https://github.com/LucasSantana-Dev/Lucky/issues/1175))
([45665c2](https://github.com/LucasSantana-Dev/Lucky/commit/45665c2aff006ab26c8c0d6a3e2658df36d66aba))
* **bot:** catch floating promises in setTimeout callbacks
([#1210](https://github.com/LucasSantana-Dev/Lucky/issues/1210))
([#1218](https://github.com/LucasSantana-Dev/Lucky/issues/1218))
([8e790f9](https://github.com/LucasSantana-Dev/Lucky/commit/8e790f95f321da6b6e32206c4d29600dffef9401))
* **bot:** catch resume errors in skip delayed play
([#1353](https://github.com/LucasSantana-Dev/Lucky/issues/1353))
([#1354](https://github.com/LucasSantana-Dev/Lucky/issues/1354))
([c2d2758](https://github.com/LucasSantana-Dev/Lucky/commit/c2d275872fbab168a1e7c603ca415ab3d3284c27))
* **bot:** catch settings fetch errors in idle disconnect scheduling
([#1361](https://github.com/LucasSantana-Dev/Lucky/issues/1361))
([61b82e4](https://github.com/LucasSantana-Dev/Lucky/commit/61b82e4ce2f6f016b22ed5b0f6b672a4da55f0cb))
* **bot:** clear presence rotation interval on shutdown
([#1171](https://github.com/LucasSantana-Dev/Lucky/issues/1171))
([c6d35f5](https://github.com/LucasSantana-Dev/Lucky/commit/c6d35f5dee0a520b31ca51e7d0ad51105c09ad92))
* **bot:** collect /vaga descricao via modal, not a single-line option
([#1701](https://github.com/LucasSantana-Dev/Lucky/issues/1701))
([ba20cd8](https://github.com/LucasSantana-Dev/Lucky/commit/ba20cd8f0857983e0f0765e16cf91722ba568e6c))
* **bot:** dead-man heartbeat + exit on fatal init failure
([#1656](https://github.com/LucasSantana-Dev/Lucky/issues/1656))
([e379f5f](https://github.com/LucasSantana-Dev/Lucky/commit/e379f5f8bd62cfa6173cd514f1c83b5ef2080c65))
* **bot:** expand SoundCloud short links before discord-player
resolution
([#1177](https://github.com/LucasSantana-Dev/Lucky/issues/1177))
([ff84610](https://github.com/LucasSantana-Dev/Lucky/commit/ff84610786cfffbd3c106d168aad475543e32d16))
* **bot:** extend graceful bot-perm guard to mgmt + automod
([#1502](https://github.com/LucasSantana-Dev/Lucky/issues/1502))
([1ee510d](https://github.com/LucasSantana-Dev/Lucky/commit/1ee510dd3773d7f55d5a0b7d7e2be7bc0e027c17))
* **bot:** graceful bot-permission guard + moderation pilot
([#1498](https://github.com/LucasSantana-Dev/Lucky/issues/1498))
([#1499](https://github.com/LucasSantana-Dev/Lucky/issues/1499))
([e2664ce](https://github.com/LucasSantana-Dev/Lucky/commit/e2664ce97b6d99a63521036d3d4b5dbd0a051878))
* **bot:** ground autoplay on seed similarity + genre-condition scoring
([#1268](https://github.com/LucasSantana-Dev/Lucky/issues/1268))
([aeacbc6](https://github.com/LucasSantana-Dev/Lucky/commit/aeacbc61ce2618159d56333c22943777febf2dba))
* **bot:** harden youtube extractor registration
([#1468](https://github.com/LucasSantana-Dev/Lucky/issues/1468))
([#1472](https://github.com/LucasSantana-Dev/Lucky/issues/1472))
([2e7f1bb](https://github.com/LucasSantana-Dev/Lucky/commit/2e7f1bbec46aab6d68d19aa07a4a503027d304bd))
* **bot:** healthcheck gateway readiness instead of redis tcp ping
([#1047](https://github.com/LucasSantana-Dev/Lucky/issues/1047))
([5ce2514](https://github.com/LucasSantana-Dev/Lucky/commit/5ce2514d5fe6b73b8f1c869a63544e7f02977cb1))
* **bot:** lastfm-similar score crushed ~100x by match/100
([#1269](https://github.com/LucasSantana-Dev/Lucky/issues/1269))
([f6bba72](https://github.com/LucasSantana-Dev/Lucky/commit/f6bba729f3943edfb2e370015d93dc4b6a58d83b))
* **bot:** process threadcreate regardless of newlycreated flag
([#1606](https://github.com/LucasSantana-Dev/Lucky/issues/1606))
([be08786](https://github.com/LucasSantana-Dev/Lucky/commit/be08786eeac2b1213a58f1487d7d5b5eba53686a))
* **bot:** queue summary position is milliseconds, not seconds
([#1202](https://github.com/LucasSantana-Dev/Lucky/issues/1202))
([#1330](https://github.com/LucasSantana-Dev/Lucky/issues/1330))
([efa9800](https://github.com/LucasSantana-Dev/Lucky/commit/efa98005cafc9e4cbacfcd8cc7f28b7bd5e26b6e))
* **bot:** reject timeout in session restore race instead of resolving
null ([#1170](https://github.com/LucasSantana-Dev/Lucky/issues/1170))
([2456fb9](https://github.com/LucasSantana-Dev/Lucky/commit/2456fb9bc38c291c870e244e42ebbc26d119acdd))
* **bot:** skip startup session restore into empty voice channel
([#1469](https://github.com/LucasSantana-Dev/Lucky/issues/1469))
([dbcc08c](https://github.com/LucasSantana-Dev/Lucky/commit/dbcc08ce511b0f19b1bc2c490c584113485e59b3))
* **bot:** startup session restore scans postgres, not redis
([#1119](https://github.com/LucasSantana-Dev/Lucky/issues/1119))
([2636ba1](https://github.com/LucasSantana-Dev/Lucky/commit/2636ba1f8b0e379f7c3068778055cb6e643a9b0d))
* **bot:** stop all schedulers/timers on shutdown
([#1197](https://github.com/LucasSantana-Dev/Lucky/issues/1197))
([#1205](https://github.com/LucasSantana-Dev/Lucky/issues/1205))
([7180579](https://github.com/LucasSantana-Dev/Lucky/commit/71805799de105dd1cf33e158c958857035d502cc))
* **bot:** tear down Discord client on initializer step failure
([#1180](https://github.com/LucasSantana-Dev/Lucky/issues/1180))
([d81ac68](https://github.com/LucasSantana-Dev/Lucky/commit/d81ac683a3235a1b3fe39fa39eccb7aa971ce52a))
* **bot:** thread real Client into endGiveaway
([#1383](https://github.com/LucasSantana-Dev/Lucky/issues/1383))
([#1388](https://github.com/LucasSantana-Dev/Lucky/issues/1388))
([d3b274a](https://github.com/LucasSantana-Dev/Lucky/commit/d3b274afa694ae8b35e3052ba8a366afee32d98f))
* **bot:** validate text-based channel before send in embed command
([#1253](https://github.com/LucasSantana-Dev/Lucky/issues/1253))
([5add32f](https://github.com/LucasSantana-Dev/Lucky/commit/5add32f7e4d88164b89fe73e7ea8c9dcaf17f909))
* **bot:** wire role exclusion enforcement + guildmembers intent
([#1668](https://github.com/LucasSantana-Dev/Lucky/issues/1668))
([e8145af](https://github.com/LucasSantana-Dev/Lucky/commit/e8145afe9f4765b927b077d3df471a2280087e14))
* **bot:** wire setupwebmusichandler at startup
([#1321](https://github.com/LucasSantana-Dev/Lucky/issues/1321))
([#1351](https://github.com/LucasSantana-Dev/Lucky/issues/1351))
([dc68e7e](https://github.com/LucasSantana-Dev/Lucky/commit/dc68e7efce26598161380c60bedb1a728a72748d))
* bound external calls — Discord-429 storm + Musical-Taste hang
([#1141](https://github.com/LucasSantana-Dev/Lucky/issues/1141))
([739d653](https://github.com/LucasSantana-Dev/Lucky/commit/739d653271a12b5a278d141545c3b5618f39f50c))
* bound music queue params, type rolegroup mapping, harden ci lint
([#1588](https://github.com/LucasSantana-Dev/Lucky/issues/1588))
([309d5d9](https://github.com/LucasSantana-Dev/Lucky/commit/309d5d9c9bbb04d2362fd0fe65e2db0f677169c1))
* **ci:** add bot to required containers and replace dead unhealthy grep
([#1054](https://github.com/LucasSantana-Dev/Lucky/issues/1054))
([b16109e](https://github.com/LucasSantana-Dev/Lucky/commit/b16109ee1de691d9d1bac69ade0168a9a69b0a75))
* **ci:** add figurinhas2026 to Vercel deploy watch
([#1063](https://github.com/LucasSantana-Dev/Lucky/issues/1063))
([b3f5e64](https://github.com/LucasSantana-Dev/Lucky/commit/b3f5e6465be5a77222ad9eccb109c2df7c169a94))
* **ci:** archive squash-merged release branch instead of failing FF
([#946](https://github.com/LucasSantana-Dev/Lucky/issues/946))
([111e860](https://github.com/LucasSantana-Dev/Lucky/commit/111e860a9efa24590ee89e975da4ab7886aaacaf))
* **ci:** cf pages deploy uses root lockfile (stops silent failure)
([#1673](https://github.com/LucasSantana-Dev/Lucky/issues/1673))
([8824fc3](https://github.com/LucasSantana-Dev/Lucky/commit/8824fc377e17bd4938b8af7d21050b2aa47b4982))
* **ci:** danger node 24 compatibility
([#1659](https://github.com/LucasSantana-Dev/Lucky/issues/1659))
([862426a](https://github.com/LucasSantana-Dev/Lucky/commit/862426a32f7d786644a02c8bde5a4c5d1e6bb6e8))
* **ci:** grant review-tools caller the scopes its reusables require
([#1424](https://github.com/LucasSantana-Dev/Lucky/issues/1424))
([c215675](https://github.com/LucasSantana-Dev/Lucky/commit/c2156759754ed65cfecb8f8fa9b25f5347522f5c))
* **ci:** hard-fail deploy on sustained 429 instead of silent oauth pass
([#1045](https://github.com/LucasSantana-Dev/Lucky/issues/1045))
([2a6b05c](https://github.com/LucasSantana-Dev/Lucky/commit/2a6b05ccaad42dbade7b4ae374e27f8661b9ac0b))
* **ci:** lockfile-hash BuildKit npm cache key to prevent esbuild
version mismatch
([#1016](https://github.com/LucasSantana-Dev/Lucky/issues/1016))
([1b3c258](https://github.com/LucasSantana-Dev/Lucky/commit/1b3c2584baf7a9361da89bf911267ca6876ff667))
* **ci:** lockfile-hash BuildKit npm cache key to prevent esbuild
version mismatch
([#1065](https://github.com/LucasSantana-Dev/Lucky/issues/1065))
([3579966](https://github.com/LucasSantana-Dev/Lucky/commit/35799666b5acc8f90b109eef03757912d192379a))
* **ci:** lockfile-hash BuildKit npm cache key to prevent esbuild
version mismatch
([#1067](https://github.com/LucasSantana-Dev/Lucky/issues/1067))
([7c5c447](https://github.com/LucasSantana-Dev/Lucky/commit/7c5c447ebb128b2ea2cb4bc717c3a3d1d8a56c6c))
* **ci:** lockfile-hash BuildKit npm cache key to prevent esbuild
version mismatch
([#1075](https://github.com/LucasSantana-Dev/Lucky/issues/1075))
([00ee269](https://github.com/LucasSantana-Dev/Lucky/commit/00ee26962d35622af8bcaeb7a84f79bddb7ce5d8))
* **ci:** lowercase image ref in yt-dlp smoke test
([e6267f5](https://github.com/LucasSantana-Dev/Lucky/commit/e6267f516dc50c417be6cbebdfe1d9b1358368c0))
* **ci:** lowercase image ref in yt-dlp smoke test
([ccb2855](https://github.com/LucasSantana-Dev/Lucky/commit/ccb2855745d1640c5a75a2aaebdc1fd61605578a))
* **ci:** make husky optional in prepare script to unblock docker builds
([#1060](https://github.com/LucasSantana-Dev/Lucky/issues/1060))
([9bf7c0e](https://github.com/LucasSantana-Dev/Lucky/commit/9bf7c0e91e671eb1347d37d1265d6a2beb376d68))
* **ci:** pin GitHub Actions to commit SHAs, scope secrets, harden
Renovate
([#1706](https://github.com/LucasSantana-Dev/Lucky/issues/1706))
([1239e28](https://github.com/LucasSantana-Dev/Lucky/commit/1239e286df0e222f4a0b39f328c622901a96f916))
* **ci:** post error commit status on deploy lock contention
([#1052](https://github.com/LucasSantana-Dev/Lucky/issues/1052))
([869d6f0](https://github.com/LucasSantana-Dev/Lucky/commit/869d6f03eae0f807befa1f712ec3a4e6c422aa9d))
* **ci:** quality/Lint green again — core rules off for bot/shared at
root lint
([#1364](https://github.com/LucasSantana-Dev/Lucky/issues/1364))
([#1365](https://github.com/LucasSantana-Dev/Lucky/issues/1365))
([cc2322f](https://github.com/LucasSantana-Dev/Lucky/commit/cc2322f0ed999ffe1aabd341b1371260ace718d5))
* **ci:** scope docker build cache per matrix service
([#1712](https://github.com/LucasSantana-Dev/Lucky/issues/1712))
([3ed9aae](https://github.com/LucasSantana-Dev/Lucky/commit/3ed9aaec6e93ec713144427d2e8290300e214c15))
* **ci:** skip docker-build validation for non-docker-relevant PRs
([#1711](https://github.com/LucasSantana-Dev/Lucky/issues/1711))
([5ea19ea](https://github.com/LucasSantana-Dev/Lucky/commit/5ea19eabf162c7ab82a1bd242979df8d8354156e))
* **ci:** surface async deploy failures via commit statuses
([#1046](https://github.com/LucasSantana-Dev/Lucky/issues/1046))
([b0838ac](https://github.com/LucasSantana-Dev/Lucky/commit/b0838aca3cd3f7d69024619c4eb37eecea337b7f))
* **ci:** use v-prefixed trivy-action tag
([#934](https://github.com/LucasSantana-Dev/Lucky/issues/934))
([ffae3cf](https://github.com/LucasSantana-Dev/Lucky/commit/ffae3cfbb0941c6ca16a8bf387511c811cd6ed82))
* **codeql:** resolve open codeql alerts
([0e0dfbe](https://github.com/LucasSantana-Dev/Lucky/commit/0e0dfbe5c027788dcc94953a67b52bbe93cc952b))
* **compose:** tag container logs so loki labels them by name
([#1476](https://github.com/LucasSantana-Dev/Lucky/issues/1476))
([4e956df](https://github.com/LucasSantana-Dev/Lucky/commit/4e956dfaad3ab88ea4d7d1f2db5874b3535f4cdd))
* **deploy:** derive require_running_containers from docker compose
([#1601](https://github.com/LucasSantana-Dev/Lucky/issues/1601))
([5715249](https://github.com/LucasSantana-Dev/Lucky/commit/571524924f2efe0cd7b5ab0d990631a86f220378))
* **deploy:** persist last-good across deploys (gitignore it)
([#1234](https://github.com/LucasSantana-Dev/Lucky/issues/1234))
([44f6487](https://github.com/LucasSantana-Dev/Lucky/commit/44f6487bf6fad06c4bcab3f688feb7f974696719))
* **deploy:** pin to short image tag; never build under a pinned tag
([#1232](https://github.com/LucasSantana-Dev/Lucky/issues/1232))
([d874d59](https://github.com/LucasSantana-Dev/Lucky/commit/d874d59bf8bb49588c08c51226975cc80b8827b3))
* **deploy:** probe nginx health on container port 8080 not 80
([#1236](https://github.com/LucasSantana-Dev/Lucky/issues/1236))
([918689d](https://github.com/LucasSantana-Dev/Lucky/commit/918689dc29c7bd7163caa5bec2b0336cb508fd43))
* **deploy:** read webhook hooks.json from live directory mount
([#1231](https://github.com/LucasSantana-Dev/Lucky/issues/1231))
([e559f42](https://github.com/LucasSantana-Dev/Lucky/commit/e559f4260bf115400ecde124b6406275e42fd888))
* **deploy:** record auto-rollback last-good from image commit-sha
([#1235](https://github.com/LucasSantana-Dev/Lucky/issues/1235))
([9cc21e7](https://github.com/LucasSantana-Dev/Lucky/commit/9cc21e76d49a8fb0f3ea74a73ce7dcf59f460861))
* **deploy:** ship prisma cli in production images + unify esbuild
([#1080](https://github.com/LucasSantana-Dev/Lucky/issues/1080))
([8e422b3](https://github.com/LucasSantana-Dev/Lucky/commit/8e422b391dd939f12abf9dea5ab2501cd5777968))
* **deploy:** verify + cache-correct the frontend so deploys actually
reach users
([#1576](https://github.com/LucasSantana-Dev/Lucky/issues/1576))
([9178576](https://github.com/LucasSantana-Dev/Lucky/commit/9178576c2c3d9b2743b5417f2516f6d9208cacd8))
* **deploy:** wire GITHUB_DEPLOY_STATUS_TOKEN into lucky-webhook
container
([#1597](https://github.com/LucasSantana-Dev/Lucky/issues/1597))
([ad4b7ed](https://github.com/LucasSantana-Dev/Lucky/commit/ad4b7ed32a66ab945ed610333a58131379b7cc08))
* **deps:** bump multer to 2.2.0 to fix high-severity dos advisory
([#1493](https://github.com/LucasSantana-Dev/Lucky/issues/1493))
([4d57ac8](https://github.com/LucasSantana-Dev/Lucky/commit/4d57ac87b0b016ffd8d79306c0705f1294c9a37a))
* **deps:** bump qs to 6.15.2 and hono to 4.12.25 (audit)
([#1295](https://github.com/LucasSantana-Dev/Lucky/issues/1295))
([ae5d949](https://github.com/LucasSantana-Dev/Lucky/commit/ae5d949c2f756eb554a24621c952cd8021b7a580))
* **deps:** pin piscina 4.9.3 for high-severity rce advisory
([#1504](https://github.com/LucasSantana-Dev/Lucky/issues/1504))
([10b68e6](https://github.com/LucasSantana-Dev/Lucky/commit/10b68e6597bae7c39286662293f1587780df0a30))
* **deps:** resolve npm audit vulnerabilities (1 critical + 5 moderate)
([#1708](https://github.com/LucasSantana-Dev/Lucky/issues/1708))
([e2b07bf](https://github.com/LucasSantana-Dev/Lucky/commit/e2b07bfece040e3f65bf0e62ff5a7565b93b670d))
* **docker:** add C toolchain to deps-production for opus source-build
fallback
([#1310](https://github.com/LucasSantana-Dev/Lucky/issues/1310))
([5ed7f11](https://github.com/LucasSantana-Dev/Lucky/commit/5ed7f11e0747eef6f303d1aa8f3f1fe8889eb351))
* **docker:** bump npm to patch bundled undici/tar CVEs in base image
([#1709](https://github.com/LucasSantana-Dev/Lucky/issues/1709))
([a635a0c](https://github.com/LucasSantana-Dev/Lucky/commit/a635a0c33c77ff99cd27369d3a8398ea51cc96de))
* **docker:** copy CHANGELOG.md into frontend build context
([#937](https://github.com/LucasSantana-Dev/Lucky/issues/937))
([44f302e](https://github.com/LucasSantana-Dev/Lucky/commit/44f302e3faf8fd448558660e964ac93aaf5ef88f))
* **download:** drop invalid --extract-flat flag from yt-dlp download
([#1488](https://github.com/LucasSantana-Dev/Lucky/issues/1488))
([9d29144](https://github.com/LucasSantana-Dev/Lucky/commit/9d291441d59ce7a01e717ad04f6844ac3d8b7947))
* **frontend:** default add-to-discord cta to public application id
([#1495](https://github.com/LucasSantana-Dev/Lucky/issues/1495))
([efda71e](https://github.com/LucasSantana-Dev/Lucky/commit/efda71e38c7152abb0d5fca2a708cbe960720ec4))
* **frontend:** fix CF Pages API routing and remove Vercel Analytics
([#1596](https://github.com/LucasSantana-Dev/Lucky/issues/1596))
([2902984](https://github.com/LucasSantana-Dev/Lucky/commit/2902984146f090eca210149eb84ea6e593c40747))
* **frontend:** flush moderation empty state (stray dark band)
([#1693](https://github.com/LucasSantana-Dev/Lucky/issues/1693))
([c64041a](https://github.com/LucasSantana-Dev/Lucky/commit/c64041a9e5aa25d411ad5115cfa0038d779a693b))
* **frontend:** healthcheck uses busybox wget, not bash /dev/tcp
([#1106](https://github.com/LucasSantana-Dev/Lucky/issues/1106))
([ec7a449](https://github.com/LucasSantana-Dev/Lucky/commit/ec7a449140eb53be135db321d0b9ca8274aafd30))
* guard unsafe external API response handling
([#1207](https://github.com/LucasSantana-Dev/Lucky/issues/1207))
([#1217](https://github.com/LucasSantana-Dev/Lucky/issues/1217))
([3b26b72](https://github.com/LucasSantana-Dev/Lucky/commit/3b26b7279312643523533d945ee0d2e85d7b9337))
* **help:** split large command categories across embed fields
([#1489](https://github.com/LucasSantana-Dev/Lucky/issues/1489))
([0fa5786](https://github.com/LucasSantana-Dev/Lucky/commit/0fa578646fe0671eda85eb6b36db076c6e0fafb1))
* **infra:** route staging via host port
([#1548](https://github.com/LucasSantana-Dev/Lucky/issues/1548))
([fe5b153](https://github.com/LucasSantana-Dev/Lucky/commit/fe5b153b2ebadbfaf20f67c95e964f3f06d443fe))
* **infra:** staging deploy git ownership
([#1554](https://github.com/LucasSantana-Dev/Lucky/issues/1554))
([de5a322](https://github.com/LucasSantana-Dev/Lucky/commit/de5a3220dac6bd517280405c69097eaca8885380))
* **infra:** staging deploy health check
([#1556](https://github.com/LucasSantana-Dev/Lucky/issues/1556))
([fda6eea](https://github.com/LucasSantana-Dev/Lucky/commit/fda6eea11e2da3f215538850445d4b9dcfd9e394))
* **logs:** serialize server logs with level, message and actor
([#1677](https://github.com/LucasSantana-Dev/Lucky/issues/1677))
([acd8fe3](https://github.com/LucasSantana-Dev/Lucky/commit/acd8fe31493931cd1cba5e93ed46d93bd35fe514))
* **middleware:** resolve guildAccess non-atomic session+context
staleness window
([5a64dd1](https://github.com/LucasSantana-Dev/Lucky/commit/5a64dd17bb1d9143c82eee49821c38e75a7f13ab))
* **moderation:** route context menus in the live event handler
([#1517](https://github.com/LucasSantana-Dev/Lucky/issues/1517))
([0232237](https://github.com/LucasSantana-Dev/Lucky/commit/0232237d9912dac9281b2e53902c4487542b98ce))
* **music:** re-target music guild FKs to discordId
([#1270](https://github.com/LucasSantana-Dev/Lucky/issues/1270))
([765d9d8](https://github.com/LucasSantana-Dev/Lucky/commit/765d9d81d2b3e776b24d70e8089056f010dd6351))
* **music:** surface youtube unavailability instead of generic errors
([#1146](https://github.com/LucasSantana-Dev/Lucky/issues/1146))
([0e66fe2](https://github.com/LucasSantana-Dev/Lucky/commit/0e66fe2e2f7f70dcdabb81e18a25cff0bdf32a50))
* **player:** warn not error on bridge exhaustion for unplayable tracks
([#1507](https://github.com/LucasSantana-Dev/Lucky/issues/1507))
([d7a4a58](https://github.com/LucasSantana-Dev/Lucky/commit/d7a4a5885ffa74fe5cbf22819df08a4dbc53e729))
* **play:** isolate post-play background ops
([#1085](https://github.com/LucasSantana-Dev/Lucky/issues/1085))
([#1101](https://github.com/LucasSantana-Dev/Lucky/issues/1101))
([ba994c4](https://github.com/LucasSantana-Dev/Lucky/commit/ba994c428253737826bbd10540112714cc0d4c6f))
* **reaction-roles:** PUT panel edit deletes mappings by cuid not
snowflake
([#1675](https://github.com/LucasSantana-Dev/Lucky/issues/1675))
([#1676](https://github.com/LucasSantana-Dev/Lucky/issues/1676))
([a51c70f](https://github.com/LucasSantana-Dev/Lucky/commit/a51c70f77dac207c5c76c8b1155f77a033a5e04b))
* **reaction-roles:** validate roleIds, fix update rollback, serialize
concurrent appends
([#1587](https://github.com/LucasSantana-Dev/Lucky/issues/1587))
([6873ac8](https://github.com/LucasSantana-Dev/Lucky/commit/6873ac8d398aa26f50d6a204d7d1de83557a402e))
* **release:** set group-pull-request-title-pattern so releases auto-tag
([#1521](https://github.com/LucasSantana-Dev/Lucky/issues/1521))
([b0e0f5f](https://github.com/LucasSantana-Dev/Lucky/commit/b0e0f5f40497c4be98135acb66b24a79e6763df2))
* **release:** set pull-request-title-pattern to include version
([#1514](https://github.com/LucasSantana-Dev/Lucky/issues/1514))
([cde12ec](https://github.com/LucasSantana-Dev/Lucky/commit/cde12ecc9881b1ca496fb0112e98d3bae2910c8e))
* **release:** tag-guard reconciles autorelease label
([#1561](https://github.com/LucasSantana-Dev/Lucky/issues/1561))
([#1583](https://github.com/LucasSantana-Dev/Lucky/issues/1583))
([6505f44](https://github.com/LucasSantana-Dev/Lucky/commit/6505f446b55fd2eb5ca5c87c5d105f2da975e28c))
* resolve discord 429 rate-limit storm and archived thread crash
([#1078](https://github.com/LucasSantana-Dev/Lucky/issues/1078))
([e79858e](https://github.com/LucasSantana-Dev/Lucky/commit/e79858ec7505b441bf538e7c38452476bd3f78f1))
* resolve Prettier syntax error in queueManipulation.spec.ts
([#985](https://github.com/LucasSantana-Dev/Lucky/issues/985))
([7cf4c83](https://github.com/LucasSantana-Dev/Lucky/commit/7cf4c83ee45da7ade4559957ff7a707a3b15871a))
* **schema:** add unique guild+thread constraint to GuildForumThread
([#1607](https://github.com/LucasSantana-Dev/Lucky/issues/1607))
([6c4c8ab](https://github.com/LucasSantana-Dev/Lucky/commit/6c4c8ab9a7488149dece8f486160ca3125d17a4e))
* **security:** bump vite 8.0.16 + form-data 4.0.6 for high advisories
([#1457](https://github.com/LucasSantana-Dev/Lucky/issues/1457))
([58d21d5](https://github.com/LucasSantana-Dev/Lucky/commit/58d21d56ad437bb5526dd5dcc9e5af3603d4b310))
* **security:** pass staging webhook secret via env not argv
([#1600](https://github.com/LucasSantana-Dev/Lucky/issues/1600))
([d12efc5](https://github.com/LucasSantana-Dev/Lucky/commit/d12efc519570026cf9a6f1b075d57b99d41898e2))
* **security:** redact operational diagnostics from
/api/health/auth-config
([#1710](https://github.com/LucasSantana-Dev/Lucky/issues/1710))
([e1b6b61](https://github.com/LucasSantana-Dev/Lucky/commit/e1b6b61c493eabd4d633d9600c46ad29a3ffc781))
* **security:** redact secrets/PII from logs
([#1208](https://github.com/LucasSantana-Dev/Lucky/issues/1208))
([#1220](https://github.com/LucasSantana-Dev/Lucky/issues/1220))
([2a09f90](https://github.com/LucasSantana-Dev/Lucky/commit/2a09f900c87e9ca1ef8c50a5ece6b1d7eecbc11e))
* **security:** resolve CodeQL/Semgrep findings (XSS, cookie, log
injection, nginx headers)
([#1707](https://github.com/LucasSantana-Dev/Lucky/issues/1707))
([3a30135](https://github.com/LucasSantana-Dev/Lucky/commit/3a301358d7cae1091bcbb79a1ff17c638317e641))
* **security:** verify bot authorship before trusting slug marker
([#1599](https://github.com/LucasSantana-Dev/Lucky/issues/1599))
([23bf73a](https://github.com/LucasSantana-Dev/Lucky/commit/23bf73a3e7db054d63ab7faea60db66124fbbfe9))
* **shared:** drop buggy token-overlap util + optimize levenshtein
([#1246](https://github.com/LucasSantana-Dev/Lucky/issues/1246))
([5b65d47](https://github.com/LucasSantana-Dev/Lucky/commit/5b65d4768f0e3bf19ca9e211957ce2fec07ef4f6))
* **shared:** env-isolate environment.test.ts (no secret dumps)
([#1292](https://github.com/LucasSantana-Dev/Lucky/issues/1292))
([588037c](https://github.com/LucasSantana-Dev/Lucky/commit/588037cf8b3a7424ad922b15d28aeb8548eb082e))
* **shared:** export ./utils/monitoring subpath — fixes lucky-bot
crash-loop
([#1105](https://github.com/LucasSantana-Dev/Lucky/issues/1105))
([2c959f3](https://github.com/LucasSantana-Dev/Lucky/commit/2c959f3d9765acdedab969faaaa229869a657ded))
* **shared:** export config/* subpath for prod esm resolution
([#1250](https://github.com/LucasSantana-Dev/Lucky/issues/1250))
([f4167a8](https://github.com/LucasSantana-Dev/Lucky/commit/f4167a80f2b78a693e9aacf5744358137e400f17))
* **shared:** export utils/support subpath for prod esm resolution
([#1248](https://github.com/LucasSantana-Dev/Lucky/issues/1248))
([8d3c092](https://github.com/LucasSantana-Dev/Lucky/commit/8d3c09265997e360e050d9006b55e12c8320abf3))
* **shared:** guard JSON.parse on embed data in CustomCommandService
([#1168](https://github.com/LucasSantana-Dev/Lucky/issues/1168))
([1c46b55](https://github.com/LucasSantana-Dev/Lucky/commit/1c46b557d7bcd5c847aca14c0562cae8a9bb77a0))
* **shared:** log db error in feature-toggle override read
([#1286](https://github.com/LucasSantana-Dev/Lucky/issues/1286))
([#1411](https://github.com/LucasSantana-Dev/Lucky/issues/1411))
([0dfc409](https://github.com/LucasSantana-Dev/Lucky/commit/0dfc4091c1e688569f656851b39f06716eecb4a0))
* **shared:** make LevelService.addXP atomic to prevent lost XP under
concurrency
([#1178](https://github.com/LucasSantana-Dev/Lucky/issues/1178))
([d1edffe](https://github.com/LucasSantana-Dev/Lucky/commit/d1edffe1c410b7393e184c796edeec83e4a17cae))
* **shared:** make read-then-write service paths atomic
([#1199](https://github.com/LucasSantana-Dev/Lucky/issues/1199))
([#1340](https://github.com/LucasSantana-Dev/Lucky/issues/1340))
([ba1b840](https://github.com/LucasSantana-Dev/Lucky/commit/ba1b840accb4858837c0b46e8018b4d1bcd53291))
* **shared:** normalize embed template name on gettemplate
([#1327](https://github.com/LucasSantana-Dev/Lucky/issues/1327))
([#1350](https://github.com/LucasSantana-Dev/Lucky/issues/1350))
([d221b57](https://github.com/LucasSantana-Dev/Lucky/commit/d221b577db03473f0930747362c65861aae501fa))
* **shared:** safe env parsing via parseIntEnv helper
([#1209](https://github.com/LucasSantana-Dev/Lucky/issues/1209))
([#1335](https://github.com/LucasSantana-Dev/Lucky/issues/1335))
([32e3684](https://github.com/LucasSantana-Dev/Lucky/commit/32e36849ac0b8c9b3e839fc24a97f1f6c1972376))
* **shared:** surface Redis client init errors instead of silent swallow
([#1176](https://github.com/LucasSantana-Dev/Lucky/issues/1176))
([157c14e](https://github.com/LucasSantana-Dev/Lucky/commit/157c14ec558a5fffd54e34400eb6a0b02a5a2763))
* **shared:** validate EmbedData shape with Zod before storing custom
commands
([#1179](https://github.com/LucasSantana-Dev/Lucky/issues/1179))
([7419b0e](https://github.com/LucasSantana-Dev/Lucky/commit/7419b0e1f4a4547b8a019c184fe63a41c2dcb017))
* **shared:** validate guildautomation json on read
([#1194](https://github.com/LucasSantana-Dev/Lucky/issues/1194))
([#1346](https://github.com/LucasSantana-Dev/Lucky/issues/1346))
([93d9eea](https://github.com/LucasSantana-Dev/Lucky/commit/93d9eea104c989485b125eb2de494a8032c2f6b4))
* **shared:** wrap ModerationService.createCase in transaction to
prevent duplicate case numbers
([#1167](https://github.com/LucasSantana-Dev/Lucky/issues/1167))
([be52580](https://github.com/LucasSantana-Dev/Lucky/commit/be5258049b6826c4a7141d4b00a8b6f6777d332e))
* **sonar:** clear main reliability gate - s1244 and tailwind v4 fps
([#1671](https://github.com/LucasSantana-Dev/Lucky/issues/1671))
([c12059d](https://github.com/LucasSantana-Dev/Lucky/commit/c12059dfc059db1915706723659812b088c5f34c))
* **spotify:** log oauth token-exchange failures
([#1286](https://github.com/LucasSantana-Dev/Lucky/issues/1286) track b)
([8306c35](https://github.com/LucasSantana-Dev/Lucky/commit/8306c35b19587d5b59e8092c0e245a2ed087b658))
* **telemetry:** un-silence skip-reason emoji prefill errors
([#1660](https://github.com/LucasSantana-Dev/Lucky/issues/1660))
([5eabbd2](https://github.com/LucasSantana-Dev/Lucky/commit/5eabbd2bad04ee92885766ba7184219ea17e5758))
* **test:** close open handles causing jest force-exit in bot suite
([#1605](https://github.com/LucasSantana-Dev/Lucky/issues/1605))
([cf2a026](https://github.com/LucasSantana-Dev/Lucky/commit/cf2a026d4852e2889eb18e1a0ce2389d73da3333))
* **twitch:** add debug logging for skipped channel notifications
([#947](https://github.com/LucasSantana-Dev/Lucky/issues/947))
([0dcf1c2](https://github.com/LucasSantana-Dev/Lucky/commit/0dcf1c2e5c32cda64f80e21f20a9e888715f6ca8))
* **twitch:** re-subscribe to EventSub after unexpected reconnect
([#870](https://github.com/LucasSantana-Dev/Lucky/issues/870))
([#1395](https://github.com/LucasSantana-Dev/Lucky/issues/1395))
([78a30f3](https://github.com/LucasSantana-Dev/Lucky/commit/78a30f31b9e97bd9e5fe86397ce5bdca272c0c10))
* **twitch:** refresh bot subscriptions on web add/remove
([#870](https://github.com/LucasSantana-Dev/Lucky/issues/870))
([939d4b3](https://github.com/LucasSantana-Dev/Lucky/commit/939d4b3721158d0c52c2f9c7944709baf38d35c0))
* **ui:** address CodeRabbit findings on
[#856](https://github.com/LucasSantana-Dev/Lucky/issues/856)
([56f2c82](https://github.com/LucasSantana-Dev/Lucky/commit/56f2c823eeec6bf2d468595fec509284b31e82da))
* **web:** clear auth check promise on settle, not via 100ms timer
([#1311](https://github.com/LucasSantana-Dev/Lucky/issues/1311))
([5cc8eef](https://github.com/LucasSantana-Dev/Lucky/commit/5cc8eefd7d83a5319175b056616ffe097a031299))
* **web:** GuildAutomation error state when both fetches reject
([#1144](https://github.com/LucasSantana-Dev/Lucky/issues/1144))
([f789aa3](https://github.com/LucasSantana-Dev/Lucky/commit/f789aa3e0e110958a0d56230c8273caaca4e6a85))
* **web:** language dropdown switches app language via radio group
([d4fdd98](https://github.com/LucasSantana-Dev/Lucky/commit/d4fdd9880f1246eb985b1214899302eb7b115192))
* **web:** relabel landing RepoCard stats to real servers/users
([#1145](https://github.com/LucasSantana-Dev/Lucky/issues/1145))
([2d63983](https://github.com/LucasSantana-Dev/Lucky/commit/2d6398374f9f0081575992d978c7f57f22005858))
* **web:** remove dead featuresStore toggle code + rollback on failure
([#1147](https://github.com/LucasSantana-Dev/Lucky/issues/1147))
([f8697fb](https://github.com/LucasSantana-Dev/Lucky/commit/f8697fb36532a76f5106dd0fb1bc9d13e5351c71))
* **web:** report swallowed member-context fetch error to Sentry
([#1286](https://github.com/LucasSantana-Dev/Lucky/issues/1286) B3)
([#1416](https://github.com/LucasSantana-Dev/Lucky/issues/1416))
([85f141d](https://github.com/LucasSantana-Dev/Lucky/commit/85f141d7926ef9eeec4a1195dda9702df25d7c02))
* **web:** route handled errors to Sentry, enforce no-console
([#1296](https://github.com/LucasSantana-Dev/Lucky/issues/1296))
([a34e777](https://github.com/LucasSantana-Dev/Lucky/commit/a34e777d1627ad3a2715b49f211c4b7bd3e74266))
* **web:** surface swallowed fetch errors instead of silent catch
([#1254](https://github.com/LucasSantana-Dev/Lucky/issues/1254))
([6afb0cd](https://github.com/LucasSantana-Dev/Lucky/commit/6afb0cd4f1a81f5c5e1616c7925c7bc75b9524b6))
### Performance Improvements
* **bot:** bound autoplay Maps + parallelize replenisher awaits
([#1215](https://github.com/LucasSantana-Dev/Lucky/issues/1215))
([1e55afa](https://github.com/LucasSantana-Dev/Lucky/commit/1e55afa125acbb60f0b1d28ff9c168a5e32b8ead))
* **bot:** bound external scrobbler track cache with lru+ttl
([#1282](https://github.com/LucasSantana-Dev/Lucky/issues/1282))
([#1316](https://github.com/LucasSantana-Dev/Lucky/issues/1316))
([7f29efc](https://github.com/LucasSantana-Dev/Lucky/commit/7f29efce0ea9ad0b6ad1dff6edfae57d4f15b2f8))
* bound unbounded findMany queries
([#1206](https://github.com/LucasSantana-Dev/Lucky/issues/1206))
([#1214](https://github.com/LucasSantana-Dev/Lucky/issues/1214))
([cdc0082](https://github.com/LucasSantana-Dev/Lucky/commit/cdc0082b64f407c313850e00864055b669bec3d8))
* **shared:** batch recommendation telemetry counts in one groupBy
([#1308](https://github.com/LucasSantana-Dev/Lucky/issues/1308))
([e5a5973](https://github.com/LucasSantana-Dev/Lucky/commit/e5a5973d9c25d5926ab576b13265fe05c2d87032))
</details>
---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Release 2.33.0 ships smarter autoplay, new role management in the
dashboard, better moderation and Twitch integrations, and stronger
observability/security. It also includes wide-ranging fixes and
performance improvements across bot, backend, and web.
- **New Features**
- Autoplay scoring upgrades: implicit dislike penalty, recency decay,
replay boost, and evaluation harness.
- Dashboard: role groups and reaction roles management with editor
(emoji picker, media, import/export).
- Moderation and guild tools: move message via context menu, batch
operations (bulk move), AFK, reminders, giveaways, smart custom
commands, starboard seeding.
- Integrations: Twitch follower/subscriber role sync and new EventSub
events; RSS bridge and weekly digest.
- Backend/Web: support intake with admin views, Postgres session store,
server logs/settings pages, previous-track command, per-route SEO and
sitemap.
- Observability/Security: request-id correlation, deploy markers/alerts,
CSP headers and violation collection.
- **Bug Fixes**
- Timeouts and guardrails on external calls with graceful degradation;
mitigations for Discord 429 storms.
- Hardening for reaction roles, role writes, guild route validation,
JSON parsing, and DB constraints; atomic write paths.
- Bot stability: safer session restore and shutdown, extractor
registration, clearer YouTube errors, accurate previous button replies.
- CI/CD and deploy reliability: SHA-pinned deploys, health probes, cache
correctness, pinned actions, verified frontend caching.
- Security: dependency updates, redacted logs/health output, and
CodeQL/Semgrep findings resolved.
<sup>Written for commit 22727a164df9bd407b3493dd3459ca46984664c4.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/LucasSantana-Dev/Lucky/pull/1733?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. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Added release notes for version 2.33.0, highlighting new features, bug
fixes, and performance improvements.
* **Chores**
* Updated the project version to 2.33.0.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->



Batch / bulk-operation framework (Phases 0–1)
Destructive interaction gate
non-destructive-confirmedoverride:/bulk-move-messagesis not yet deployed — the bot runs frommain, so the slash command is unavailable for pre-merge smoke testing. The PR body note below confirms this ("staging is dashboard-only, no bot"). The command ships a mandatory two-step confirmation gate and a--dry-runpreview mode; no messages are moved without explicit confirmation. Live smoke will be performed on the first guild deployment after merge.Adds a reusable batch-operation framework and its flagship command. Designed via /brainstorming → /deep-research; see ADR
decisions/2026-06-23-batch-operations-bullmq.md.Phase 0 — infrastructure
BatchJob+BatchJobItemmodels (+ migration); resumable vianextCursor.@lucky/sharedbatch services:BatchJobService(CRUD + crash-safe checkpoint),ScopeResolver,ProgressReporter,PermissionChecker,BatchJobExecutorinterface.clientReady, graceful-degrades if Redis is down).Phase 1 — flagship
/bulk-move-messagesChannelMoveBatchExecutor— re-posts messages source→dest (reusing move-message's embed/attachment helpers), checkpointsnextCursorbefore the delete (no duplicate re-posts on crash), honors cancel, resumes from cursor./bulk-move-messages(scope: all/count/user/date_range/contains + dry-run + confirmation gate) and/batch-resume.batchJobsroute (list/detail/progress/cancel) + frontend Batch Jobs dashboard (live progress + cancel) with en + pt-BR i18n.Constraints handled (Discord)
No native move → re-post + delete;
bulkDelete<14d/100-cap; a 5k-message move ≈ 30–60 min / 10k+ requests, so jobs run on the background worker (not the 15-min interaction window).Tests / gates
build:shared, tsc ×3 green. New: 57 bot (executor 95%, commands 90/100%), 13 backend, 15 frontend. Regression: bot 2669 / backend 1169 / frontend 854 / shared 1312, 0 failures.
Follow-ups
Note: staging is dashboard-only (no bot), so the Batch Jobs page is reviewable on staging; the slash commands run post-merge.
Summary by cubic
Adds a reusable batch/bulk-operations framework powered by an in‑process
bullmqworker and the flagship/bulk-move-messagescommand. Jobs are resumable, cancelable, and show live progress via a new Batch Jobs dashboard and backend API.New Features
nextCursorand shared services in@lucky/shared(BatchJobService,ScopeResolver,ProgressReporter,PermissionChecker, executor interface + registry).bullmqqueue/worker using existing Redis; auto-starts on client ready; degrades gracefully if Redis is unavailable./bulk-move-messageswith scopes (all/count/user/date_range/contains), dry‑run, and a confirmation gate; re‑posts then deletes; cancel/resume via/batch-resume./api/guilds/:guildId/batch-jobs(list/detail/progress/cancel) and a Batch Jobs page (/batch-jobs) with search/filter, pagination, live progress, and cancel (en, pt‑BR).Bug Fixes
clientStore; used by the channel‑move executor.removeOnComplete/removeOnFailto avoid stale dedupe; correct Redis progress mapping; stricterbullmqJobtyping.date_rangeafter JSON roundtrip; widen toDate|string.BatchJobService: fix Prisma JSON parsing; addincludeItemsfor details and avoid eager item loads in lists.@lucky/shared./services/batch.Written for commit 85e11f5. Summary will update on new commits.
Summary by CodeRabbit