Skip to content

feat: add guild automation control-plane core - #192

Merged
LucasSantana-Dev merged 12 commits into
mainfrom
feature/guild-automation-control-plane
Mar 13, 2026
Merged

LucasSantana-Dev merged 12 commits into
mainfrom
feature/guild-automation-control-plane

Conversation

@LucasSantana-Dev

@LucasSantana-Dev LucasSantana-Dev commented Mar 13, 2026 •

Copy link
Copy Markdown
Owner

Scope

Track A of root-delta packaging: automation control-plane only (backend/shared/bot/prisma).

Included

  • Guild automation routes and management schemas
  • Criativaria preset and automod template APIs
  • Bot /guildconfig command + management/moderation/automod command registration path
  • Shared automation manifest/diff/service types and Prisma migration (guild_automation_manifests, guild_automation_runs, guild_automation_drifts)
  • Targeted backend/bot automation tests
  • README/CHANGELOG automation notes

Verification

  • npm run test --workspace=packages/backend -- tests/integration/routes/guildAutomation.test.ts tests/integration/routes/guilds.test.ts
  • npm run test --workspace=packages/bot -- src/functions/management/commands/guildconfig.spec.ts src/register.spec.ts
  • npm run type:check
  • npm run lint

Summary by CodeRabbit

  • New Features

    • Added automod templates system with a pre-configured "balanced" template for easier moderation setup.
    • Introduced Criativaria guild automation preset for simplified server configuration.
    • Added new management endpoints for viewing and applying automod templates, plus accessing guild channels.
  • Improvements

    • Streamlined automation configuration with preset-based setup options.

@vercel

vercel Bot commented Mar 13, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lucky Ready Ready Preview, Comment Mar 13, 2026 3:01am

Request Review

@netlify

netlify Bot commented Mar 13, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for regal-bunny-0c8efe ready!

Name Link
🔨 Latest commit f320938
🔍 Latest deploy log https://app.netlify.com/projects/regal-bunny-0c8efe/deploys/69b37dca326d7d00077ed184
😎 Deploy Preview https://deploy-preview-192--regal-bunny-0c8efe.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Mar 13, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@LucasSantana-Dev has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 10 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6a484d61-24f7-44ac-ac1a-5a5ecfc60e61

📥 Commits

Reviewing files that changed from the base of the PR and between bb20fa5 and f320938.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • README.md
📝 Walkthrough

Walkthrough

This PR introduces new management API endpoints for automod templates and guild channel access, adds a Criativaria preset builder, refactors the guild automation execution layer by removing the GuildAutomationExecutionService and replacing distributed Redis-based locking with in-memory locking, simplifies various handlers through code inlining, and updates the database schema to adjust the relationship between manifests and automation runs.

Changes

Cohort / File(s) Summary
Documentation
CHANGELOG.md, README.md
Added entries documenting new automation management endpoints for channels, automod templates, and Criativaria preset bootstrap.
Automod Templates
packages/backend/src/constants/automodTemplates.ts
Introduces TemplateSettings type, AutoModTemplate interface, domain blocklist, banned words list, and getAutoModTemplate() helper to retrieve templates by ID.
Criativaria Preset
packages/backend/src/constants/guildAutomationPresets.ts
Adds buildCriativariaPreset() function that constructs a GuildAutomationManifestDocument with moderation, automessage, and parity configurations.
Backend Route Updates
packages/backend/src/routes/guildAutomation.ts, packages/backend/src/routes/management.ts, packages/backend/src/routes/index.ts
Adds new endpoints: GET/POST for automod templates, POST for Criativaria preset apply; simplifies plan/apply flows by removing error-mapping wrappers and adding schema-based request parsing; adds channel route guard entry.
Schema & Types Refinement
packages/backend/src/schemas/management.ts, packages/shared/src/services/guildAutomation/types.ts, packages/shared/src/services/guildAutomation/manifestSchema.ts
Replaces external manifest schema import with inline definition; removes AutoModSettingsUpdate and ModerationSettingsUpdate types; makes automod and moderationSettings fields more permissive (z.record(z.unknown())); loosens status/drift type constraints from enums to strings; removes retireOnCutover from external bot parity.
Guild Automation Service Refactor
packages/shared/src/services/guildAutomation/service.ts, packages/shared/src/services/guildAutomation/diff.ts
Replaces distributed Redis locking with in-memory lock map; removes resolveActualState helper and inlines actual-state resolution; removes GuildAutomationExecutionService flow; removes permission-tightening comparison logic (isPermissionTightening); marks all updates as not protected (protected: false).
Bot Utilities Refactor
packages/bot/src/utils/guildAutomation/applyPlan.ts, packages/bot/src/utils/guildAutomation/captureGuildState.ts
Inlines multiple handler functions and removes internal helper types; consolidates error handling; simplifies onboarding fetch with catch-based null return; removes normalization helpers (normalizeAutoModSettings, normalizeModerationSettings); renames error check to isMissingOnboardingError with refined detection logic.
Command Module Exports Cleanup
packages/bot/src/functions/automod/commands/index.ts, packages/bot/src/functions/management/commands/index.ts, packages/bot/src/functions/moderation/commands/index.ts
Removes public re-exports of individual command modules (automod, customcommand, embed, automessage, serversetup, warn, mute, unmute, kick, ban, unban, caseCmd, cases, history); updates to use node: prefixed path/url imports.
Guild Config Command Rewrite
packages/bot/src/functions/management/commands/guildconfig.ts, packages/bot/src/functions/management/commands/guildconfig.spec.ts
Removes private helper types (SummaryField, ExternalBotEntry); inlines subcommand handlers (capture, plan, apply/reconcile, status, cutover) directly into main execute path; updates test scaffolding with new createInteraction helper; adds tests for guild-context validation, protected operations blocking, legacy bot cutover cleanup, and adjusted status/error messaging.
Bot Registration
packages/bot/src/register.ts, packages/bot/src/register.spec.ts
Reorders management and moderation command loading; updates test mocks to use explicit mock functions with \__esModule wrapping and generic command data assertions via arrayContaining.
Database Schema
prisma/schema.prisma, prisma/migrations/20260311193000_add_guild_automation_manifest/migration.sql
Removes bidirectional relation between GuildAutomationManifest and GuildAutomationRun; removes manifestId index on GuildAutomationRun; adds guildId index to GuildAutomationManifest.
Test Additions & Updates
packages/backend/tests/integration/routes/guildAutomation.test.ts, packages/backend/tests/integration/routes/management.test.ts, packages/backend/tests/unit/routes/index.test.ts, packages/bot/src/functions/automod/commands/index.spec.ts, packages/bot/src/functions/management/commands/index.spec.ts, packages/bot/src/functions/moderation/commands/index.spec.ts, packages/bot/src/utils/guildAutomation/applyPlan.spec.ts, packages/bot/src/utils/guildAutomation/captureGuildState.spec.ts, packages/bot/src/utils/guildAutomation/diff.spec.ts, packages/bot/src/utils/guildAutomation/manifestSchema.spec.ts
Removes GuildAutomationExecutionService tests and error-mapping tests; adds new endpoint tests for automod templates and Criativaria preset; adds channel-type mapping and cleanup error handling tests; updates command loader specs to verify source content; refactors guild state test fixtures with new ID schemas and permission representations; removes protected-update tests for commandaccess and role permission reductions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #171: Modifies the same guild automation subsystem (routes, services, schemas, types) with overlapping changes to execution flow and manifest handling.
  • PR #147: Updates backend management routes (packages/backend/src/routes/management.ts) with schema parsing and user-id validation guards in the same file.
  • PR #190: Modifies the management command loader index (packages/bot/src/functions/management/commands/index.ts) regarding excludePatterns and runtime filtering configuration.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add guild automation control-plane core' accurately describes the primary objective of this pull request, which is to introduce guild automation control-plane infrastructure including routes, schemas, templates, presets, and related service logic.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/guild-automation-control-plane
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

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/utils/guildAutomation/applyPlan.ts (1)

1-16: 🛠️ Refactor suggestion | 🟠 Major

Missing errorLog import for error handling.

The file lacks the errorLog import from @lucky/shared/utils, which is required per bot package guidelines. This is needed for proper error logging in the deletion catch block (line 185).

 import {
     autoMessageService,
     autoModService,
     manifestOnboardingToDiscordEdit,
     guildRoleAccessService,
     roleManagementService,
     updateModerationSettings,
     type GuildAutomationManifestDocument,
     type GuildAutomationPlan,
 } from '@lucky/shared/services'
+import { errorLog } from '@lucky/shared/utils'

As per coding guidelines: "Use errorLog and debugLog from @lucky/shared/utils for logging throughout the bot package."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/guildAutomation/applyPlan.ts` around lines 1 - 16, The
file is missing the required errorLog import from `@lucky/shared/utils` which is
used for error handling in the deletion catch block inside applyPlan.ts; add
errorLog to the module imports (alongside any existing imports from
'@lucky/shared/utils') so the catch block uses errorLog(...) instead of an
undefined identifier, ensuring consistent logging per bot package guidelines and
referencing the deletion catch block and functions like applyPlan where the
error is currently handled.
🧹 Nitpick comments (14)
packages/bot/src/utils/guildAutomation/applyPlan.ts (2)

168-176: Inconsistent error handling between role and channel deletion.

Role deletion (lines 173-175) has no error handling and will throw on failure, aborting the entire operation. Channel deletion (lines 183-188) catches errors and continues. Consider whether this asymmetry is intentional—if a role deletion fails mid-operation, previously applied changes won't be rolled back.

If role deletion should also be resilient:

♻️ Optional: Add consistent error handling for role deletion
         if (role.editable) {
-            await role.delete('Lucky guild automation protected-delete apply')
+            try {
+                await role.delete('Lucky guild automation protected-delete apply')
+            } catch (error) {
+                errorLog({
+                    message: 'Failed to delete role during guild automation apply',
+                    data: { guildId: guild.id, roleId: role.id, error },
+                })
+            }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/guildAutomation/applyPlan.ts` around lines 168 - 176,
Role deletion in the roles loop (the await role.delete('Lucky guild automation
protected-delete apply') call in applyPlan.ts) lacks error handling and will
abort the whole operation on failure; wrap the await role.delete(...) in a
try/catch and handle errors the same way channel deletion does (catch errors,
log them with the same logger or console, and continue) so a single failed
role.delete won't stop the rest of the automation from applying.

223-296: Function exceeds 50-line limit.

applyAutomationModules spans 73 lines, exceeding the 50-line guideline. Consider extracting module-specific handlers while keeping the orchestration simple:

♻️ Suggested refactor approach
// Extract handlers that return applied/skipped module names
async function applyOnboardingModule(
    guild: Guild,
    desired: GuildAutomationManifestDocument,
): Promise<string | null> {
    const payload = manifestOnboardingToDiscordEdit(desired.onboarding)
    if (payload) {
        await guild.editOnboarding(payload)
        return 'onboarding'
    }
    return null
}

// Similar handlers for each module...

// Then orchestrate in applyAutomationModules:
const handlers = [
    { module: 'onboarding', fn: () => applyOnboardingModule(guild, desired) },
    { module: 'roles', fn: () => applyRolesModule(guild, desired, allowProtected) },
    // ...
]

for (const { module, fn } of handlers) {
    if (shouldApplyModule(plan, module, allowProtected)) {
        const result = await fn()
        if (result) appliedModules.push(result)
    }
}

As per coding guidelines: "Functions must be less than 50 lines with cyclomatic complexity less than 10."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/guildAutomation/applyPlan.ts` around lines 223 - 296,
The applyAutomationModules function is too long; refactor by extracting each
module's logic into small handler functions (e.g., applyOnboardingModule,
applyRolesModule, applyModerationModule, applyAutoMessagesModule,
applyReactionRolesModule, applyCommandAccessModule, applyParityModule) that
return either an applied module name or a skipped message (string | null or
{applied?:string, skipped?:string}), then simplify applyAutomationModules to
iterate over a handlers array and call shouldApplyModule(plan, '<module>',
allowProtected) before invoking each handler and collecting returned
applied/skipped names; keep existing helpers (manifestOnboardingToDiscordEdit,
applyRolesAndChannels, autoModService.updateSettings, updateModerationSettings,
upsertAutoMessage, applyReactionRoleRules,
guildRoleAccessService.replaceRoleGrants) inside the new handlers so behavior is
unchanged.
packages/bot/src/utils/guildAutomation/captureGuildState.ts (1)

83-189: Function exceeds 50-line guideline.

captureGuildAutomationState spans ~106 lines, exceeding the 50-line function limit. The logic is largely declarative data mapping with low cyclomatic complexity, so this is a lower-priority refactor. If addressed, consider extracting the Promise.all calls and return object construction into smaller helper functions.

As per coding guidelines: "Functions must be less than 50 lines with cyclomatic complexity less than 10."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/guildAutomation/captureGuildState.ts` around lines 83
- 189, captureGuildAutomationState is too long; split its responsibilities into
smaller helpers: extract the async parallel fetch block (the Promise.all that
assigns automodSettings, moderationSettings, welcomeMessage, leaveMessage,
reactionRoleMessages, exclusiveRoles, roleGrants, parity) into a new helper like
fetchGuildAutomationData(guildId, botUserId) that returns a typed object, and
move the large return object construction into a buildGuildManifest(guild,
onboarding, fetchedData, roles, channels) helper; keep
captureGuildAutomationState to calling those helpers, computing roles/channels,
and returning the result. Reference the existing symbols
captureGuildAutomationState, captureParity, autoModService.getSettings,
autoMessageService.getWelcomeMessage/getLeaveMessage,
reactionRolesService.listReactionRoleMessages,
roleManagementService.listExclusiveRoles, guildRoleAccessService.listRoleGrants
in the new helpers so locating code is straightforward.
packages/shared/src/services/guildAutomation/manifestSchema.ts (1)

120-125: Schema relaxation removes structural validation for moderation settings.

Changing automod and moderationSettings from typed schemas to z.record(z.unknown()) accepts any object structure. This loses:

  • Compile-time type checking for known fields
  • Runtime validation of expected properties
  • Clear documentation of the expected shape

If this flexibility is intentional (e.g., to support evolving automod configurations), consider adding a brief code comment explaining the design decision per coding guidelines. Alternatively, define a minimal base schema with .passthrough() to validate known fields while allowing extras.

💡 Alternative: Minimal base validation with passthrough
const automodBaseSchema = z.object({
    enabled: z.boolean().optional(),
}).passthrough()

// Then use automodBaseSchema.optional() instead of z.record(z.unknown()).optional()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/services/guildAutomation/manifestSchema.ts` around lines
120 - 125, The moderation schema currently relaxes structure by using
z.record(z.unknown()) for fields automod and moderationSettings; restore
meaningful validation by replacing those records with a minimal base schema
(e.g., an automodBaseSchema with known properties like enabled and other
expected keys, using .passthrough() to allow extras) and use
automodBaseSchema.optional() and a similar base for moderationSettings, or if
the loose shape is intentional add a concise code comment explaining why
structural validation was removed; update the object in manifestSchema.ts (the
moderation object containing automod and moderationSettings) to reference these
base schemas to preserve runtime validation and compile-time typing.
packages/bot/src/functions/moderation/commands/index.ts (1)

2-3: Use node: prefix for Node.js built-in modules.

Same issue as flagged in automod/commands/index.ts. For consistency and ESM best practices, use the node: prefix.

♻️ Proposed fix
-import path from 'path'
-import { fileURLToPath } from 'url'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/functions/moderation/commands/index.ts` around lines 2 - 3,
Update the built-in module imports to use the Node.js "node:" protocol for ESM
consistency: replace the import of path and fileURLToPath from 'path' and 'url'
with the namespaced imports using 'node:path' and 'node:url' (refer to the
top-level import statements that currently import path and fileURLToPath in
packages/bot/src/functions/moderation/commands/index.ts). Ensure both import
specifiers are updated so other code using path and fileURLToPath remains
unchanged.
packages/bot/src/functions/automod/commands/index.ts (1)

2-3: Use node: prefix for Node.js built-in modules.

SonarCloud correctly flags that ESM best practice is to use node:path and node:url for built-in modules. This improves clarity and avoids potential conflicts with npm packages.

♻️ Proposed fix
-import path from 'path'
-import { fileURLToPath } from 'url'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/functions/automod/commands/index.ts` around lines 2 - 3,
Change the imports of Node built-ins to use the node: prefix: replace "import
path from 'path'" with "import path from 'node:path'" and "import {
fileURLToPath } from 'url'" with "import { fileURLToPath } from 'node:url'";
update any references to path and fileURLToPath in this module (e.g., the
top-level import statements in this file) so they continue to work unchanged
after switching to node: imports.
packages/bot/src/functions/management/commands/guildconfig.ts (2)

97-401: File exceeds 250-line limit.

The execute function spans ~300 lines with inline subcommand handling. While the consolidated approach is readable, the file is approximately 400 lines total, exceeding the project's 250-line guideline.

Consider extracting subcommand handlers into separate functions or a handlers module to reduce file length, though this can be deferred given the clear structure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/functions/management/commands/guildconfig.ts` around lines
97 - 401, The execute handler in guildconfig.ts has grown too large (contains
inline logic for subcommands capture, plan, apply/reconcile, status, cutover)
and should be split to keep the file under the 250-line guideline; refactor by
extracting each subcommand block into separate functions (e.g., handleCapture,
handlePlan, handleApplyOrReconcile, handleStatus, handleCutover) that accept the
same context (interaction, guild) and call existing helpers like
captureGuildAutomationState, guildAutomationService methods,
applyAutomationModules, interactionReply and errorLog, then replace the inline
blocks in execute with a small dispatcher that calls these new handler functions
based on subcommand; keep existing behavior and return values/updates (including
interaction.editReply and guildAutomationService updates) so tests and logic
remain unchanged.

203-229: Invert negated condition for clarity.

SonarCloud flagged the negated condition if (!blockedByProtected). Inverting the branches improves readability:

♻️ Proposed refactor to use positive condition first
-               if (!blockedByProtected) {
-                   const applyResult = await applyAutomationModules({
-                       guild,
-                       desired:
-                           planResult.desired as GuildAutomationManifestDocument,
-                       plan: planResult.plan,
-                       allowProtected,
-                   })
-
-                   applyDiagnostics = {
-                       ...applyDiagnostics,
-                       appliedModules: applyResult.appliedModules,
-                       skippedModules: applyResult.skippedModules,
-                   }
-
-                   await guildAutomationService.updateRunStatus({
-                       runId: planResult.runId,
-                       status: 'completed',
-                       diagnostics: applyDiagnostics,
-                   })
-               } else {
+               if (blockedByProtected) {
                    await guildAutomationService.updateRunStatus({
                        runId: planResult.runId,
                        status: 'blocked',
                        diagnostics: applyDiagnostics,
                    })
+               } else {
+                   const applyResult = await applyAutomationModules({
+                       guild,
+                       desired:
+                           planResult.desired as GuildAutomationManifestDocument,
+                       plan: planResult.plan,
+                       allowProtected,
+                   })
+
+                   applyDiagnostics = {
+                       ...applyDiagnostics,
+                       appliedModules: applyResult.appliedModules,
+                       skippedModules: applyResult.skippedModules,
+                   }
+
+                   await guildAutomationService.updateRunStatus({
+                       runId: planResult.runId,
+                       status: 'completed',
+                       diagnostics: applyDiagnostics,
+                   })
                }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/functions/management/commands/guildconfig.ts` around lines
203 - 229, Invert the negated condition for clarity: replace the `if
(!blockedByProtected)` branch with a positive `if (blockedByProtected)` first so
the blocked case calls `guildAutomationService.updateRunStatus` with status
'blocked' and diagnostics, and the else branch performs the apply flow (calling
`applyAutomationModules` with `guild`, `planResult.desired`, `planResult.plan`,
`allowProtected`), merges `applyResult` into `applyDiagnostics` and then calls
`guildAutomationService.updateRunStatus` with status 'completed'; update
references to `planResult.runId`, `applyDiagnostics`, and `applyResult`
accordingly to preserve behavior.
packages/shared/src/services/guildAutomation/types.ts (2)

196-202: Type widening from enum literals to string loses exhaustiveness checking.

The latestRun.type, latestRun.status, drifts[].module, and drifts[].severity fields are now typed as string instead of the specific union types (AutomationRunType, AutomationRunStatus, AutomationModule, DriftSeverity).

This aligns with the Prisma schema (which stores plain String fields), but consumers performing exhaustive pattern matching (e.g., switch on status) will silently accept invalid values without TypeScript warnings.

Consider adding runtime validation in getStatus() if strict enum conformance is needed, or keep this as a documentation note that database values may not conform to expected literals.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/services/guildAutomation/types.ts` around lines 196 -
202, The fields latestRun.type, latestRun.status, drifts[].module and
drifts[].severity were widened to plain string losing exhaustiveness checks;
revert those specific fields in
packages/shared/src/services/guildAutomation/types.ts to the corresponding union
types (AutomationRunType, AutomationRunStatus, AutomationModule, DriftSeverity)
so TypeScript preserves literal unions for consumers, and if DB string values
may be invalid add runtime validation inside the getStatus() helper (validate
and map/throw/fallback to a safe sentinel like "UNKNOWN") to ensure only
expected enum literals are used at runtime.

76-86: Type relaxation trades safety for flexibility.

The change from specific typed interfaces (AutoModSettingsUpdate, ModerationSettingsUpdate) to permissive index signatures ([key: string]: unknown) allows arbitrary fields to pass through without compile-time validation. This is a reasonable trade-off for a control-plane that needs to handle varying automod configurations, but consumers should validate at runtime.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/services/guildAutomation/types.ts` around lines 76 - 86,
The types for automod and moderationSettings were relaxed to include permissive
index signatures which removes compile-time safety; adjust types.ts by either
restoring the original specific update interfaces (AutoModSettingsUpdate,
ModerationSettingsUpdate) for known fields and adding an explicit fallback type
(e.g., Record<string, unknown> or a named
ExtraAutomodFields/ExtraModerationFields) for extension, and/or add runtime
validation helpers that consumers can call to validate unknown keys before
usage; update the automod and moderationSettings declarations to reference these
stricter update types (or the combined specific+fallback types) so callers get
type safety for known properties while still allowing extensibility at runtime.
packages/bot/src/functions/management/commands/guildconfig.spec.ts (1)

64-172: Test coverage is streamlined but may need expansion.

The tests cover the happy path for capture, plan, and apply subcommands. Consider adding tests for:

  • status and cutover subcommands
  • Error handling (service failures)
  • Protected operations blocking (blockedByProtected branch)

These can be added in a follow-up if the current scope is intentionally limited.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/functions/management/commands/guildconfig.spec.ts` around
lines 64 - 172, Tests only cover happy paths for capture/plan/apply; add unit
tests for the remaining branches: add tests that call guildconfigCommand.execute
with the 'status' and 'cutover' subcommands to ensure getStatusMock and
runCutoverMock are invoked and interaction.editReply is called, add tests that
simulate service failures by having mocks (e.g., createPlanMock,
captureGuildAutomationStateMock, runCutoverMock) reject and assert error
handling paths (interaction.editReply with error), and add a test that exercises
the protected-operations branch (set createPlanMock to return a plan with
protected ops and call execute with allow_protected false to assert
blockedByProtected handling and that applyAutomationModulesMock is not called);
reference guildconfigCommand.execute, getStatusMock, runCutoverMock,
createPlanMock, captureGuildAutomationStateMock, applyAutomationModulesMock,
recordCaptureMock, and updateRunStatusMock when locating code to change.
prisma/schema.prisma (1)

137-147: Drop the extra guildId index on GuildAutomationManifest.

guildId @unique`` already gives Postgres an index, so this @@index([guildId]) only adds another structure to maintain on every manifest write and is what produced the duplicate SQL index in the migration.

♻️ Proposed fix
 model GuildAutomationManifest {
   id                String   `@id` `@default`(cuid())
   guildId           String   `@unique`
   version           Int      `@default`(1)
   manifest          Json
   moduleOwnership   Json?
   lastCapturedState Json?
   lastCapturedAt    DateTime?
   createdBy         String?
   createdAt         DateTime `@default`(now())
   updatedAt         DateTime `@updatedAt`

-  @@index([guildId])
   @@map("guild_automation_manifests")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@prisma/schema.prisma` around lines 137 - 147, The model defines guildId as
unique, which already creates a PostgreSQL index, so the extra index declaration
@@index([guildId]) in the GuildAutomationManifest model should be removed to
avoid duplicate indexes; locate the GuildAutomationManifest model in
schema.prisma and delete the @@index([guildId]) line (leave guildId String
`@unique`, version, manifest, etc. unchanged), then regenerate the migration to
reflect the removal.
packages/shared/src/services/guildAutomation/service.ts (2)

186-196: Extract nested ternary into a helper function for readability.

SonarCloud flagged this nested ternary. Consider extracting the actual state resolution logic into a named helper to improve readability.

♻️ Proposed refactor
+ function resolveActualState(
+     providedState: GuildAutomationManifestInput | undefined,
+     lastCapturedState: unknown,
+ ): GuildAutomationManifestDocument | null {
+     if (providedState) {
+         return guildAutomationManifestSchema.parse(providedState)
+     }
+     if (lastCapturedState) {
+         return toManifestDocument(lastCapturedState)
+     }
+     return null
+ }

// In createPlan:
- const actual = options?.actualState
-     ? guildAutomationManifestSchema.parse(options.actualState)
-     : manifestRow.lastCapturedState
-       ? toManifestDocument(manifestRow.lastCapturedState)
-       : null
+ const actual = resolveActualState(
+     options?.actualState,
+     manifestRow.lastCapturedState,
+ )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/services/guildAutomation/service.ts` around lines 186 -
196, Extract the nested ternary that computes actual into a small helper (e.g.,
resolveActualState or getActualState) that accepts options and manifestRow and
returns either guildAutomationManifestSchema.parse(options.actualState),
toManifestDocument(manifestRow.lastCapturedState), or null; replace the inline
expression in service.ts with a call to that helper and keep the existing null
check/throw behavior unchanged so the error message remains the same.

208-215: Extract severity calculation into a named function.

SonarCloud flagged the nested ternary. A helper function with clear thresholds improves readability and makes the severity logic reusable.

♻️ Proposed refactor
+ function computeDriftSeverity(
+     operationCount: number,
+ ): 'none' | 'low' | 'medium' | 'high' {
+     if (operationCount === 0) return 'none'
+     if (operationCount < 3) return 'low'
+     if (operationCount < 8) return 'medium'
+     return 'high'
+ }

// In createPlan loop:
- const severity: 'none' | 'low' | 'medium' | 'high' =
-     count === 0
-         ? 'none'
-         : count < 3
-           ? 'low'
-           : count < 8
-             ? 'medium'
-             : 'high'
+ const severity = computeDriftSeverity(count)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/services/guildAutomation/service.ts` around lines 208 -
215, Replace the nested ternary that assigns severity with a small named helper:
implement a function (e.g., getSeverityFromCount or calculateSeverity) that
accepts count and returns the union type 'none' | 'low' | 'medium' | 'high'
using the same thresholds (0 -> 'none', count < 3 -> 'low', count < 8 ->
'medium', else 'high'), then replace the inline ternary expression that sets the
severity variable with a call to that helper; ensure the helper is exported or
kept local to the module as appropriate and used everywhere this logic may be
needed for reusability and clarity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/backend/src/routes/guilds.ts`:
- Around line 115-124: The route handler for GET /api/guilds/:guildId/channels
currently only uses requireAuth and trusts outer wiring; update it to enforce
the same guild-module access middleware used by neighboring guild-scoped
endpoints (e.g., requireGuildModuleAccess) and apply validateParams(...) with
the Zod params schema for guildId (e.g., guildIdParamsSchema) before the async
handler; locate the handler around guildService.getGuildTextChannelOptions and
replace the middleware list to be [requireAuth,
validateParams(guildIdParamsSchema), requireGuildModuleAccess] (or the actual
middleware name used nearby) so guildId is validated locally and access is
checked.

In `@packages/backend/tests/integration/routes/management.test.ts`:
- Around line 251-260: The test is using `as any` casts on the mocked responses
for `mockAutoModService.getSettings` and `mockAutoModService.updateSettings`;
remove those casts and return properly typed objects instead (e.g., create or
import a fixture type matching the service return types or use the TypeScript
`satisfies` operator to assert the shape), or if unavoidable add a single-line
eslint disable with justification above the offending mock (//
eslint-disable-next-line `@typescript-eslint/no-explicit-any` -- explain why) so
the linter is satisfied; update the two mockResolvedValue calls for
`mockAutoModService.getSettings` and `mockAutoModService.updateSettings` to use
the typed fixtures/satisfies assertion or the documented eslint exception.

In `@packages/bot/src/utils/guildAutomation/applyPlan.ts`:
- Around line 183-188: The catch block that swallows errors when deleting
channels in applyPlan should log the failure instead of silently continuing:
import and use errorLog from `@lucky/shared/utils` inside the catch for the await
channel.delete('Lucky guild automation protected-delete apply') call (within the
applyPlan function) and pass a descriptive message plus the caught error and
channel identifier (e.g., channel.id or channel.name) so tests expecting
errorLog are satisfied and failures are recorded; after logging, keep the
continue to preserve current control flow.
- Around line 246-262: The unsafe "as never" casts bypass type safety when
passing desired.moderation.automod and desired.moderation.moderationSettings
into autoModService.updateSettings and updateModerationSettings; replace these
casts by ensuring the manifest has proper types or add runtime validation/guards
before calling the services (e.g., narrow desired.moderation with type
predicates or validate shape/nullability), and only call
autoModService.updateSettings(guild.id, validatedAutomod) and
updateModerationSettings(guild.id, validatedModerationSettings) when the values
pass those checks; update the GuildAutomationManifestDocument types or add
conversion/validation helpers to guarantee correct types rather than using "as
never".

In `@packages/bot/src/utils/guildAutomation/captureGuildState.ts`:
- Line 87: The silent .catch(() => null) on the guild.fetchOnboarding() call
hides all errors; change it to catch the error into a variable, keep the
existing isOnboardingNotConfiguredError check to return null for expected "not
configured" errors, and for all other errors call errorLog from
`@lucky/shared/utils` with structured fields (message, code, stack, cause and any
available correlation/user context) before returning null; ensure errorLog is
imported and reference guild.fetchOnboarding(), onboarding,
isOnboardingNotConfiguredError and errorLog in your fix.
- Around line 143-144: Add a brief inline comment next to the automod assignment
explaining why you cast automodSettings to a loose Record<string, unknown>
(e.g., for manifest/schema serialization, forward compatibility with unknown
future AutoModSettings fields, or to satisfy storage/transport typing),
referencing the automod and automodSettings symbols and the AutoModSettings
interface so reviewers understand the intentional loss of specific typing.

In `@packages/shared/src/services/guildAutomation/service.ts`:
- Line 32: Replace all generic Error throws in the guild automation service with
domain-specific error classes: add new error classes (e.g.,
GuildAutomationNotFoundError with code 'ERR_AUTOMATION_NOT_FOUND',
GuildAutomationInvalidPayloadError with code 'ERR_AUTOMATION_INVALID_PAYLOAD',
and GuildAutomationLockError with code 'ERR_AUTOMATION_LOCK_CONFLICT' and
retryable = true) in the shared types/errors area, export them, and then update
the service.ts locations that currently throw generic errors (the throws for
invalid manifest payload, missing manifest, and lock contention) to throw the
corresponding new classes (use the guildId or relevant context in constructors
and preserve original messages while adding the stable code property).
- Around line 19-20: Replace the in-memory Map-based lock (locks Map) used
around applyManifestDiff() with the Redis-based distributed lock helpers:
acquire the lock by calling RedisClient.setNxPx(lockKey, lockValue, ttl) instead
of setting locks.set(key, ...), and release it with
RedisClient.delIfValueMatches(lockKey, lockValue) instead of deleting from the
Map; update the locking logic in the function(s) that reference locks (e.g.,
applyManifestDiff) to generate a unique lockKey/lockValue, handle acquisition
failure/ttl expiry similarly to the current Map logic, and ensure the release
happens in finally/failure paths to avoid deadlocks.

---

Outside diff comments:
In `@packages/bot/src/utils/guildAutomation/applyPlan.ts`:
- Around line 1-16: The file is missing the required errorLog import from
`@lucky/shared/utils` which is used for error handling in the deletion catch block
inside applyPlan.ts; add errorLog to the module imports (alongside any existing
imports from '@lucky/shared/utils') so the catch block uses errorLog(...)
instead of an undefined identifier, ensuring consistent logging per bot package
guidelines and referencing the deletion catch block and functions like applyPlan
where the error is currently handled.

---

Nitpick comments:
In `@packages/bot/src/functions/automod/commands/index.ts`:
- Around line 2-3: Change the imports of Node built-ins to use the node: prefix:
replace "import path from 'path'" with "import path from 'node:path'" and
"import { fileURLToPath } from 'url'" with "import { fileURLToPath } from
'node:url'"; update any references to path and fileURLToPath in this module
(e.g., the top-level import statements in this file) so they continue to work
unchanged after switching to node: imports.

In `@packages/bot/src/functions/management/commands/guildconfig.spec.ts`:
- Around line 64-172: Tests only cover happy paths for capture/plan/apply; add
unit tests for the remaining branches: add tests that call
guildconfigCommand.execute with the 'status' and 'cutover' subcommands to ensure
getStatusMock and runCutoverMock are invoked and interaction.editReply is
called, add tests that simulate service failures by having mocks (e.g.,
createPlanMock, captureGuildAutomationStateMock, runCutoverMock) reject and
assert error handling paths (interaction.editReply with error), and add a test
that exercises the protected-operations branch (set createPlanMock to return a
plan with protected ops and call execute with allow_protected false to assert
blockedByProtected handling and that applyAutomationModulesMock is not called);
reference guildconfigCommand.execute, getStatusMock, runCutoverMock,
createPlanMock, captureGuildAutomationStateMock, applyAutomationModulesMock,
recordCaptureMock, and updateRunStatusMock when locating code to change.

In `@packages/bot/src/functions/management/commands/guildconfig.ts`:
- Around line 97-401: The execute handler in guildconfig.ts has grown too large
(contains inline logic for subcommands capture, plan, apply/reconcile, status,
cutover) and should be split to keep the file under the 250-line guideline;
refactor by extracting each subcommand block into separate functions (e.g.,
handleCapture, handlePlan, handleApplyOrReconcile, handleStatus, handleCutover)
that accept the same context (interaction, guild) and call existing helpers like
captureGuildAutomationState, guildAutomationService methods,
applyAutomationModules, interactionReply and errorLog, then replace the inline
blocks in execute with a small dispatcher that calls these new handler functions
based on subcommand; keep existing behavior and return values/updates (including
interaction.editReply and guildAutomationService updates) so tests and logic
remain unchanged.
- Around line 203-229: Invert the negated condition for clarity: replace the `if
(!blockedByProtected)` branch with a positive `if (blockedByProtected)` first so
the blocked case calls `guildAutomationService.updateRunStatus` with status
'blocked' and diagnostics, and the else branch performs the apply flow (calling
`applyAutomationModules` with `guild`, `planResult.desired`, `planResult.plan`,
`allowProtected`), merges `applyResult` into `applyDiagnostics` and then calls
`guildAutomationService.updateRunStatus` with status 'completed'; update
references to `planResult.runId`, `applyDiagnostics`, and `applyResult`
accordingly to preserve behavior.

In `@packages/bot/src/functions/moderation/commands/index.ts`:
- Around line 2-3: Update the built-in module imports to use the Node.js "node:"
protocol for ESM consistency: replace the import of path and fileURLToPath from
'path' and 'url' with the namespaced imports using 'node:path' and 'node:url'
(refer to the top-level import statements that currently import path and
fileURLToPath in packages/bot/src/functions/moderation/commands/index.ts).
Ensure both import specifiers are updated so other code using path and
fileURLToPath remains unchanged.

In `@packages/bot/src/utils/guildAutomation/applyPlan.ts`:
- Around line 168-176: Role deletion in the roles loop (the await
role.delete('Lucky guild automation protected-delete apply') call in
applyPlan.ts) lacks error handling and will abort the whole operation on
failure; wrap the await role.delete(...) in a try/catch and handle errors the
same way channel deletion does (catch errors, log them with the same logger or
console, and continue) so a single failed role.delete won't stop the rest of the
automation from applying.
- Around line 223-296: The applyAutomationModules function is too long; refactor
by extracting each module's logic into small handler functions (e.g.,
applyOnboardingModule, applyRolesModule, applyModerationModule,
applyAutoMessagesModule, applyReactionRolesModule, applyCommandAccessModule,
applyParityModule) that return either an applied module name or a skipped
message (string | null or {applied?:string, skipped?:string}), then simplify
applyAutomationModules to iterate over a handlers array and call
shouldApplyModule(plan, '<module>', allowProtected) before invoking each handler
and collecting returned applied/skipped names; keep existing helpers
(manifestOnboardingToDiscordEdit, applyRolesAndChannels,
autoModService.updateSettings, updateModerationSettings, upsertAutoMessage,
applyReactionRoleRules, guildRoleAccessService.replaceRoleGrants) inside the new
handlers so behavior is unchanged.

In `@packages/bot/src/utils/guildAutomation/captureGuildState.ts`:
- Around line 83-189: captureGuildAutomationState is too long; split its
responsibilities into smaller helpers: extract the async parallel fetch block
(the Promise.all that assigns automodSettings, moderationSettings,
welcomeMessage, leaveMessage, reactionRoleMessages, exclusiveRoles, roleGrants,
parity) into a new helper like fetchGuildAutomationData(guildId, botUserId) that
returns a typed object, and move the large return object construction into a
buildGuildManifest(guild, onboarding, fetchedData, roles, channels) helper; keep
captureGuildAutomationState to calling those helpers, computing roles/channels,
and returning the result. Reference the existing symbols
captureGuildAutomationState, captureParity, autoModService.getSettings,
autoMessageService.getWelcomeMessage/getLeaveMessage,
reactionRolesService.listReactionRoleMessages,
roleManagementService.listExclusiveRoles, guildRoleAccessService.listRoleGrants
in the new helpers so locating code is straightforward.

In `@packages/shared/src/services/guildAutomation/manifestSchema.ts`:
- Around line 120-125: The moderation schema currently relaxes structure by
using z.record(z.unknown()) for fields automod and moderationSettings; restore
meaningful validation by replacing those records with a minimal base schema
(e.g., an automodBaseSchema with known properties like enabled and other
expected keys, using .passthrough() to allow extras) and use
automodBaseSchema.optional() and a similar base for moderationSettings, or if
the loose shape is intentional add a concise code comment explaining why
structural validation was removed; update the object in manifestSchema.ts (the
moderation object containing automod and moderationSettings) to reference these
base schemas to preserve runtime validation and compile-time typing.

In `@packages/shared/src/services/guildAutomation/service.ts`:
- Around line 186-196: Extract the nested ternary that computes actual into a
small helper (e.g., resolveActualState or getActualState) that accepts options
and manifestRow and returns either
guildAutomationManifestSchema.parse(options.actualState),
toManifestDocument(manifestRow.lastCapturedState), or null; replace the inline
expression in service.ts with a call to that helper and keep the existing null
check/throw behavior unchanged so the error message remains the same.
- Around line 208-215: Replace the nested ternary that assigns severity with a
small named helper: implement a function (e.g., getSeverityFromCount or
calculateSeverity) that accepts count and returns the union type 'none' | 'low'
| 'medium' | 'high' using the same thresholds (0 -> 'none', count < 3 -> 'low',
count < 8 -> 'medium', else 'high'), then replace the inline ternary expression
that sets the severity variable with a call to that helper; ensure the helper is
exported or kept local to the module as appropriate and used everywhere this
logic may be needed for reusability and clarity.

In `@packages/shared/src/services/guildAutomation/types.ts`:
- Around line 196-202: The fields latestRun.type, latestRun.status,
drifts[].module and drifts[].severity were widened to plain string losing
exhaustiveness checks; revert those specific fields in
packages/shared/src/services/guildAutomation/types.ts to the corresponding union
types (AutomationRunType, AutomationRunStatus, AutomationModule, DriftSeverity)
so TypeScript preserves literal unions for consumers, and if DB string values
may be invalid add runtime validation inside the getStatus() helper (validate
and map/throw/fallback to a safe sentinel like "UNKNOWN") to ensure only
expected enum literals are used at runtime.
- Around line 76-86: The types for automod and moderationSettings were relaxed
to include permissive index signatures which removes compile-time safety; adjust
types.ts by either restoring the original specific update interfaces
(AutoModSettingsUpdate, ModerationSettingsUpdate) for known fields and adding an
explicit fallback type (e.g., Record<string, unknown> or a named
ExtraAutomodFields/ExtraModerationFields) for extension, and/or add runtime
validation helpers that consumers can call to validate unknown keys before
usage; update the automod and moderationSettings declarations to reference these
stricter update types (or the combined specific+fallback types) so callers get
type safety for known properties while still allowing extensibility at runtime.

In `@prisma/schema.prisma`:
- Around line 137-147: The model defines guildId as unique, which already
creates a PostgreSQL index, so the extra index declaration @@index([guildId]) in
the GuildAutomationManifest model should be removed to avoid duplicate indexes;
locate the GuildAutomationManifest model in schema.prisma and delete the
@@index([guildId]) line (leave guildId String `@unique`, version, manifest, etc.
unchanged), then regenerate the migration to reflect the removal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 257b03a3-70e6-436a-9d7c-0b3cf61bd50e

📥 Commits

Reviewing files that changed from the base of the PR and between 083e513 and 1bdb3ff.

📒 Files selected for processing (31)
  • CHANGELOG.md
  • README.md
  • packages/backend/src/constants/automodTemplates.ts
  • packages/backend/src/constants/guildAutomationPresets.ts
  • packages/backend/src/routes/guildAutomation.ts
  • packages/backend/src/routes/guilds.ts
  • packages/backend/src/routes/index.ts
  • packages/backend/src/routes/management.ts
  • packages/backend/src/schemas/management.ts
  • packages/backend/src/services/GuildService.ts
  • packages/backend/tests/integration/routes/guildAutomation.test.ts
  • packages/backend/tests/integration/routes/guilds.test.ts
  • packages/backend/tests/integration/routes/management.test.ts
  • packages/backend/tests/unit/routes/index.test.ts
  • packages/bot/src/functions/automod/commands/index.ts
  • packages/bot/src/functions/management/commands/guildconfig.spec.ts
  • packages/bot/src/functions/management/commands/guildconfig.ts
  • packages/bot/src/functions/management/commands/index.ts
  • packages/bot/src/functions/moderation/commands/index.ts
  • packages/bot/src/register.spec.ts
  • packages/bot/src/register.ts
  • packages/bot/src/utils/guildAutomation/applyPlan.ts
  • packages/bot/src/utils/guildAutomation/captureGuildState.ts
  • packages/bot/src/utils/guildAutomation/diff.spec.ts
  • packages/bot/src/utils/guildAutomation/manifestSchema.spec.ts
  • packages/shared/src/services/guildAutomation/diff.ts
  • packages/shared/src/services/guildAutomation/manifestSchema.ts
  • packages/shared/src/services/guildAutomation/service.ts
  • packages/shared/src/services/guildAutomation/types.ts
  • prisma/migrations/20260311193000_add_guild_automation_manifest/migration.sql
  • prisma/schema.prisma
💤 Files with no reviewable changes (3)
  • packages/bot/src/utils/guildAutomation/diff.spec.ts
  • packages/backend/tests/unit/routes/index.test.ts
  • packages/bot/src/utils/guildAutomation/manifestSchema.spec.ts

Comment thread packages/backend/src/routes/guilds.ts
Comment thread packages/backend/tests/integration/routes/management.test.ts
Comment thread packages/bot/src/utils/guildAutomation/applyPlan.ts
Comment thread packages/bot/src/utils/guildAutomation/applyPlan.ts
Comment thread packages/bot/src/utils/guildAutomation/captureGuildState.ts Outdated
Comment thread packages/bot/src/utils/guildAutomation/captureGuildState.ts
Comment thread packages/shared/src/services/guildAutomation/service.ts
Comment thread packages/shared/src/services/guildAutomation/service.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
packages/bot/src/utils/guildAutomation/applyPlan.ts (1)

280-291: ⚠️ Potential issue | 🟠 Major

Remove as never moderation casts and validate before service calls.

Lines 283 and 290 bypass type safety at a service boundary. This can pass malformed moderation payloads into shared services.

As per coding guidelines: "Always validate and sanitize external data (HTTP, DB) at the boundary using type guards or schema validators".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/guildAutomation/applyPlan.ts` around lines 280 - 291,
The code is currently using unsafe casts ("as never") when passing
desired.moderation.automod and desired.moderation.moderationSettings into
service boundaries; remove those casts and validate/sanitize the payloads before
calling autoModService.updateSettings(guild.id, ...) and
updateModerationSettings(guild.id, ...). Implement or call the existing type
guard/schema validator for ModerationAutomod and ModerationSettings (or add
simple runtime checks) to ensure the objects conform, and if validation fails,
log or return early rather than calling the service with malformed data.
🧹 Nitpick comments (12)
packages/bot/src/functions/automod/commands/index.spec.ts (1)

5-14: Same source-string testing concern as other loader specs.

This test has the same brittle pattern as the moderation and management loader specs. Testing raw source text doesn't validate actual loader behavior.

If source-string validation is intentional (e.g., enforcing code generation consistency), consider extracting a shared test utility rather than duplicating the same test structure across three files.

♻️ Shared test utility approach
// test-utils/loaderSourceValidator.ts
export const validateLoaderSource = (
    category: string,
    sourcePath: string
) => {
    const source = fs.readFileSync(sourcePath, 'utf8')
    expect(source).not.toContain('excludePatterns')
    expect(source).toContain(`category: '${category}'`)
    expect(source).toContain("import path from 'node:path'")
    expect(source).toContain("import { fileURLToPath } from 'node:url'")
}

As per coding guidelines: "Test behavior, not implementation details".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/functions/automod/commands/index.spec.ts` around lines 5 -
14, The test describe('automod command loader source') in index.spec.ts is
brittle because it asserts raw source text; replace the duplicated string checks
with a shared utility (e.g., create test-utils/loaderSourceValidator.ts
exporting validateLoaderSource(category, sourcePath)) and call
validateLoaderSource('automod', path.join(__dirname, 'index.ts')) from this
spec, or better, rewrite the spec to test actual loader behavior by importing
the loader module and asserting its runtime properties (category === 'automod'
and no excludePatterns) instead of reading the file contents; update other
similar specs to reuse validateLoaderSource if you keep the source-string
approach.
packages/bot/src/functions/moderation/commands/index.spec.ts (2)

5-14: Testing source code strings is brittle and tests implementation rather than behavior.

This test reads the raw source file and asserts on string patterns. Per coding guidelines, tests should verify behavior, not implementation details. String-based assertions break on:

  • Formatting changes (quotes, spacing)
  • Import reordering
  • Comments or minor refactors

Consider testing actual loader behavior instead—verify the exported module has the expected category property, correct command count, or proper structure.

♻️ Behavioral test approach
-import { describe, expect, it } from '@jest/globals'
-import fs from 'node:fs'
-import path from 'node:path'
-
-describe('moderation command loader source', () => {
-    it('uses moderation category with centralized loader defaults', () => {
-        const sourcePath = path.join(__dirname, 'index.ts')
-        const source = fs.readFileSync(sourcePath, 'utf8')
-
-        expect(source).not.toContain('excludePatterns')
-        expect(source).toContain("category: 'moderation'")
-        expect(source).toContain("import path from 'node:path'")
-        expect(source).toContain("import { fileURLToPath } from 'node:url'")
-    })
-})
+import { describe, expect, it } from '@jest/globals'
+import loader from './index.js'
+
+describe('moderation command loader', () => {
+    it('exports commands with moderation category', async () => {
+        const commands = await loader()
+        expect(commands.length).toBeGreaterThan(0)
+        commands.forEach(cmd => {
+            expect(cmd.category).toBe('moderation')
+        })
+    })
+})

As per coding guidelines: "Test behavior, not implementation details".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/functions/moderation/commands/index.spec.ts` around lines 5
- 14, Replace brittle string-based assertions in the test that reads the source
file with a behavior-driven test that imports/loads the module under test (the
exported loader from the index.ts file) and asserts on its runtime shape:
require or import the module and check that the exported loader object has
category === 'moderation', that its commands (or load function) returns/contains
the expected number of commands or expected structure, and that it does not
expose an excludePatterns property; update the test names accordingly (the
existing it() description can remain) and remove assertions that inspect raw
source via source/sourcePath.

7-7: Replace __dirname with the ESM-equivalent pattern.

__dirname is a CommonJS global not available in native ESM. Use fileURLToPath(import.meta.url) instead to align with the project's ESM-only guidelines and the pattern already used in other command files.

Example from similar test file

Other command files like packages/bot/src/functions/automod/commands/index.ts use:

import { fileURLToPath } from 'node:url'
// ...
const dirName = path.dirname(fileURLToPath(import.meta.url))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/functions/moderation/commands/index.spec.ts` at line 7, The
test uses the CommonJS __dirname global when building sourcePath; replace that
with the ESM pattern by importing fileURLToPath from 'node:url' and computing
dirName = path.dirname(fileURLToPath(import.meta.url)), then build sourcePath
using that dirName (i.e. update the symbol sourcePath and its construction to
use dirName instead of __dirname); ensure the new import and dirName calculation
are added at the top of the file near other imports and referenced where
sourcePath is defined.
packages/bot/src/functions/management/commands/index.spec.ts (1)

5-13: Consistent with other loader specs, but same brittleness concerns apply.

The additions (lines 11-13) extend the source-string validation pattern. While this maintains consistency with the automod and moderation specs, the same concerns about testing implementation rather than behavior apply here.

If this pattern is intentional for enforcing generated code consistency, adding a comment explaining the rationale would help future maintainers understand why source-string testing is used instead of behavioral tests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/functions/management/commands/index.spec.ts` around lines 5
- 13, The spec index.spec.ts now asserts source-string fragments
(expect(...).toContain for "category: 'management'", "import path from
'node:path'", and "import { fileURLToPath } from 'node:url'") which is
intentionally brittle but used for generated-code consistency; add a short
comment above the test describing that these source-string assertions are
intentional to enforce generated loader output consistency (matching
automod/moderation specs) and note the tradeoff vs behavioral testing so future
maintainers understand why we test the source string rather than behavior.
packages/bot/src/utils/guildAutomation/captureGuildState.spec.ts (3)

167-167: Consider a typed partial mock to avoid as any.

Per coding guidelines, any should be avoided. A typed approach using Partial<Guild> or a custom mock interface could provide better type safety while still allowing the mock to omit unneeded properties.

Example typed mock interface
// At top of file
type MockGuild = Pick<Guild, 'id' | 'name' | 'members' | 'roles' | 'channels'> & {
    fetchOnboarding: jest.Mock
}

// In test
const result = await captureGuildAutomationState(
    guild as unknown as Guild,
    'bot-self'
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/guildAutomation/captureGuildState.spec.ts` at line
167, Replace the unsafe "as any" cast when calling captureGuildAutomationState
by creating a typed partial/mock for the Guild used in the test (e.g., use
Partial<Guild> or a narrow MockGuild that picks required properties like id,
name, members, roles, channels and stubs methods such as fetchOnboarding) and
cast the test guild to that type (or to unknown then Guild) so the call becomes
type-safe; update the test's guild declaration and the invocation of
captureGuildAutomationState to use this typed mock instead of "as any".

37-127: Consider extracting sub-factories for readability (optional).

The createGuild helper exceeds the 50-line guideline at ~91 lines. While comprehensive test fixtures often require more setup, extracting createChannels(), createRoles(), and createMembers() sub-helpers could improve maintainability and make individual channel/role configurations reusable across tests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/guildAutomation/captureGuildState.spec.ts` around
lines 37 - 127, The createGuild test helper is long and should be split into
sub-factories to improve readability and reuse; refactor createGuild by
extracting createChannels(), createRoles(), and createMembers() functions (or
similarly named helpers) that return the respective objects (members.cache,
roles.cache, channels.cache) and then compose them inside createGuild (preserve
overrides parameter and existing keys like members, roles, channels,
fetchOnboarding, and id); update tests to import/call these sub-factories or
keep createGuild calling them so existing tests keep the same shape.

228-235: Prefer using an Error object for the rejection mock.

The test correctly verifies rethrow behavior, but mocking with a string ('onboarding-fail') doesn't reflect real-world error scenarios where Error objects are thrown. Using an Error instance would make the test more realistic and align with the coding guideline to "never throw strings."

Suggested change
     it('rethrows unexpected onboarding fetch errors', async () => {
         const guild = createGuild()
-        ;(guild.fetchOnboarding as jest.Mock).mockRejectedValue('onboarding-fail')
+        const unexpectedError = new Error('onboarding-fail')
+        ;(guild.fetchOnboarding as jest.Mock).mockRejectedValue(unexpectedError)

         await expect(
             captureGuildAutomationState(guild as any, 'bot-self'),
-        ).rejects.toBe('onboarding-fail')
+        ).rejects.toBe(unexpectedError)
     })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/guildAutomation/captureGuildState.spec.ts` around
lines 228 - 235, The test currently mocks fetchOnboarding rejection with a
string literal which is unrealistic; update the mock in
captureGuildAutomationState.spec (the test block that sets
(guild.fetchOnboarding as jest.Mock).mockRejectedValue('onboarding-fail')) to
reject with an Error object instead (e.g., mockRejectedValue(new
Error('onboarding-fail'))) so the test simulates a real thrown Error and still
asserts that captureGuildAutomationState rethrows it.
packages/shared/src/services/guildAutomation/types.ts (2)

76-86: Index signatures bypass compile-time type checking.

The [key: string]: unknown index signatures allow arbitrary properties without compiler validation. While runtime validation via Zod (.strict() in autoModSettingsBody) catches invalid requests, type-level enforcement is lost—callers can pass any key without compile errors.

If this flexibility is intentional to support dynamic template properties, consider documenting that rationale. Otherwise, define explicit optional fields for known properties.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/services/guildAutomation/types.ts` around lines 76 - 86,
The types automod and moderationSettings currently include permissive index
signatures ([key: string]: unknown) which bypass compile-time checks; remove
those index signatures from the automod and moderationSettings interfaces in
types.ts and instead declare explicit optional fields for any known dynamic
properties (e.g., add named optional properties for template-related fields) or,
if truly needed, replace the signature with a documented Record type alias and
comment explaining why dynamic keys are required; also update the corresponding
runtime validator autoModSettingsBody (Zod schema) to match the tightened type
shape so compile-time and runtime validation stay in sync.

195-205: Use existing union types instead of string for type safety.

The properties type, status, module, and severity are typed as string in GuildAutomationStatus, but the corresponding union types (AutomationRunType, AutomationRunStatus, AutomationModule, DriftSeverity) are defined in this same file (lines 11–29). Using string loses compile-time exhaustiveness checks and IDE autocomplete. The fact that Prisma stores these as string fields does not prevent the TypeScript interface from using union types—validation happens at the database boundary.

♻️ Proposed fix to restore type safety
 latestRun: {
     id: string
-    type: string
-    status: string
+    type: AutomationRunType
+    status: AutomationRunStatus
     createdAt: Date
 } | null
 drifts: Array<{
-    module: string
-    severity: string
+    module: AutomationModule
+    severity: DriftSeverity
     updatedAt: Date
 }>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/services/guildAutomation/types.ts` around lines 195 -
205, In GuildAutomationStatus (fields latestRun and drifts) replace the loose
string types with the existing union types defined earlier: use
AutomationRunType for latestRun.type, AutomationRunStatus for latestRun.status,
AutomationModule for each drifts[].module, and DriftSeverity for each
drifts[].severity; keep createdAt/updatedAt as Date. Update the type annotations
for latestRun and drifts accordingly so the interface uses AutomationRunType,
AutomationRunStatus, AutomationModule, and DriftSeverity for compile-time safety
and IDE autocomplete.
packages/bot/src/utils/guildAutomation/applyPlan.spec.ts (2)

294-595: Reduce as any usage in new tests with typed builders/fixtures.

Frequent as any casts in these new cases weaken compile-time contract checks between test inputs and applyAutomationModules.

As per coding guidelines: "Do not use any types - ESLint enforces this at error level".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/guildAutomation/applyPlan.spec.ts` around lines 294 -
595, The tests overuse "as any" which bypasses type checking; update the specs
to use typed builders/fixtures instead: change createGuild to return a properly
typed Guild (or use a generic createGuild<TGuild>() helper), type the desired
payloads to the real DesiredState or AutomationDesired types used by
applyAutomationModules, and type buildPlan to return the real Plan type so plan:
buildPlan(['roles']) is strongly typed; ensure mocks like
roleCreateMock/channelCreateMock keep their Jest types (e.g.,
jest.MockedFunction) and replace each "as any" around guild, desired, and plan
with the correct types so TypeScript/ESLint no-any errors are resolved while
keeping test behavior identical.

79-600: Consider splitting this spec by module concern.

This file now exceeds the repository size guideline; splitting into focused specs (roles, moderation, onboarding, etc.) will improve maintainability.

As per coding guidelines: "Files must not exceed 250 lines and this is enforced".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/guildAutomation/applyPlan.spec.ts` around lines 79 -
600, Spec file exceeds the 250-line guideline — split tests by module concern.
Create focused spec files (e.g., applyPlan.roles.spec.ts,
applyPlan.moderation.spec.ts, applyPlan.onboarding.spec.ts,
applyPlan.automessages.spec.ts, applyPlan.reactionroles.spec.ts,
applyPlan.commandaccess.spec.ts) and move each related it(...) block into the
matching file; keep shared helpers (createGuild, buildPlan) and common mocks
(manifestOnboardingToDiscordEditMock, getWelcomeMessageMock,
getLeaveMessageMock, createMessageMock, updateMessageMock, updateSettingsMock,
updateModerationSettingsMock, listExclusiveRolesMock, removeExclusiveRoleMock,
setExclusiveRoleMock, replaceRoleGrantsMock, errorLogMock) in a single
test-utils module and import them into each spec; ensure each new spec imports
applyAutomationModules and only sets up the beforeEach mocks relevant to that
module so files stay under 250 lines and maintain test isolation.
packages/bot/src/utils/guildAutomation/applyPlan.ts (1)

256-329: Split applyAutomationModules into smaller module appliers.

This function now combines orchestration plus per-module execution and exceeds the project function-size guideline. Extracting per-module handlers would improve readability and testability.

As per coding guidelines: "Functions must be less than 50 lines with cyclomatic complexity less than 10".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/guildAutomation/applyPlan.ts` around lines 256 - 329,
The applyAutomationModules function is too large; extract each module's logic
into small handlers (e.g., applyOnboardingModule, applyRolesModule,
applyModerationModule, applyAutoMessagesModule, applyReactionRolesModule,
applyCommandAccessModule, applyParityModule) that accept the same context
(guild, desired, allowProtected) and return an object/tuple indicating applied
and skipped module names; keep shouldApplyModule checks in the orchestrator or
inside each handler, preserve all existing calls
(manifestOnboardingToDiscordEdit, guild.editOnboarding, applyRolesAndChannels,
autoModService.updateSettings, updateModerationSettings, upsertAutoMessage,
applyReactionRoleRules, guildRoleAccessService.replaceRoleGrants) and await
semantics, aggregate their returned appliedModules/skippedModules into the final
ApplyResult, and ensure types/signature of applyAutomationModules and the
ApplyResult remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bot/src/functions/management/commands/guildconfig.ts`:
- Around line 391-394: The current reply exposes raw error.message to users;
update the error handling in the guild automation command to use
createUserFriendlyError from `@lucky/shared/general` to produce a sanitized
user-facing message and send it with interactionReply, while logging the full
error details to your existing logger/structured logs (do not include raw
error.message in the reply). Locate the reply code that builds content using
error instanceof Error ? `❌ ${error.message}` and replace that branch to call
createUserFriendlyError(error) for the message and then call
interactionReply(...) to send it; ensure the original error is forwarded to your
logger for diagnostics.
- Around line 203-226: If applyAutomationModules or the subsequent update call
throws, the run can remain non-terminal; wrap the apply + successful update
block (the call to applyAutomationModules and the
guildAutomationService.updateRunStatus that sets status: 'completed' or
'blocked') with a try/catch that, on any exception, calls
guildAutomationService.updateRunStatus({ runId: planResult.runId, status:
'failed', diagnostics: {...applyDiagnostics, error: <stringified error> }}) (and
swallow/log any errors from that update to avoid masking the original error)
before rethrowing or returning the original error; ensure you reference
applyAutomationModules, guildAutomationService.updateRunStatus and
planResult.runId when making the change.
- Around line 327-345: Replace the non-deterministic cache lookup
guild.members.cache.get(bot.id) with an awaited guild.members.fetch(bot.id) in
the loop so external bots are always resolved; wrap the fetch in a try/catch
that skips the bot when fetch throws a 404 (member not found) and rethrows or
logs other errors, only proceed to compute removableRoleIds and call
member.roles.remove(removableRoleIds, 'Lucky cutover removed legacy bot
permissions') when the fetch succeeds, and only increment cleanedBots after a
successful removal.
- Around line 110-381: The execute function is too large/complex—extract each
subcommand block (capture, plan, apply, reconcile, status, cutover) into
dedicated handler functions (e.g. handleCapture, handlePlan,
handleApplyOrReconcile, handleStatus, handleCutover) and make execute a thin
dispatcher that calls these handlers based on subcommand; move logic that calls
captureGuildAutomationState, guildAutomationService.createPlan,
applyAutomationModules, guildAutomationService.updateRunStatus,
guildAutomationService.getStatus/listRuns, and guildAutomationService.runCutover
into their corresponding handlers and return the reply logic from there. Also
replace the local interactionReply import with the package import from
`@lucky/shared/general` and wrap any user-facing errors using
createUserFriendlyError instead of exposing error.message directly so handlers
throw or return friendly errors for the caller to surface. Ensure unique symbols
referenced: execute, captureGuildAutomationState, guildAutomationService
(methods: recordCapture, createPlan, updateRunStatus, getStatus, listRuns,
runCutover, getManifest), applyAutomationModules, and summaryEmbed are
moved/used inside the new handler functions.

In `@packages/bot/src/utils/guildAutomation/applyPlan.ts`:
- Around line 203-220: The catch block that currently does "throw error" should
normalize any non-Error throws into a proper Error and rethrow with the original
value as the cause; update the catch in applyPlan (around the channel.delete
call and shouldIgnoreProtectedDeleteError usage) so that if the error is not
handled by shouldIgnoreProtectedDeleteError you construct and throw a new Error
with a clear message like "Failed to delete channel during guild automation
apply" and attach the original error as the cause (using Error's cause option or
wrapping it if the environment lacks cause), preserving guild.id and channel.id
context in the message or data passed to the new Error.

---

Duplicate comments:
In `@packages/bot/src/utils/guildAutomation/applyPlan.ts`:
- Around line 280-291: The code is currently using unsafe casts ("as never")
when passing desired.moderation.automod and
desired.moderation.moderationSettings into service boundaries; remove those
casts and validate/sanitize the payloads before calling
autoModService.updateSettings(guild.id, ...) and
updateModerationSettings(guild.id, ...). Implement or call the existing type
guard/schema validator for ModerationAutomod and ModerationSettings (or add
simple runtime checks) to ensure the objects conform, and if validation fails,
log or return early rather than calling the service with malformed data.

---

Nitpick comments:
In `@packages/bot/src/functions/automod/commands/index.spec.ts`:
- Around line 5-14: The test describe('automod command loader source') in
index.spec.ts is brittle because it asserts raw source text; replace the
duplicated string checks with a shared utility (e.g., create
test-utils/loaderSourceValidator.ts exporting validateLoaderSource(category,
sourcePath)) and call validateLoaderSource('automod', path.join(__dirname,
'index.ts')) from this spec, or better, rewrite the spec to test actual loader
behavior by importing the loader module and asserting its runtime properties
(category === 'automod' and no excludePatterns) instead of reading the file
contents; update other similar specs to reuse validateLoaderSource if you keep
the source-string approach.

In `@packages/bot/src/functions/management/commands/index.spec.ts`:
- Around line 5-13: The spec index.spec.ts now asserts source-string fragments
(expect(...).toContain for "category: 'management'", "import path from
'node:path'", and "import { fileURLToPath } from 'node:url'") which is
intentionally brittle but used for generated-code consistency; add a short
comment above the test describing that these source-string assertions are
intentional to enforce generated loader output consistency (matching
automod/moderation specs) and note the tradeoff vs behavioral testing so future
maintainers understand why we test the source string rather than behavior.

In `@packages/bot/src/functions/moderation/commands/index.spec.ts`:
- Around line 5-14: Replace brittle string-based assertions in the test that
reads the source file with a behavior-driven test that imports/loads the module
under test (the exported loader from the index.ts file) and asserts on its
runtime shape: require or import the module and check that the exported loader
object has category === 'moderation', that its commands (or load function)
returns/contains the expected number of commands or expected structure, and that
it does not expose an excludePatterns property; update the test names
accordingly (the existing it() description can remain) and remove assertions
that inspect raw source via source/sourcePath.
- Line 7: The test uses the CommonJS __dirname global when building sourcePath;
replace that with the ESM pattern by importing fileURLToPath from 'node:url' and
computing dirName = path.dirname(fileURLToPath(import.meta.url)), then build
sourcePath using that dirName (i.e. update the symbol sourcePath and its
construction to use dirName instead of __dirname); ensure the new import and
dirName calculation are added at the top of the file near other imports and
referenced where sourcePath is defined.

In `@packages/bot/src/utils/guildAutomation/applyPlan.spec.ts`:
- Around line 294-595: The tests overuse "as any" which bypasses type checking;
update the specs to use typed builders/fixtures instead: change createGuild to
return a properly typed Guild (or use a generic createGuild<TGuild>() helper),
type the desired payloads to the real DesiredState or AutomationDesired types
used by applyAutomationModules, and type buildPlan to return the real Plan type
so plan: buildPlan(['roles']) is strongly typed; ensure mocks like
roleCreateMock/channelCreateMock keep their Jest types (e.g.,
jest.MockedFunction) and replace each "as any" around guild, desired, and plan
with the correct types so TypeScript/ESLint no-any errors are resolved while
keeping test behavior identical.
- Around line 79-600: Spec file exceeds the 250-line guideline — split tests by
module concern. Create focused spec files (e.g., applyPlan.roles.spec.ts,
applyPlan.moderation.spec.ts, applyPlan.onboarding.spec.ts,
applyPlan.automessages.spec.ts, applyPlan.reactionroles.spec.ts,
applyPlan.commandaccess.spec.ts) and move each related it(...) block into the
matching file; keep shared helpers (createGuild, buildPlan) and common mocks
(manifestOnboardingToDiscordEditMock, getWelcomeMessageMock,
getLeaveMessageMock, createMessageMock, updateMessageMock, updateSettingsMock,
updateModerationSettingsMock, listExclusiveRolesMock, removeExclusiveRoleMock,
setExclusiveRoleMock, replaceRoleGrantsMock, errorLogMock) in a single
test-utils module and import them into each spec; ensure each new spec imports
applyAutomationModules and only sets up the beforeEach mocks relevant to that
module so files stay under 250 lines and maintain test isolation.

In `@packages/bot/src/utils/guildAutomation/applyPlan.ts`:
- Around line 256-329: The applyAutomationModules function is too large; extract
each module's logic into small handlers (e.g., applyOnboardingModule,
applyRolesModule, applyModerationModule, applyAutoMessagesModule,
applyReactionRolesModule, applyCommandAccessModule, applyParityModule) that
accept the same context (guild, desired, allowProtected) and return an
object/tuple indicating applied and skipped module names; keep shouldApplyModule
checks in the orchestrator or inside each handler, preserve all existing calls
(manifestOnboardingToDiscordEdit, guild.editOnboarding, applyRolesAndChannels,
autoModService.updateSettings, updateModerationSettings, upsertAutoMessage,
applyReactionRoleRules, guildRoleAccessService.replaceRoleGrants) and await
semantics, aggregate their returned appliedModules/skippedModules into the final
ApplyResult, and ensure types/signature of applyAutomationModules and the
ApplyResult remain unchanged.

In `@packages/bot/src/utils/guildAutomation/captureGuildState.spec.ts`:
- Line 167: Replace the unsafe "as any" cast when calling
captureGuildAutomationState by creating a typed partial/mock for the Guild used
in the test (e.g., use Partial<Guild> or a narrow MockGuild that picks required
properties like id, name, members, roles, channels and stubs methods such as
fetchOnboarding) and cast the test guild to that type (or to unknown then Guild)
so the call becomes type-safe; update the test's guild declaration and the
invocation of captureGuildAutomationState to use this typed mock instead of "as
any".
- Around line 37-127: The createGuild test helper is long and should be split
into sub-factories to improve readability and reuse; refactor createGuild by
extracting createChannels(), createRoles(), and createMembers() functions (or
similarly named helpers) that return the respective objects (members.cache,
roles.cache, channels.cache) and then compose them inside createGuild (preserve
overrides parameter and existing keys like members, roles, channels,
fetchOnboarding, and id); update tests to import/call these sub-factories or
keep createGuild calling them so existing tests keep the same shape.
- Around line 228-235: The test currently mocks fetchOnboarding rejection with a
string literal which is unrealistic; update the mock in
captureGuildAutomationState.spec (the test block that sets
(guild.fetchOnboarding as jest.Mock).mockRejectedValue('onboarding-fail')) to
reject with an Error object instead (e.g., mockRejectedValue(new
Error('onboarding-fail'))) so the test simulates a real thrown Error and still
asserts that captureGuildAutomationState rethrows it.

In `@packages/shared/src/services/guildAutomation/types.ts`:
- Around line 76-86: The types automod and moderationSettings currently include
permissive index signatures ([key: string]: unknown) which bypass compile-time
checks; remove those index signatures from the automod and moderationSettings
interfaces in types.ts and instead declare explicit optional fields for any
known dynamic properties (e.g., add named optional properties for
template-related fields) or, if truly needed, replace the signature with a
documented Record type alias and comment explaining why dynamic keys are
required; also update the corresponding runtime validator autoModSettingsBody
(Zod schema) to match the tightened type shape so compile-time and runtime
validation stay in sync.
- Around line 195-205: In GuildAutomationStatus (fields latestRun and drifts)
replace the loose string types with the existing union types defined earlier:
use AutomationRunType for latestRun.type, AutomationRunStatus for
latestRun.status, AutomationModule for each drifts[].module, and DriftSeverity
for each drifts[].severity; keep createdAt/updatedAt as Date. Update the type
annotations for latestRun and drifts accordingly so the interface uses
AutomationRunType, AutomationRunStatus, AutomationModule, and DriftSeverity for
compile-time safety and IDE autocomplete.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ea81855a-c2e1-4cb4-9310-c172871eb97c

📥 Commits

Reviewing files that changed from the base of the PR and between 1bdb3ff and bb20fa5.

📒 Files selected for processing (14)
  • packages/backend/tests/integration/routes/guildAutomation.test.ts
  • packages/bot/src/functions/automod/commands/index.spec.ts
  • packages/bot/src/functions/automod/commands/index.ts
  • packages/bot/src/functions/management/commands/guildconfig.spec.ts
  • packages/bot/src/functions/management/commands/guildconfig.ts
  • packages/bot/src/functions/management/commands/index.spec.ts
  • packages/bot/src/functions/management/commands/index.ts
  • packages/bot/src/functions/moderation/commands/index.spec.ts
  • packages/bot/src/functions/moderation/commands/index.ts
  • packages/bot/src/utils/guildAutomation/applyPlan.spec.ts
  • packages/bot/src/utils/guildAutomation/applyPlan.ts
  • packages/bot/src/utils/guildAutomation/captureGuildState.spec.ts
  • packages/bot/src/utils/guildAutomation/captureGuildState.ts
  • packages/shared/src/services/guildAutomation/types.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/bot/src/functions/automod/commands/index.ts
  • packages/bot/src/functions/management/commands/index.ts
  • packages/bot/src/functions/moderation/commands/index.ts

Comment thread packages/bot/src/functions/management/commands/guildconfig.ts
Comment thread packages/bot/src/functions/management/commands/guildconfig.ts
Comment thread packages/bot/src/functions/management/commands/guildconfig.ts
Comment thread packages/bot/src/functions/management/commands/guildconfig.ts
Comment thread packages/bot/src/utils/guildAutomation/applyPlan.ts
@LucasSantana-Dev
LucasSantana-Dev enabled auto-merge (squash) March 13, 2026 02:59
@sonarqubecloud

Copy link
Copy Markdown

@sonarqubecloud

Copy link
Copy Markdown

@LucasSantana-Dev
LucasSantana-Dev merged commit 5e6dd9b into main Mar 13, 2026
15 checks passed
@LucasSantana-Dev
LucasSantana-Dev deleted the feature/guild-automation-control-plane branch March 15, 2026 14:06
LucasSantana-Dev added a commit that referenced this pull request May 13, 2026
* feat(automation): add guild automation control-plane routes/services/command

* test(db): add automation coverage and migration

* docs: update README and CHANGELOG for automation control-plane

* fix(automation): align moderation manifest typing with execution flow

* fix(automation): restore protected deletion and onboarding error semantics

* test(automation): raise sonar coverage for control-plane loaders

* test(automation): expand apply-plan coverage branches

* test(automation): cover capture guild state branches for sonar

* test(automation): cover remaining apply and cutover branches

* refactor(automation): drop dashboard guild-channel slice from track A

This branch was successfully deployed

1 active deployment
Preview — f3209380 Deployed Mar 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant