Skip to content

V3 - Tidy up and rewrite - #729

Merged
BrentOates merged 77 commits into
mainfrom
feature/v3
Oct 6, 2026
Merged

BrentOates merged 77 commits into
mainfrom
feature/v3

Conversation

@BrentOates

Copy link
Copy Markdown
Owner

No description provided.

BrentOates and others added 30 commits October 5, 2026 21:25
Add .editorconfig and reformat src/**/*.ts (no functional changes).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- node:test harness (tsconfig.test.json, npm test, CI step) with env,
  Logger and in-memory sequelize tests; createSequelize factory
- src/config/env.ts validates runtime config at startup; .env.example updated
- Logger accepts unknown errors (prints stack), adds level helpers, ISO timestamps
- typed linting with no-floating-promises / no-misused-promises; fix violations
- enable strictNullChecks and fix fallout

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… events

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…king columns

Replaces sequelize.sync({ alter: true }) at startup with versioned
migrations; test helper runs the migrator on :memory: databases.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Activity score is +1 per human guild message with no decrement; honeypot
logic moved to handleHoneypot. Message/member events use AuditEmbed and
handle partials. /post, /noroles and /rolesince defer first, check
permissions, and paginate long listings into a text attachment.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…rofile and lifecycle events

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ug, simulate and member events

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…Registrar to src/framework

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…alidator, bump to 3.0.0

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Comprehensive guide based on actual code. Covers setup (local & Docker),
environment configuration (all vars with defaults), Discord setup (intents
and permissions), all commands organized by category with descriptions,
behaviour (onboarding flow, audit logs, honeypot, activity score, birthdays
with timezone and Feb 29 handling, scheduled job order), data storage and
retention policy, and contributing guidelines with code examples.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…ite migrator

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- ES modules, run src/*.ts directly with Node type stripping (no build step)
- explicit .ts import extensions, import type, erasable syntax only
- replace runtime file discovery with static command/event registries and
  validateRegistry (with tests)
- replace node-schedule with croner; Scheduler.stop() is now async and
  waits for an in-flight run
- load .env via --env-file-if-exists; drop dotenv, tslib, copyfiles, rimraf,
  tsc-watch; single tsconfig covering src and tests

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ctories

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Replace the env Proxy, lazy database singleton, honeypot static map and
module-level shutdown hooks with an App built once in index.ts. Handlers
receive it as run(app, client, ...) for events and ctx.app for commands;
services become plain functions taking the db or app. The clock is
injected where it decides behaviour.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The dispatcher replies with a UserError's message and logs it at info,
while other errors keep the generic reply and error log.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add fakeUser, fakeMember, fakeGuild, fakeConfig, fakeAuditChannel and a
shared fakeCommandContext, centralise structural casts in stub(), and
drop the duplicated ad-hoc builders across test files.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… script

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Read the guild's profiles before the member fetch so profiles created
while it runs are not mistaken for departed members.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 07d880a79b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/events/guildMemberUpdate.ts Outdated
BrentOates and others added 4 commits October 6, 2026 17:14
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…state

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 27639d1b08

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/config/groups/birthdays.ts Outdated
Comment on lines +68 to +70
const status = await refreshCalendar(ctx.app, client, ctx.config);
if (previousPath && previousPath !== path) {
await deleteCalendarMessage(client, previousPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the old calendar when initial population fails

When the new message is sent and saved but its initial refreshCalendar returns failed—for example, because Discord transiently rejects the edit—this still deletes the previous working calendar and leaves the stored replacement showing only the placeholder until a later sync succeeds. Fresh evidence beyond the earlier send/save ordering issue is that refreshCalendar now converts fetch/edit errors into non-updated statuses, but those statuses still fall through to this unconditional deletion; remove the old message only after the replacement reports updated, or restore the previous path on failure.

Useful? React with 👍 / 👎.

Comment thread src/commands/config/groups/birthdays.ts Outdated
Comment on lines +107 to +110
await deleteCalendarMessage(ctx.interaction.client, ctx.config.birthdayCalendarMessagePath);
ctx.config = await updateConfig(ctx.app, ctx.config.serverId, {
birthdayCalendarMessagePath: null,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the calendar path when message deletion fails

When Discord transiently rejects the fetch or deletion of an existing calendar message, deleteCalendarMessage suppresses the error, so this still clears the only stored path and replies that the calendar was removed. The visible message is then orphaned and can no longer be updated or retried by the bot; distinguish an already-missing message from other failures and retain the path or report the failure when deletion did not succeed.

Useful? React with 👍 / 👎.

Comment thread src/services/DataCheckService.ts Outdated
Comment on lines +78 to +81
await updateConfig(app, config.serverId, {
accessFailureCount: next.accessFailureCount,
firstAccessFailureAt: next.firstAccessFailureAt,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid recreating a guild config after a concurrent purge

If the bot is removed from a guild while this data check is awaiting Discord, the guildDelete handler can purge the guild after getConfigs() took its snapshot, and this subsequent updateConfig then upserts a fresh row for the already-purged guild. That defeats the documented immediate deletion and leaves the bot repeatedly checking retained orphan state until the grace policy purges it again; retention updates should only update an existing row or confirm the guild was not concurrently removed before writing.

Useful? React with 👍 / 👎.

Comment thread SECURITY.md Outdated
| 1.0.x | :no_entry: |
| 1.1.x | :no_entry: |
| 1.2.x | :no_entry: |
| 1.3.x | :no_entry: |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge List the current v3 release as supported

The change releases the project as version 3.0.0, but the security policy still identifies only 2.0.x as supported and does not mention v3. Users of the newly documented release therefore cannot tell whether security reports and fixes apply to their version; add the 3.0.x support status and update the legacy status if v2 is no longer maintained.

Useful? React with 👍 / 👎.

BrentOates and others added 4 commits October 6, 2026 17:36
If the new calendar cannot be populated, restore the previous path, delete
the new placeholder and keep the existing calendar.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
deleteCalendarMessage now reports deleted, already-missing or failed; remove
only clears the stored path when the message is gone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add updateExistingConfig (update-only) and use it for the data check's
access-state writes, skipping the guild if its row is gone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 210f9aed0b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .npmrc
Comment thread src/config/env.ts
Comment on lines +52 to +54
if (
(cronFields.length !== 5 && cronFields.length !== 6) ||
!cronFields.every((field) => CRON_FIELD.test(field))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject cron expressions Croner cannot schedule

When JOB_SCHEDULE contains five syntactically simple but invalid fields, such as 99 99 99 99 99, this character-only check accepts it even though Croner rejects the out-of-range values. ready.ts then catches the scheduler construction error and leaves cleanup, birthday notifications, and calendar refreshes disabled while the bot otherwise remains online; validate by constructing/parsing the Croner expression during environment loading so invalid configuration fails startup.

Useful? React with 👍 / 👎.

Comment thread src/commands/config/groups/birthdays.ts Outdated
Comment on lines +36 to +38
if (
!perms?.has(PermissionFlagsBits.ViewChannel) ||
!perms.has(PermissionFlagsBits.SendMessages)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require history access before creating a calendar

When a channel override grants the bot View Channel and Send Messages but denies Read Message History, creation can initially succeed because the just-sent message is cached, but after a restart or cache eviction refreshCalendar() cannot fetch that message and every scheduled/manual sync reports it missing. Include PermissionFlagsBits.ReadMessageHistory in this permission check so the command does not persist a calendar that cannot be maintained.

Useful? React with 👍 / 👎.

Comment on lines +161 to +163
.onConflictDoUpdate({
target: [userProfiles.serverId, userProfiles.userId],
set: { screeningPendingAt: at, updatedAt: at },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reset stale onboarding state for a new screening cycle

When a previously onboarded member leaves while the bot is offline and rejoins a screening-enabled guild before cleanup, their profile still contains onboardedAt. Recording the new pending state here preserves that old marker, and guildMemberUpdate subsequently skips both full and partial screening-completion events as “already recorded,” so the returning member never receives configured roles or a welcome message. Treat a definitively new pending join as a new onboarding cycle and clear or otherwise supersede the stale completion marker.

Useful? React with 👍 / 👎.

BrentOates and others added 7 commits October 6, 2026 18:13
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Document the onboarding state machine, claim the single onboarding per
membership atomically before any step runs, drop state from previous
memberships using the join time, catch up on screenings that finished
while offline, and replace the piecemeal event tests with a table-driven
state machine test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Classify refresh failures (missing, no access, transient) instead of
reporting every error as missing, serialise refreshes per guild so the
last edit has the latest profiles, replace the calendar a create actually
overwrote (not the one it first saw), never clobber a concurrent create on
rollback, skip purged guilds in the scheduled refresh, and refresh the
calendar when a member with a birthday leaves. Consolidate the calendar
tests into a table-driven state machine test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Trust a stored hash only while the recorded scope matches the current one
(an unrecorded scope re-registers to record it), and stop retrying the old
dev guild cleanup on every start once the bot can no longer reach that
guild. Replace the piecemeal registrar tests with a table-driven state
machine test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Re-read each config before the data check acts on it, never purge a guild
the gateway still lists, delete departed members' profiles by row id so a
rejoiner's new profile survives, and re-record pending screenings when the
bot is added to a guild. Consolidate the data check tests into a
table-driven state machine test with the concurrent guildDelete/guildCreate
cases.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@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.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

BrentOates and others added 7 commits October 6, 2026 20:29
Add fitMessage to truncate user-configured welcome text with a note, and
use it for welcome preview/get and the simulate onboard dry run.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Wrap the full member fetch in noroles and rolesince so a discord.js
GatewayRateLimitError becomes a UserError with the retry time.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Reject on an oversized content-length header, then stream the body and
cancel as soon as 10 MB is exceeded, all under the existing 10s timeout.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Chunk the mention list so each announcement stays within Discord's
2000 character limit, with allowedMentions limited to each chunk.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Updated native dependencies note to mention both better-sqlite3 and @napi-rs/canvas
- Clarified onboarding audit titles: "New member joined" and "Member completed onboarding"
- Rewrote v2 → v3 command changes list with accurate details for each command
- Fixed example code imports to include .ts extension (Node runs TS with ESM)
- Fixed updateConfig signature in example code
- Added notes about welcome preview/get truncation, rate-limit handling for /noroles and /rolesince, welcome image download cap, and birthday announcement splitting

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@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.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@BrentOates
BrentOates merged commit 8cceb98 into main Oct 6, 2026
1 check passed
@BrentOates
BrentOates deleted the feature/v3 branch October 6, 2026 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants