chore: restrict sourcery to local only, skip in CI#110
Merged
JacobCoffee merged 1 commit intomainfrom Nov 23, 2025
Merged
Conversation
Contributor
Reviewer's GuideIntroduces a new Discord bot microservice ( Sequence diagram for guild join and API-backed guild registrationsequenceDiagram
actor U as "Discord Guild Owner"
participant Discord as "Discord Gateway"
participant Bot as "Byte (discord.Bot)"
participant APIClient as "ByteAPIClient"
participant API as "API Service (/api/guilds/create)"
participant DB as "Database"
U->>Discord: "Invite bot to guild"
Discord->>Bot: "GUILD_CREATE event"
Bot->>Bot: "on_guild_join(guild) handler"
Bot->>Bot: "self.tree.sync(guild)"
Bot->>APIClient: "Construct API URL for /api/guilds/create\n?guild_id=&guild_name="
Bot->>API: "HTTP POST /api/guilds/create\nwith guild_id, guild_name"
API->>DB: "Create guild record if not exists"
DB-->>API: "Persisted guild data"
API-->>Bot: "201 CREATED or error status"
Bot->>Bot: "Build status Embed (success or failure)"
Bot->>Discord: "Send embed to dev guild internal channel"
Discord-->>U: "(Optionally) Feedback via dev guild logging"
Class diagram for core bot, configuration, and API clientclassDiagram
class Byte {
+Byte(command_prefix: list[str], intents: Intents, activity: Activity)
+setup_hook() async
+load_cogs() async
+on_ready() async
+on_message(message: Message) async
+on_command_error(ctx: Context, error: CommandError) async
+on_member_join(member: Member) async
+on_guild_join(guild: discord.Guild) async
}
class BotEntrypoint {
+main() void
+run_bot() void
}
class BotSettings {
+discord_token: str
+discord_dev_guild_id: int | None
+discord_dev_user_id: int | None
+command_prefix: list[str]
+presence_url: str
+api_service_url: str
+plugins_dir: Path
+environment: str
+debug: bool
+assemble_command_prefix(value: list[str]) list[str]
+assemble_presence_url(value: str) str
+get_discord_token(value: str | None) str
}
class LogSettingsBot {
+level: int
+discord_level: int
+websockets_level: int
+asyncio_level: int
+httpx_level: int
+format: str
+file: Path | None
}
class DiscordSettings {
+TOKEN: str
+COMMAND_PREFIX: list[str]
+DEV_GUILD_ID: int
+DEV_USER_ID: int
+DEV_GUILD_INTERNAL_ID: int
+PLUGINS_LOC: Path
+PLUGINS_DIRS: list[Path]
+PRESENCE_URL: str
+assemble_command_prefix(value: list[str]) list[str]
+assemble_presence_url(value: str) str
}
class ProjectLogSettings {
+LEVEL: int
+DISCORD_LEVEL: int
+WEBSOCKETS_LEVEL: int
+ASYNCIO_LEVEL: int
+HTTP_CORE_LEVEL: int
+HTTPX_LEVEL: int
+FORMAT: str
+FILE: Path
}
class ProjectSettings {
+DEBUG: bool
+ENVIRONMENT: str
+VERSION: str
}
class SettingsModule {
+discord: DiscordSettings
+log: ProjectLogSettings
+project: ProjectSettings
+load_settings() (DiscordSettings, ProjectLogSettings, ProjectSettings)
}
class LogModule {
+setup_logging() void
+get_logger(name: str) Logger
}
class APIError {
+status_code: int | None
+APIError(message: str, status_code: int | None)
}
class ByteAPIClient {
-base_url: str
-client: httpx.AsyncClient
+ByteAPIClient(base_url: str, timeout: float = 10.0)
+close() async
+__aenter__() async Self
+__aexit__(*args: object) async void
+create_guild(guild_id: int, guild_name: str, prefix: str = "!", **kwargs) async GuildSchema
+get_guild(guild_id: int) async GuildSchema | None
+update_guild(guild_id: UUID, **updates) async GuildSchema
+delete_guild(guild_id: UUID) async None
+get_or_create_guild(guild_id: int, guild_name: str, prefix: str = "!") async GuildSchema
+health_check() async dict[str, Any]
}
class ChecksModule {
+is_guild_admin() Check
+is_byte_dev() Check
}
BotEntrypoint --> Byte : "creates and runs"
Byte --> SettingsModule : "uses for config"
Byte --> LogModule : "uses get_logger()"
Byte --> ByteAPIClient : "uses for API calls (guilds, health)"
SettingsModule --> DiscordSettings
SettingsModule --> ProjectLogSettings
SettingsModule --> ProjectSettings
LogModule --> ProjectLogSettings : "reads log settings"
ByteAPIClient --> APIError : "raises on failures"
Class diagram for views and plugins in the new bot serviceclassDiagram
class ButtonEmbedView {
+author_id: int
+bot: Bot
+original_embed: Embed
+minified_embed: Embed
+ButtonEmbedView(author: int, bot: Bot, original_embed: Embed, minified_embed: Embed, *args, **kwargs)
+delete_interaction_check(interaction: Interaction) async bool
+delete_button_callback(interaction: Interaction) async void
+learn_more_button_callback(interaction: Interaction) async void
+delete_button(interaction: Interaction, button: Button) async void
+learn_more_button(interaction: Interaction, button: Button) async void
}
class ExtendedEmbed {
+add_field_dict(field: Field) ExtendedEmbed
+add_field_dicts(fields: list[Field]) ExtendedEmbed
+from_field_dicts(..., fields: list[Field] | None) ExtendedEmbed
+deepcopy() ExtendedEmbed
}
class Field {
+name: Any
+value: Any
+inline: bool (optional)
}
class RuffView {
}
class PEPView {
}
class HelpThreadView {
+author: Member
+guild_id: int
+bot: Bot
+HelpThreadView(author: Member, guild_id: int, bot: Bot, timeout: float | None)
+setup() async void
+delete_interaction_check(interaction: Interaction) async bool
+solve_button_callback(interaction: Interaction, button: Button) async void
+remove_button_callback(interaction: Interaction, button: Button) async void
}
class ConfigView {
+ConfigView(preselected: str | None)
}
class ConfigKeyView {
+ConfigKeyView(option: dict[str, Any])
}
class ConfigModal {
+option: dict[str, Any] | None
+ConfigModal(title: str, sub_setting: dict[str, str] | None, sub_settings: list[dict[str, str]] | None, option: dict[str, Any] | None)
+on_submit(interaction: Interaction) async void
+on_error(interaction: Interaction, error: Exception) async void
}
class ConfigSelect {
+ConfigSelect(preselected: str | None)
+callback(interaction: Interaction) async void
}
class ConfigKeySelect {
+ConfigKeySelect(option: dict[str, Any])
+callback(interaction: Interaction) async void
}
class AstralCog {
+_rules: dict[str, RuffRule]
+Astral(bot: Bot, rules: list[RuffRule])
+_rule_autocomplete(interaction: Interaction, current_rule: str) async list[Choice[str]]
+ruff_rule(interaction: Interaction, rule: str) async void
+format_code(interaction: Interaction, code_block: str) async void
}
class PythonCog {
+_peps: dict[int, PEP]
+Python(bot: Bot, peps: list[PEP])
+_pep_autocomplete(interaction: Interaction, current_pep: str) async list[Choice[str]]
+peps(interaction: Interaction, pep: int) async void
}
class AdminCommands {
+admin(ctx: Context) async void
+list_cogs(ctx: Context) async void
+reload(ctx: Context, cog: str) async void
+reload_all_cogs(ctx: Context) async void
+reload_single_cog(ctx: Context, cog: str, send_message: bool) async str
+tree_sync(interaction: Interaction) async void
+bootstrap_guild(ctx: Context, guild_id: int | None) async void
}
class ForumCommands {
+solved(ctx: Context) async void
+tags(ctx: Context) async void
+tree_sync(interaction: Interaction, user: Member) async void
}
class EventsCog {
+on_thread_create(thread: Thread) async void
}
class GeneralCommands {
+show_paste(interaction: Interaction) async void
}
class ConfigCog {
+config_options: list[dict[str, Any]]
+Config(bot: Bot)
+_config_autocomplete(interaction: Interaction, current: str) async list[Choice[str]]
+config_rule(interaction: Interaction, setting: str | None) async void
}
RuffView --|> ButtonEmbedView
PEPView --|> ButtonEmbedView
ExtendedEmbed --|> Embed
ButtonEmbedView --|> View
HelpThreadView --|> View
ConfigView --|> View
ConfigKeyView --|> View
ConfigModal --|> Modal
ConfigSelect --|> Select
ConfigKeySelect --|> Select
AstralCog --|> Cog
PythonCog --|> Cog
AdminCommands --|> Cog
ForumCommands --|> Cog
EventsCog --|> Cog
GeneralCommands --|> Cog
ConfigCog --|> Cog
AstralCog --> RuffView : "creates for /ruff"
PythonCog --> PEPView : "creates for /pep"
ForumCommands --> HelpThreadView : "invokes solve via button"
ConfigCog --> ConfigView
ConfigView --> ConfigSelect
ConfigKeyView --> ConfigKeySelect
ConfigSelect --> ConfigModal
ConfigKeySelect --> ConfigModal
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
🚅 Environment byte-pr-110 in byte has no services deployed. |
Contributor
There was a problem hiding this comment.
Hey there - I've reviewed your changes - here's some feedback:
- There are now two separate configuration systems for the bot (services/bot/src/byte_bot/config.py and services/bot/src/byte_bot/lib/settings.py) with overlapping responsibilities; consider consolidating them into a single settings source to avoid drift and confusion.
- Several plugins (e.g., litestar, forums, events, admin) hard-code guild, channel, and role IDs; it would be more maintainable to move these IDs into configuration or a central mapping so they can be changed without touching code.
- The GitHub issue modal logic is duplicated between the litestar-specific plugin and the generic github plugin; consider extracting a shared helper or base modal to reduce duplication and keep behavior consistent.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- There are now two separate configuration systems for the bot (services/bot/src/byte_bot/config.py and services/bot/src/byte_bot/lib/settings.py) with overlapping responsibilities; consider consolidating them into a single settings source to avoid drift and confusion.
- Several plugins (e.g., litestar, forums, events, admin) hard-code guild, channel, and role IDs; it would be more maintainable to move these IDs into configuration or a central mapping so they can be changed without touching code.
- The GitHub issue modal logic is duplicated between the litestar-specific plugin and the generic github plugin; consider extracting a shared helper or base modal to reduce duplication and keep behavior consistent.
## Individual Comments
### Comment 1
<location> `Makefile:120` </location>
<code_context>
@$(UV) run --no-sync coverage xml
-check-all: lint test fmt-check coverage ## Run all linting, tests, and coverage checks
+check-all: lint type-check fmt test ## Run all linting, tests, and coverage checks
+
+ci: check-all ## Run all checks for CI
</code_context>
<issue_to_address>
**issue:** check-all description is now misleading since coverage is no longer run
Since `check-all` no longer runs `coverage`, consider either adding `coverage` back to the target or updating the help text so it accurately reflects what the target now does (and how `ci` relates to it).
</issue_to_address>
### Comment 2
<location> `services/bot/src/byte_bot/bot.py:98` </location>
<code_context>
+ embed.add_field(name="Guild", value=ctx.guild.name if ctx.guild else "DM")
+ embed.add_field(name="Location", value=f"[Jump]({ctx.message.jump_url})")
+ embed.set_footer(text=f"Time: {ctx.message.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
+ await ctx.send(embed=embed, ephemeral=True)
+
+ @staticmethod
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `ephemeral=True` with `ctx.send` is not supported and will be ignored or error
`ephemeral` is only valid on interaction responses (e.g., `InteractionResponse.send_message`), not on `Context.send`. Here it will either be ignored or may raise, depending on the library version. To make this message private, use an interaction-based response instead; otherwise remove `ephemeral=True`.
</issue_to_address>
### Comment 3
<location> `services/bot/src/byte_bot/plugins/events.py:3-12` </location>
<code_context>
+"""Plugins for events."""
+
+from threading import Thread
+from typing import cast
+
+from discord import Embed
+from discord.ext.commands import Bot, Cog
+
+from byte_bot.byte.lib.common.assets import litestar_logo_yellow
+from byte_bot.byte.lib.common.links import mcve
+from byte_bot.byte.lib.utils import linker
+from byte_bot.byte.views.forums import HelpThreadView
+
+__all__ = ("Events", "setup")
+
+
+class Events(Cog):
+ """Events cog."""
+
+ def __init__(self, bot: Bot) -> None:
+ """Initialize cog."""
+ self.bot = bot
+
+ @Cog.listener()
+ async def on_thread_create(self, thread: Thread) -> None:
+ """Handle thread create event.
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Thread type annotation should use `discord.Thread`, not `threading.Thread`
Since this listener handles Discord thread creation, the parameter should be typed as `discord.Thread`, not `threading.Thread`, to reflect the actual runtime type and avoid confusing type checkers. Please import and use `discord.Thread` (or `from discord import Thread as DiscordThread`) here.
</issue_to_address>
### Comment 4
<location> `services/bot/src/byte_bot/plugins/admin.py:83` </location>
<code_context>
+ async def reload_single_cog(self, ctx: Context, cog: str, send_message: bool = True) -> str:
+ """Reload a single cog."""
+ try:
+ await self.bot.reload_extension(f"plugins.{cog}")
+ message = f"Cog `{cog}` reloaded!"
+ except (commands.ExtensionNotLoaded, commands.ExtensionNotFound) as e:
</code_context>
<issue_to_address>
**issue (bug_risk):** Reload path for cogs likely doesn’t match how they were originally loaded
`Byte.load_cogs` loads extensions using full module paths (e.g., `byte_bot...`), but `reload_extension` here assumes `plugins.{cog}`. If those don’t match, reloads will consistently raise `ExtensionNotLoaded`/`ExtensionNotFound`. Consider reloading by using the original extension name from `self.bot.extensions` (or persisting the full module path when listing cogs) instead of constructing `plugins.{cog}`.
</issue_to_address>
### Comment 5
<location> `services/bot/src/byte_bot/views/config.py:95` </location>
<code_context>
+ Args:
+ interaction: Interaction object.
+ """
+ selected_option = next(option for option in config_options if option["label"] == self.values[0])
+ if "sub_settings" in selected_option:
+ view = ConfigKeyView(selected_option)
</code_context>
<issue_to_address>
**issue:** `next(...)` without a default will raise if the selected label is missing from `config_options`
If `self.values[0]` doesn’t match any entry in `config_options`, this will raise `StopIteration` and break the interaction. Please pass a default to `next(...)` and handle the “not found” case (for example, by showing an error to the user) instead of letting it raise.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Adds stages restriction to Sourcery hook so it only runs locally and is skipped when CI runs prek with --hook-stage manual. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
8ab5c59 to
88f93fc
Compare
JacobCoffee
added a commit
that referenced
this pull request
Nov 23, 2025
- Resolve conflict with .prek-config.yaml (removed, using .pre-commit-config.yaml) - Merge PR #110 (restrict sourcery to local only) - Merge PR #112 (migrate to dependency-groups.dev) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
stages: [commit]to Sourcery hook configuration in both.prek-config.yamland.pre-commit-config.yaml--hook-stage manualChanges
.prek-config.yaml: Addedstages: [commit]to Sourcery hook (line 31).pre-commit-config.yaml: Addedstages: [commit]to Sourcery hook (line 31)Testing
✅ Verified Sourcery is skipped when running
prek run --hook-stage manual(CI behavior)✅ Verified Sourcery runs when running
prek run --hook-stage pre-commit(local commits)Result
Sourcery feedback remains available for developers locally while being excluded from CI workflows, reducing CI runtime.
🤖 Generated with Claude Code
Summary by Sourcery
Introduce a new Discord bot service and migrate the project build configuration to uv, while tightening developer tooling and CI targets.
New Features:
Enhancements:
Build:
CI: