Skip to content

feat(composer): support full bot configuration - #135

Merged
rolling-codes merged 1 commit into
mainfrom
feat/composer-parity
Aug 13, 2026
Merged

feat(composer): support full bot configuration#135
rolling-codes merged 1 commit into
mainfrom
feat/composer-parity

Conversation

@rolling-codes

@rolling-codes rolling-codes commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • add fluent Composer parity for the remaining Bot framework options
  • merge Discord client options safely while rejecting EasyCord-owned constructor keys
  • update the community scaffold and onboarding docs to demonstrate composition-first setup

Verification

  • 81 focused tests passed
  • Ruff critical rules passed
  • git diff check passed

Part 1 of the EasyCord v5.62.0 DX release.

Summary by Sourcery

Extend the Composer builder to fully cover Bot configuration while promoting composition-first usage in scaffolds and docs.

New Features:

  • Expose additional Bot options through Composer, including guild sync settings, AI provider, conversation memory, health command, and cooldown cleanup interval.
  • Allow passing arbitrary discord.Client keyword options via Composer.client_options while preventing collisions with EasyCord-owned Bot options.

Enhancements:

  • Update the community CLI template and developer toolkit docs to showcase Composer-based, composition-first bot setup.

Tests:

  • Add focused tests ensuring Composer-to-Bot option parity, correct forwarding and merging of client options, and validation of EasyCord-owned option keys in client_options.
  • Adjust existing CLI template tests to match the new Composer-based community scaffold and auto_sync configuration representation.

@rolling-codes
rolling-codes marked this pull request as ready for review August 13, 2026 05:20
@github-actions github-actions Bot added the enhancement New feature or request label Aug 13, 2026
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests labels Aug 13, 2026
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@rolling-codes, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 95 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b5e23b79-e3b9-4e5d-b84b-d0b308de5626

📥 Commits

Reviewing files that changed from the base of the PR and between 7af149f and 78b3b30.

📒 Files selected for processing (6)
  • docs/developer-toolkit.md
  • docs/getting-started.md
  • easycord/cli.py
  • easycord/composer.py
  • tests/test_composer.py
  • tests/test_developer_toolkit.py

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.

@sourcery-ai

sourcery-ai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

Extend Composer to fully mirror Bot configuration options, introduce a guarded client_options API for Discord Client kwargs, and shift the community scaffold/docs/tests to a composition-first, Composer-based setup.

Sequence diagram for Composer client_options validation and Bot construction

sequenceDiagram
    actor Developer
    participant Composer
    participant Bot

    Developer->>Composer: Composer()
    Developer->>Composer: auto_sync(False)
    Developer->>Composer: database(SQLiteDatabase(path="data/bot.db"))
    Developer->>Composer: client_options(shard_count=2)
    alt [option in _BOT_OPTION_METHODS]
        Composer-->>Developer: ValueError
    else [no Bot option collision]
        Composer-->>Developer: Composer
    end

    Developer->>Composer: build()
    Composer->>Bot: Bot.__init__(intents, auto_sync, sync_guild_id, load_builtin_plugins, database, db_backend, db_path, db_auto_sync_guilds, guild_sync_timeout, localization, default_locale, translations, auto_translator, ai_provider, enable_conversation_memory, enable_health_command, cooldown_cleanup_interval, **_client_options)
    Bot-->>Developer: Bot
Loading

File-Level Changes

Change Details Files
Extend Composer to cover all relevant Bot configuration options and preserve Bot defaults.
  • Add TYPE_CHECKING-only AIProviderProtocol import and a BOT option mapping table for validation.
  • Add new Composer fields and fluent methods for sync guild configuration, guild sync timeout, localization/translation defaults, AI provider, conversation memory, health command, and cooldown cleanup interval.
  • Ensure build() passes all new options to Bot while preserving existing Bot default values and explicit boolean toggling semantics.
easycord/composer.py
tests/test_composer.py
Introduce a safe client_options API on Composer for forwarding arbitrary discord.Client kwargs while rejecting EasyCord-owned Bot options.
  • Add internal _client_options storage and a client_options(**options) method that merges kwargs with later values winning.
  • Validate client_options keys against the BOT option mapping table and raise ValueError with guidance to use the corresponding Composer method for EasyCord-owned options.
  • Add focused tests covering Bot kwargs forwarding, merge semantics, and rejection of EasyCord-owned options.
easycord/composer.py
tests/test_composer.py
Update CLI community template, docs, and CLI tests to make Composer-based, composition-first setup the primary path.
  • Refactor the community bot scaffold from direct Bot construction to a Composer-based pipeline that wires database, builtin plugins, and community plugins before build().
  • Adjust CLI tests to account for Composer usage, including detecting both direct and fluent auto_sync configuration and asserting composition-first characteristics.
  • Clarify developer toolkit documentation to describe the community template as a Composer-based composition-first bot and add getting-started docs showing when to prefer Composer vs direct Bot construction.
easycord/cli.py
tests/test_developer_toolkit.py
docs/developer-toolkit.md
docs/getting-started.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Composer parity: full Bot options + safe Discord client_options forwarding

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Extend Composer with remaining Bot framework options for configuration parity.
• Add safe client_options() forwarding while rejecting EasyCord-owned Bot constructor keys.
• Update community scaffold, docs, and tests to promote composition-first setup.
Diagram

graph TD
Dev([Developer]) --> CLI["easycord/cli.py"] --> Template["community bot.py"] --> Composer["easycord/composer.py"] --> Bot["easycord/bot.py"] --> Discord{{"discord.Client"}}
Tests(("tests/*")) --> Composer
Docs["docs/*.md"] --> Dev
subgraph Legend
  direction LR
  _act(["Actor"]) ~~~ _mod["Module/File"] ~~~ _ext{{"External"}}
end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Allow build(**discord_kwargs) instead of client_options()
  • ➕ Single call-site for Discord kwargs; fewer builder methods
  • ➖ Harder to validate/reject collisions with EasyCord-owned Bot options
  • ➖ Encourages mixing Bot and client configuration at the final step (less readable/less discoverable)
2. Expose a typed DiscordClientOptions dataclass on Composer
  • ➕ Stronger typing and discoverability for common discord.py kwargs
  • ➕ Central place for validation/defaults
  • ➖ Needs ongoing maintenance as discord.py adds options
  • ➖ Still requires an escape hatch for arbitrary kwargs
3. Pass a nested client_options dict into Bot (Bot(..., client_options={...}))
  • ➕ Keeps Bot signature stable while clearly separating concerns
  • ➖ Requires Bot API change and extra indirection (Bot must unpack into discord.Client)
  • ➖ Potentially larger breaking change surface

Recommendation: The chosen approach (Composer.client_options(**kwargs) with explicit rejection of EasyCord-owned keys) is the best trade-off: it preserves Bot option parity, avoids kwargs collisions at build-time, and keeps fluent configuration readable. If typing becomes a pain point later, consider layering a small typed helper for common discord.py options while keeping client_options() as the escape hatch.

Files changed (6) +254 / -16

Enhancement (2) +98 / -13
cli.pySwitch community scaffold to composition-first Composer setup +12/-12

Switch community scaffold to composition-first Composer setup

• Updates the community project template to build the bot via Composer (auto_sync, database, builtin plugins, and plugin registration) rather than direct Bot(...) construction. Removes redundant post-construction add_plugins calls since plugins are added during composition.

easycord/cli.py

composer.pyAdd full Bot-option parity + safe client_options forwarding +86/-1

Add full Bot-option parity + safe client_options forwarding

• Extends Composer to support remaining Bot framework options (sync guild, guild sync timeout, AI provider, conversation memory, health command, cooldown cleanup interval). Introduces client_options(**kwargs) that merges arbitrary discord.Client kwargs while rejecting EasyCord-owned Bot options to prevent collisions in build().

easycord/composer.py

Tests (2) +136 / -2
test_composer.pyAdd focused Composer parity and client_options tests +130/-0

Add focused Composer parity and client_options tests

• Introduces new tests asserting Composer preserves Bot defaults, forwards new Bot options and discord.Client kwargs, supports explicit boolean disabling, merges repeated client_options with last-write-wins semantics, and rejects EasyCord-owned option names.

tests/test_composer.py

test_developer_toolkit.pyAdjust CLI template tests for Composer-based scaffolding +6/-2

Adjust CLI template tests for Composer-based scaffolding

• Updates assertions to accept either direct Bot(auto_sync=False) or fluent .auto_sync(False) depending on template output. Tightens community template checks to require Composer usage and build() call presence.

tests/test_developer_toolkit.py

Documentation (2) +20 / -1
developer-toolkit.mdClarify community template is Composer-based +1/-1

Clarify community template is Composer-based

• Updates the template description to explicitly call out a composition-first Composer bot scaffold. Helps set expectations for generated projects and onboarding.

docs/developer-toolkit.md

getting-started.mdDocument when to use Composer vs direct Bot() construction +19/-0

Document when to use Composer vs direct Bot() construction

• Adds a short example showing fluent Composer configuration for bots with multiple options/plugins. Provides guidance on choosing direct Bot(...) vs Composer for readability.

docs/getting-started.md

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="tests/test_developer_toolkit.py" line_range="248-251" />
<code_context>
     test_source = (project / "tests" / "test_bot.py").read_text(encoding="utf-8")

-    assert "load_builtin_plugins=True" in bot_source
+    assert "Composer()" in bot_source
+    assert ".auto_sync(False)" in bot_source
+    assert ".builtin_plugins()" in bot_source
+    assert ".build()" in bot_source
     assert "ModerationPlugin()" in bot_source
     assert "EconomyPlugin()" in bot_source
</code_context>
<issue_to_address>
**suggestion (testing):** Add an assertion that the community template does not instantiate `Bot` directly to fully guarantee composition-first setup.

The current assertions ensure the template uses `Composer` with the expected calls. To fully enforce the composition-first invariant, also assert that `Bot(` does not appear in `bot_source` (e.g. `assert "Bot(" not in bot_source`) so tests fail if a direct `Bot` instantiation is reintroduced alongside `Composer`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +248 to +251
assert "Composer()" in bot_source
assert ".auto_sync(False)" in bot_source
assert ".builtin_plugins()" in bot_source
assert ".build()" in bot_source

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Add an assertion that the community template does not instantiate Bot directly to fully guarantee composition-first setup.

The current assertions ensure the template uses Composer with the expected calls. To fully enforce the composition-first invariant, also assert that Bot( does not appear in bot_source (e.g. assert "Bot(" not in bot_source) so tests fail if a direct Bot instantiation is reintroduced alongside Composer.

@rolling-codes
rolling-codes merged commit 4562634 into main Aug 13, 2026
16 checks passed
@rolling-codes
rolling-codes deleted the feat/composer-parity branch August 13, 2026 05:22
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Tests import easycord.composer 📘 Rule violation ⌂ Architecture
Description
tests/test_composer.py imports Composer from the internal submodule easycord.composer instead
of the public top-level easycord API. This breaks the public-API-only import rule for code outside
the easycord/ package and increases coupling to internal module layout.
Code

tests/test_composer.py[9]

+from easycord.composer import Composer
Relevance

●●● Strong

Repo frequently accepts test import hygiene fixes; public-API-only import in tests likely enforced.

PR-#62
PR-#82

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 526296 requires code outside easycord/ to import symbols only from the public
easycord top-level package. The new test imports Composer from the easycord.composer submodule
even though Composer is publicly re-exported in easycord/__init__.py.

Rule 526296: Only import public easycord API from outside the package
tests/test_composer.py[9-9]
easycord/init.py[21-26]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A test file outside the `easycord/` package imports `Composer` via `from easycord.composer import Composer`, but external code must import only from the public top-level `easycord` package API.

## Issue Context
`Composer` is already re-exported from `easycord/__init__.py`, so the test can import it as `from easycord import Composer`.

## Fix Focus Areas
- tests/test_composer.py[9-9]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. guild_id not mapped 🐞 Bug ≡ Correctness
Description
Composer.client_options() accepts guild_id, but Bot.__init__ only consumes sync_guild_id, so
passing guild_id via client_options cannot set the dev sync target and leaves command syncing in
the wrong mode (global vs guild). This is especially likely because the docs/config use guild_id
naming, while Composer exposes sync_guild_id.
Code

easycord/composer.py[R187-194]

+        for option in options:
+            method = _BOT_OPTION_METHODS.get(option)
+            if method is not None:
+                raise ValueError(
+                    f"{option!r} is an EasyCord Bot option; "
+                    f"use Composer.{method}(...) instead of client_options()."
+                )
+        self._client_options.update(options)
Relevance

●●● Strong

Correctness bug: misnamed option silently breaks sync mode; team tends to accept subtle
parameter/forwarding fixes.

PR-#129

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The docs/config encourage using guild_id for the dev sync target, but Bot only assigns
_sync_guild_id from the sync_guild_id parameter; guild_id is not a Bot option. Composer’s
client_options only rejects exact Bot option names and does not include guild_id, so the misnamed
key can slip through and won’t affect _sync_guild_id.

docs/getting-started.md[86-89]
easycord/config.py[249-274]
easycord/bot.py[77-104]
easycord/composer.py[20-38]
easycord/composer.py[180-195]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`Composer.client_options()` only rejects keys listed in `_BOT_OPTION_METHODS`. The repo’s docs/config surface the development sync setting as `guild_id`, but `Bot`/`Composer` use `sync_guild_id`. As a result, `Composer().client_options(guild_id=...)` won’t configure the bot’s sync target.

### Issue Context
- `BotConfig` uses `guild_id` and maps it to `sync_guild_id` when building a `Bot`.
- `Composer` exposes `.sync_guild_id(...)`, but `client_options()` does not reject or translate `guild_id`.

### Fix Focus Areas
- easycord/composer.py[20-38]
- easycord/composer.py[180-195]

### Suggested fix
Choose one:
1) **Reject** `guild_id` in `client_options()` with a clear message directing users to `Composer.sync_guild_id(...)`.
2) **Alias** `guild_id` to `sync_guild_id` (either in `client_options()` or by adding it to `_BOT_OPTION_METHODS` mapping to `sync_guild_id`). Ensure it can’t conflict with an explicit `.sync_guild_id(...)` call (pick a precedence rule and test it).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context
✅ Compliance rules (platform): 41 rules

Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread tests/test_composer.py
import discord
import pytest

from easycord.composer import Composer

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Tests import easycord.composer 📘 Rule violation ⌂ Architecture

tests/test_composer.py imports Composer from the internal submodule easycord.composer instead
of the public top-level easycord API. This breaks the public-API-only import rule for code outside
the easycord/ package and increases coupling to internal module layout.
Agent Prompt
## Issue description
A test file outside the `easycord/` package imports `Composer` via `from easycord.composer import Composer`, but external code must import only from the public top-level `easycord` package API.

## Issue Context
`Composer` is already re-exported from `easycord/__init__.py`, so the test can import it as `from easycord import Composer`.

## Fix Focus Areas
- tests/test_composer.py[9-9]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread easycord/composer.py
Comment on lines +187 to +194
for option in options:
method = _BOT_OPTION_METHODS.get(option)
if method is not None:
raise ValueError(
f"{option!r} is an EasyCord Bot option; "
f"use Composer.{method}(...) instead of client_options()."
)
self._client_options.update(options)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Guild_id not mapped 🐞 Bug ≡ Correctness

Composer.client_options() accepts guild_id, but Bot.__init__ only consumes sync_guild_id, so
passing guild_id via client_options cannot set the dev sync target and leaves command syncing in
the wrong mode (global vs guild). This is especially likely because the docs/config use guild_id
naming, while Composer exposes sync_guild_id.
Agent Prompt
### Issue description
`Composer.client_options()` only rejects keys listed in `_BOT_OPTION_METHODS`. The repo’s docs/config surface the development sync setting as `guild_id`, but `Bot`/`Composer` use `sync_guild_id`. As a result, `Composer().client_options(guild_id=...)` won’t configure the bot’s sync target.

### Issue Context
- `BotConfig` uses `guild_id` and maps it to `sync_guild_id` when building a `Bot`.
- `Composer` exposes `.sync_guild_id(...)`, but `client_options()` does not reject or translate `guild_id`.

### Fix Focus Areas
- easycord/composer.py[20-38]
- easycord/composer.py[180-195]

### Suggested fix
Choose one:
1) **Reject** `guild_id` in `client_options()` with a clear message directing users to `Composer.sync_guild_id(...)`.
2) **Alias** `guild_id` to `sync_guild_id` (either in `client_options()` or by adding it to `_BOT_OPTION_METHODS` mapping to `sync_guild_id`). Ensure it can’t conflict with an explicit `.sync_guild_id(...)` call (pick a precedence rule and test it).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants