feat: /profile command with engagement tracking#111
Conversation
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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. 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughAdds engagement tracking: new DB migration for Changes
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Claude finished @BillChirico's task in 2m 15s —— View job Code Review: PR #111 —
|
There was a problem hiding this comment.
Review Summary
3 issues found (2 warnings, 2 nitpicks) — requesting changes for the warnings.
🟡 Warnings
-
Dead config flags
trackMessages/trackReactions(config.json:187-188)
Config definesengagement.trackMessagesandengagement.trackReactionsbutsrc/modules/engagement.jsnever reads them — it only checksengagement.enabled. Either wire them up or remove them to avoid misleading operators. -
Missing documentation updates (
AGENTS.md,README.md)
Per the project's own docs policy (AGENTS.md lines 170-189), new commands/modules require updates to:- AGENTS.md Key Files table — add
src/commands/profile.js,src/modules/engagement.js,migrations/008_user_stats.cjs - AGENTS.md Database Tables — add
user_statsrow - README.md Features list — add
/profileand engagement tracking
- AGENTS.md Key Files table — add
🔵 Nitpicks
-
Sequential DB queries in
trackReaction()(src/modules/engagement.js:72-84)
Thereactions_givenandreactions_receivedupserts are independent and could be parallelized withPromise.all(). -
reactions_receivedupsert skipsdays_active/last_active(src/modules/engagement.js:89-95)
Asymmetry:reactions_givenupdates activity timestamps butreactions_receiveddoes not. If intentional, add a comment explaining why.
📋 Fix-all prompt (copy → paste into AI agent)
Fix the following issues on branch feat/profile-command in VolvoxLLC/volvox-bot:
1. config.json lines 187-188: Remove the unused `trackMessages` and `trackReactions` keys
from the `engagement` config block (keep only `enabled`). Alternatively, wire them up in
src/modules/engagement.js — check `config.engagement.trackMessages` before calling the
message upsert and `config.engagement.trackReactions` before the reaction upsert.
2. AGENTS.md — Add to the Key Files table:
| `src/commands/profile.js` | `/profile` command — shows user engagement stats (messages, reactions, days active, activity badge) |
| `src/modules/engagement.js` | Engagement tracking — fire-and-forget upserts for message/reaction activity |
| `migrations/008_user_stats.cjs` | Migration — creates `user_stats` table (guild_id, user_id, messages_sent, reactions_given, reactions_received, days_active, first_seen, last_active) |
Add to the Database Tables section:
| `user_stats` | Per-guild user engagement stats — messages sent, reactions given/received, days active, first/last seen timestamps |
3. README.md — Add a bullet to the Features list:
- **📊 Engagement Profiles** — `/profile` command showing messages sent, reactions given/received, days active, and activity badges.
4. src/modules/engagement.js lines 72-95 (trackReaction function): Parallelize the two
independent DB queries using Promise.all() instead of sequential awaits.
5. src/modules/engagement.js line 89-95: Add a code comment above the reactions_received
query explaining that days_active/last_active are intentionally not updated here because
receiving reactions is passive activity.
|
| Filename | Overview |
|---|---|
| migrations/008_user_stats.cjs | Creates user_stats table with proper indexes. Minor issue: last_active DEFAULT sets timestamp for passive engagement. |
| src/modules/engagement.js | Solid tracking logic with proper bot filtering, config checks, and days_active calculation. Correctly handles passive vs active engagement. |
| src/commands/profile.js | Well-implemented command with proper error handling, config validation, and flexible badge tiers. Handles missing data gracefully. |
| src/modules/events.js | Fire-and-forget integration for both message and reaction tracking. Properly handles partial messages and uses non-blocking pattern. |
Sequence Diagram
sequenceDiagram
participant User
participant Discord
participant Events
participant Engagement
participant DB
participant Profile
User->>Discord: Send message
Discord->>Events: messageCreate event
Events->>Engagement: trackMessage() [fire-and-forget]
Engagement->>DB: UPSERT user_stats<br/>(messages_sent++, days_active logic)
User->>Discord: Add reaction
Discord->>Events: MessageReactionAdd event
Events->>Engagement: trackReaction() [fire-and-forget]
Engagement->>DB: UPSERT reactor stats<br/>(reactions_given++)
Engagement->>DB: UPSERT author stats<br/>(reactions_received++)
User->>Discord: /profile command
Discord->>Profile: execute()
Profile->>DB: SELECT user_stats
DB-->>Profile: stats data
Profile->>Profile: Calculate badge tier
Profile-->>Discord: Embed with stats
Discord-->>User: Display profile
Last reviewed commit: 6862b82
There was a problem hiding this comment.
2 warnings, 1 nitpick found. See inline comments for details.
🟡 Warnings:
- Inconsistent
last_activeinreactions_receivedupsert (src/modules/engagement.js:91-97) —last_activeis in the INSERT but not the ON CONFLICT UPDATE, freezing it at first-reaction time. Remove it from INSERT or add to UPDATE. - Missing documentation updates — Per AGENTS.md §Documentation policy, new commands/modules/tables require updates to AGENTS.md (Key Files, Database Tables) and README.md (Features list).
🔵 Nitpick:
3. Sequential DB queries in trackReaction() (src/modules/engagement.js:74-97) — The two independent upserts could be parallelized with Promise.all().
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/modules/engagement.js`:
- Around line 43-49: The catch block in src/modules/engagement.js logs tracking
errors but swallows them; update the catch in the function that wraps message
tracking (e.g., the function handling message engagement /
trackMessageEngagement) to re-throw the error after logging, wrapping it in the
appropriate custom error class from src/utils/errors.js (use the specific class
provided there, e.g., TrackingError or similar) so upstream callers can observe
failures; apply the same change to the other catch at lines ~99-105 (the other
tracking/error-handling block) — log with logError as currently done, then throw
new CustomError(originalError, { context }) or re-throw the wrapped error.
- Around line 20-25: Summary: Engagement counters are including bot/webhook
activity; exclude automated accounts so days_active and badge tiers reflect
humans only. Fix: in the message handler (around the initial guard where
getConfig is called) add a guard to return if message.author?.bot or
message.webhookId is set before calling trackMessage; likewise in the reaction
handler (the function handling reactions where trackReaction is called,
referenced around lines 62-68) return/ignore if the reacting user?.bot or the
reaction is from a webhook; also ensure trackMessage and trackReaction
themselves defensively check and return early when given a bot/webhook user. Use
the symbols message.author?.bot, message.webhookId, and the reaction handler’s
user?.bot to locate and implement these checks.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (9)
config.jsonmigrations/008_user_stats.cjssrc/api/utils/configAllowlist.jssrc/commands/profile.jssrc/modules/engagement.jssrc/modules/events.jstests/commands/profile.test.jstests/modules/engagement.test.jsweb/src/components/dashboard/config-editor.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Greptile Review
- GitHub Check: claude-review
🧰 Additional context used
📓 Path-based instructions (7)
**/*.js
📄 CodeRabbit inference engine (AGENTS.md)
**/*.js: Use ESM modules only — import/export, never require()
Always use node: protocol for Node.js builtin imports (e.g., import { readFileSync } from 'node:fs')
Files:
tests/commands/profile.test.jssrc/modules/engagement.jssrc/modules/events.jssrc/commands/profile.jssrc/api/utils/configAllowlist.jstests/modules/engagement.test.js
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,tsx}: Always use semicolons
Use single quotes — enforced by Biome
Use 2-space indentation — enforced by Biome
Files:
tests/commands/profile.test.jssrc/modules/engagement.jssrc/modules/events.jssrc/commands/profile.jsweb/src/components/dashboard/config-editor.tsxsrc/api/utils/configAllowlist.jstests/modules/engagement.test.js
src/**/*.js
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.js: NEVER use console.log, console.warn, console.error, or any console.* method in src/ files — always use Winston logger instead: import { info, warn, error } from '../logger.js'
Pass structured metadata to Winston logger: info('Message processed', { userId, channelId })
Use custom error classes from src/utils/errors.js and always log errors with context before re-throwing
Use getConfig(guildId?) from src/modules/config.js to read config; use setConfigValue(path, value, guildId?) to update at runtime
Use safeSend() utility for outgoing Discord messages to sanitize mentions and enforce allowedMentions
Use splitMessage() utility for messages exceeding Discord's 2000-character limit
Add tests for all new code with mandatory 80% coverage threshold on statements, branches, functions, and lines; run pnpm test before every commit
Files:
src/modules/engagement.jssrc/modules/events.jssrc/commands/profile.jssrc/api/utils/configAllowlist.js
src/modules/*.js
📄 CodeRabbit inference engine (AGENTS.md)
src/modules/*.js: Register event handlers in src/modules/events.js by importing handler functions and calling client.on() with config parameter
Check config.yourModule.enabled before processing in module event handlers
Prefer per-request getConfig() pattern in new modules over reactive onConfigChange() wiring; only add onConfigChange() listeners for stateful resources that cannot re-read config on each use
Files:
src/modules/engagement.jssrc/modules/events.js
src/commands/*.js
📄 CodeRabbit inference engine (AGENTS.md)
src/commands/*.js: Use parseDuration() from src/utils/duration.js for duration-based commands (timeout, tempban, slowmode); enforce Discord duration caps (timeouts max 28 days, slowmode max 6 hours)
Create slash commands by exporting data (SlashCommandBuilder) and execute() function from src/commands/*.js; export adminOnly = true for mod-only commands; commands are auto-discovered on startup
Files:
src/commands/profile.js
web/**/*.{tsx,ts}
📄 CodeRabbit inference engine (AGENTS.md)
Use next/image Image component with appropriate layout and sizing props in Next.js components
Files:
web/src/components/dashboard/config-editor.tsx
web/src/**/*.{tsx,ts}
📄 CodeRabbit inference engine (AGENTS.md)
Use Zustand store (zustand) for state management in React components; implement fetch-on-demand pattern in stores
Files:
web/src/components/dashboard/config-editor.tsx
🧠 Learnings (7)
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/**/*.js : Use safeSend() utility for outgoing Discord messages to sanitize mentions and enforce allowedMentions
Applied to files:
src/modules/events.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/**/*.js : Use splitMessage() utility for messages exceeding Discord's 2000-character limit
Applied to files:
src/modules/events.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/**/*.js : Use getConfig(guildId?) from src/modules/config.js to read config; use setConfigValue(path, value, guildId?) to update at runtime
Applied to files:
src/modules/events.jsweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/commands/*.js : Create slash commands by exporting data (SlashCommandBuilder) and execute() function from src/commands/*.js; export adminOnly = true for mod-only commands; commands are auto-discovered on startup
Applied to files:
src/commands/profile.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/commands/*.js : Use parseDuration() from src/utils/duration.js for duration-based commands (timeout, tempban, slowmode); enforce Discord duration caps (timeouts max 28 days, slowmode max 6 hours)
Applied to files:
src/commands/profile.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/commands/*mod*.js : Moderation commands must follow the shared pattern: deferReply({ ephemeral: true }), validate inputs, sendDmNotification(), execute Discord action, createCase(), sendModLogEmbed(), checkEscalation()
Applied to files:
src/commands/profile.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/**/*.js : Add tests for all new code with mandatory 80% coverage threshold on statements, branches, functions, and lines; run pnpm test before every commit
Applied to files:
tests/modules/engagement.test.js
🧬 Code graph analysis (5)
tests/commands/profile.test.js (4)
src/commands/profile.js (5)
target(55-55)getActivityBadge(20-25)execute(39-102)pool(54-54)pool(57-62)src/modules/config.js (1)
getConfig(282-313)src/utils/safeSend.js (1)
safeEditReply(178-185)src/db.js (1)
getPool(142-147)
src/modules/engagement.js (3)
src/commands/profile.js (3)
config(46-46)pool(54-54)pool(57-62)src/modules/config.js (2)
getConfig(282-313)err(94-94)src/db.js (1)
getPool(142-147)
src/modules/events.js (1)
src/modules/engagement.js (2)
trackMessage(19-50)trackReaction(61-106)
src/commands/profile.js (3)
src/utils/safeSend.js (1)
safeEditReply(178-185)src/modules/config.js (2)
getConfig(282-313)err(94-94)src/db.js (1)
getPool(142-147)
tests/modules/engagement.test.js (3)
src/modules/engagement.js (6)
guildId(62-62)authorId(89-89)pool(27-27)pool(70-70)trackMessage(19-50)trackReaction(61-106)src/modules/config.js (1)
getConfig(282-313)src/db.js (1)
getPool(142-147)
🔇 Additional comments (19)
src/api/utils/configAllowlist.js (1)
23-23: LGTM!The addition of
'engagement'toSAFE_CONFIG_KEYScorrectly enables API access for the new engagement configuration section, aligning with the config.json and dashboard UI changes in this PR.migrations/008_user_stats.cjs (1)
1-29: LGTM!The migration correctly creates the
user_statstable with:
- Composite primary key
(guild_id, user_id)for per-guild user tracking- Appropriate integer counters with defaults
- Timestamps with
TIMESTAMPTZfor timezone awareness- An index on
guild_idfor efficient guild-scoped queries- Proper rollback via
DROP TABLE ... CASCADEweb/src/components/dashboard/config-editor.tsx (2)
51-51: LGTM!The
engagementkey is correctly added toknownSections, enabling the type guard to recognize engagement as a valid guild configuration section.
1205-1205: LGTM!The engagement tracking toggle is properly integrated into the Community Features card, following the established pattern and providing a clear description of the feature.
config.json (2)
152-153: LGTM!The
profilecommand permission is correctly set to"everyone", allowing all users to view their own or others' engagement profiles.
185-189: LGTM!The engagement configuration block is well-structured:
enabled: falseprovides a safe opt-in defaulttrackMessagesandtrackReactionsallow granular control when the feature is enabledsrc/modules/events.js (3)
15-15: LGTM!Correctly imports the engagement tracking functions from the new module.
156-158: LGTM!Fire-and-forget pattern is correctly implemented. The empty
.catch()is acceptable sincetrackMessageinternally logs errors before they propagate.
276-279: LGTM!Fire-and-forget pattern is correctly implemented for reaction tracking. The placement after
guildConfigretrieval ensures the config is available, and the tracking runs independently of the starboard feature gate below.tests/commands/profile.test.js (3)
1-93: LGTM!Well-structured test setup with proper mocks for all dependencies and clean helper factories for creating test fixtures.
94-114: LGTM!Excellent boundary testing for
getActivityBadgecovering all four tiers with both lower and upper boundary values (0, 6, 7, 29, 30, 89, 90, 200).
116-209: LGTM!Comprehensive test coverage for the
/profile executefunction including:
- Feature disabled/enabled states
- Guild-only enforcement
- Zero-stats and populated-stats scenarios
- Target user lookup
- Database error handling
- Self-fallback behavior
tests/modules/engagement.test.js (3)
1-53: LGTM!Well-organized test setup with proper mocks and helper factories. The mock config correctly includes all three engagement flags (
enabled,trackMessages,trackReactions).
55-99: LGTM!Comprehensive
trackMessagetests covering:
- Successful upsert with correct parameters
- Feature disabled gate
trackMessagesflag gate- No-guild guard
- Error handling (logs but doesn't throw)
101-153: LGTM!Comprehensive
trackReactiontests covering:
- Both
reactions_givenandreactions_receivedincrements- Self-reaction skip logic
- Feature disabled gate
trackReactionsflag gate- No-guild guard
- Error handling (logs but doesn't throw)
src/commands/profile.js (3)
1-12: LGTM!Imports are correctly structured using ESM, the Winston logger is used properly (no console.*), and all required utilities are imported.
14-32: LGTM!The
getActivityBadgefunction correctly implements the tier thresholds from the requirements (Newcomer <7, Regular 7-29, Veteran 30-89, Legend 90+). The slash command data is properly configured with an optional user option.
39-102: LGTM!The execute function is well-implemented:
- Properly defers reply for the potentially slow DB query
- Validates guild context before proceeding
- Checks engagement feature flag
- Handles missing user stats gracefully with defaults
- Uses
safeEditReplyfor all responses as per guidelines- Properly logs errors with context before returning user-friendly message
src/modules/engagement.js (1)
22-25: Good config gating before DB work.Early returns on
engagement.enabledand per-signal flags keep this module lightweight and aligned with the intended runtime behavior.Also applies to: 65-67
… re-throw on db error - Add bot guard in trackMessage (message.author?.bot) and trackReaction (user.bot) to prevent automated accounts from skewing engagement stats - Remove last_active from reactions_received INSERT/ON CONFLICT — receiving a reaction is passive and should not update the actor's last_active timestamp - Re-throw DB errors after logging so upstream callers can observe failures; update tests to assert rejects instead of resolves
There was a problem hiding this comment.
3 issues found (1 warning, 2 nitpicks). The previous review findings (dead config flags, bot exclusion, last_active inconsistency, error swallowing) have all been addressed — nice work on the fixup commits.
Remaining issues:
🟡 Missing documentation updates — Per AGENTS.md §Documentation policy (lines 170-189), new commands/modules/tables require updates to:
- AGENTS.md Key Files table: add
src/commands/profile.js,src/modules/engagement.js,migrations/008_user_stats.cjs - AGENTS.md Database Tables: add
user_statsrow - README.md Features list: add engagement tracking /
/profile
🟡 Missing test coverage for bot guard (tests/modules/engagement.test.js) — The bot-filtering guards added in d05a614 (message.author?.bot, user.bot) have no test cases. See inline comment for suggested tests.
🔵 Sequential DB queries in trackReaction() (src/modules/engagement.js:73-101) — The two independent upserts could be parallelized with Promise.all(). See inline comment.
…, bot tests - Skip reactions_received tracking when message author is a bot - Add last_active to reactions_received upsert (INSERT + ON CONFLICT) - Add test coverage for bot guard in trackMessage and trackReaction
There was a problem hiding this comment.
2 warnings, 1 nitpick
🟡 Warnings:
-
last_activeinflated by passivereactions_received(src/modules/engagement.js:95-102) — When user B reacts to user A's message, A'slast_activeis set toNOW()even if A is offline. This makes the "Last Active" field on/profilemisleading. Removelast_activefrom both INSERT and UPDATE in thereactions_receivedupsert. -
Missing documentation updates — Per AGENTS.md §Documentation policy (lines 170-189), new commands/modules/tables require updates to:
- AGENTS.md Key Files table: add
src/commands/profile.js,src/modules/engagement.js,migrations/008_user_stats.cjs - AGENTS.md Database Tables: add
user_statsrow - README.md Features list: add engagement tracking /
/profile
- AGENTS.md Key Files table: add
🔵 Nitpick:
3. Sequential DB queries in trackReaction() (src/modules/engagement.js:77-103) — The two independent upserts could be parallelized with Promise.all() to halve round-trip time.
📋 Fix-all prompt (copy → paste into AI agent)
Fix the following issues on branch feat/profile-command in VolvoxLLC/volvox-bot:
1. src/modules/engagement.js lines 95-102: Remove `last_active` from the
`reactions_received` INSERT and ON CONFLICT UPDATE. Passive engagement
(receiving reactions) should NOT update `last_active` — only the user's
own actions should. Change the query to:
INSERT INTO user_stats (guild_id, user_id, reactions_received, first_seen)
VALUES ($1, $2, 1, NOW())
ON CONFLICT (guild_id, user_id) DO UPDATE
SET reactions_received = user_stats.reactions_received + 1
2. src/modules/engagement.js lines 77-103: Parallelize the two independent
DB queries (reactions_given and reactions_received) using Promise.all()
instead of sequential awaits.
3. AGENTS.md — Add to the Key Files table:
| `src/commands/profile.js` | `/profile` command — shows user engagement stats |
| `src/modules/engagement.js` | Engagement tracking — fire-and-forget upserts for message/reaction activity |
| `migrations/008_user_stats.cjs` | Migration — creates `user_stats` table |
Add to the Database Tables section:
| `user_stats` | Per-guild user engagement stats — messages sent, reactions given/received, days active, first/last seen |
4. README.md — Add a bullet to the Features list:
- **📊 Engagement Profiles** — `/profile` command showing messages sent, reactions, days active, and activity badges.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/modules/engagement.js (1)
44-50:⚠️ Potential issue | 🟠 MajorWrap and re-throw with the project’s custom error classes.
On Line 50 and Line 110, the code re-throws raw
err. Insrc/**/*.js, these should be wrapped with the appropriate class fromsrc/utils/errors.jsafter logging context so upstream handling stays consistent.As per coding guidelines, "Use custom error classes from src/utils/errors.js and always log errors with context before re-throwing".
Also applies to: 104-110
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/engagement.js` around lines 44 - 50, Replace raw re-throws in the catch blocks that currently call logError(...) and then throw err with wrapping the original error in the project’s custom error class (e.g., ApplicationError) from errors.js: after logging via logError('Failed to track message engagement', { ... }), create and throw new ApplicationError('Failed to track message engagement', { cause: err, context: { userId: message.author.id, guildId: message.guild.id } }); do the same for the other catch (lines around 104–110) so upstream code receives the ApplicationError while preserving the original error as the cause.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/modules/engagement.js`:
- Around line 36-40: The days_active increment logic uses last_active::date
which causes a reaction_received update earlier in the same day to block a
subsequent real activity from incrementing days_active; update the CASE
condition to compare day-truncations of timestamps (e.g. date_trunc('day',
$3::timestamptz) > date_trunc('day', user_stats.last_active)) instead of
last_active::date, and when updating last_active in the reactions_received/other
update paths ensure you either set last_active =
GREATEST(user_stats.last_active, $new_ts) or only assign last_active when
$new_ts > user_stats.last_active so later-in-day active events still trigger the
days_active increment (apply same change to the blocks referencing days_active
and last_active).
---
Duplicate comments:
In `@src/modules/engagement.js`:
- Around line 44-50: Replace raw re-throws in the catch blocks that currently
call logError(...) and then throw err with wrapping the original error in the
project’s custom error class (e.g., ApplicationError) from errors.js: after
logging via logError('Failed to track message engagement', { ... }), create and
throw new ApplicationError('Failed to track message engagement', { cause: err,
context: { userId: message.author.id, guildId: message.guild.id } }); do the
same for the other catch (lines around 104–110) so upstream code receives the
ApplicationError while preserving the original error as the cause.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
src/modules/engagement.jstests/modules/engagement.test.js
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Greptile Review
- GitHub Check: claude-review
🧰 Additional context used
📓 Path-based instructions (4)
**/*.js
📄 CodeRabbit inference engine (AGENTS.md)
**/*.js: Use ESM modules only — import/export, never require()
Always use node: protocol for Node.js builtin imports (e.g., import { readFileSync } from 'node:fs')
Files:
src/modules/engagement.jstests/modules/engagement.test.js
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,tsx}: Always use semicolons
Use single quotes — enforced by Biome
Use 2-space indentation — enforced by Biome
Files:
src/modules/engagement.jstests/modules/engagement.test.js
src/**/*.js
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.js: NEVER use console.log, console.warn, console.error, or any console.* method in src/ files — always use Winston logger instead: import { info, warn, error } from '../logger.js'
Pass structured metadata to Winston logger: info('Message processed', { userId, channelId })
Use custom error classes from src/utils/errors.js and always log errors with context before re-throwing
Use getConfig(guildId?) from src/modules/config.js to read config; use setConfigValue(path, value, guildId?) to update at runtime
Use safeSend() utility for outgoing Discord messages to sanitize mentions and enforce allowedMentions
Use splitMessage() utility for messages exceeding Discord's 2000-character limit
Add tests for all new code with mandatory 80% coverage threshold on statements, branches, functions, and lines; run pnpm test before every commit
Files:
src/modules/engagement.js
src/modules/*.js
📄 CodeRabbit inference engine (AGENTS.md)
src/modules/*.js: Register event handlers in src/modules/events.js by importing handler functions and calling client.on() with config parameter
Check config.yourModule.enabled before processing in module event handlers
Prefer per-request getConfig() pattern in new modules over reactive onConfigChange() wiring; only add onConfigChange() listeners for stateful resources that cannot re-read config on each use
Files:
src/modules/engagement.js
🧠 Learnings (3)
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/**/*.js : Use custom error classes from src/utils/errors.js and always log errors with context before re-throwing
Applied to files:
src/modules/engagement.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/modules/moderation.js : Case numbering must be per-guild sequential, assigned atomically using COALESCE(MAX(case_number), 0) + 1 in a single INSERT statement
Applied to files:
src/modules/engagement.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/**/*.js : Add tests for all new code with mandatory 80% coverage threshold on statements, branches, functions, and lines; run pnpm test before every commit
Applied to files:
tests/modules/engagement.test.js
🧬 Code graph analysis (2)
src/modules/engagement.js (3)
src/commands/profile.js (3)
config(46-46)pool(54-54)pool(57-62)src/modules/config.js (2)
getConfig(282-313)err(94-94)src/db.js (1)
getPool(142-147)
tests/modules/engagement.test.js (1)
src/modules/engagement.js (6)
guildId(64-64)authorId(93-93)pool(28-28)pool(73-73)trackMessage(19-52)trackReaction(63-112)
Updating last_active when a user passively receives a reaction inflates their activity timestamp — even if they haven't been online in weeks. It also suppresses days_active increments: if a reaction sets last_active to today, a subsequent message today can't increment days_active. Remove last_active from the reactions_received ON CONFLICT UPDATE clause. The INSERT path retains last_active=NOW() for first-time record creation, which is harmless (no prior record to corrupt). Resolves PR review threads: - last_active inflation from passive receives - days_active suppression via premature last_active update - inconsistent last_active in upsert UPDATE clause
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
1 warning, 2 nitpicks
The code itself is solid after 8 fixup commits — bot guards, config flag gating, days_active off-by-one fix, Promise.all parallelization, and last_active passive-inflation fix are all correctly implemented. Tests cover all branches including bot guards.
🟡 Missing documentation updates (AGENTS.md, README.md)
Per AGENTS.md §Documentation policy (lines 170-189): "Added a new command → update Key Files table, add to README command list" and "Added a new module → update Key Files table, document config section."
Missing updates:
- AGENTS.md Key Files table: add
src/commands/profile.js,src/modules/engagement.js,migrations/008_user_stats.cjs - AGENTS.md Database Tables: add
user_statsrow - README.md Features list (line 19): add engagement tracking /
/profile - README.md Config reference (after line 227): add
engagementconfig section
🔵 err.message may be undefined for non-Error rejections (src/modules/engagement.js:48, 110)
See inline comments.
📋 Fix-all prompt (copy → paste into AI agent)
Fix the following issues on branch feat/profile-command in VolvoxLLC/volvox-bot:
1. AGENTS.md — Add to the Key Files table (after the config.json row, around line 68):
| `src/commands/profile.js` | `/profile` command — shows user engagement stats (messages, reactions, days active, activity badge) |
| `src/modules/engagement.js` | Engagement tracking — fire-and-forget upserts for message/reaction activity |
| `migrations/008_user_stats.cjs` | Migration — creates `user_stats` table (guild_id + user_id PK, counters, timestamps) |
Add to the Database Tables section (after the afk_pings row, around line 143):
| `user_stats` | Per-guild user engagement stats — messages sent, reactions given/received, days active, first/last seen timestamps |
2. README.md line 19 — Add a bullet to the Features list after the Voice Activity entry:
- **📊 Engagement Profiles** — `/profile` command showing messages sent, reactions given/received, days active, and activity badges.
3. README.md after line 227 (after the Reputation section) — Add engagement config reference:
### Engagement (`engagement`)
| Key | Type | Description |
|-----|------|-------------|
| `enabled` | boolean | Enable engagement tracking and `/profile` command |
| `trackMessages` | boolean | Track messages sent per user (default: `true`) |
| `trackReactions` | boolean | Track reactions given/received per user (default: `true`) |
**Commands:** `/profile [user]` — show engagement stats with activity badge (🌱 Newcomer / 🌿 Regular / 🌳 Veteran / 👑 Legend).
4. src/modules/engagement.js line 48: Change `error: err.message` to
`error: err instanceof Error ? err.message : String(err)`
5. src/modules/engagement.js line 110: Same change as #4.
There was a problem hiding this comment.
1 warning, 4 nitpicks
Previous review findings (dead config flags, bot exclusion, error re-throw, bot guard on reactions_received, days_active off-by-one, last_active passive inflation, Promise.all parallelization) have all been addressed through 8 fixup commits — nice work on the iteration.
🟡 Warning:
- Missing documentation updates — Per AGENTS.md §Documentation policy (lines 170-189), new commands/modules/tables require:
- AGENTS.md Key Files table: add
src/commands/profile.js,src/modules/engagement.js,migrations/008_user_stats.cjs - AGENTS.md Database Tables: add
user_statsrow - README.md Features list (line 19): add engagement tracking /
/profile
- AGENTS.md Key Files table: add
🔵 Nitpicks:
2. Redundant database index (migrations/008_user_stats.cjs:23) — idx_user_stats_guild is covered by the composite PK's leftmost prefix
3. err.message undefined for non-Error rejections (src/modules/engagement.js:48, 110)
4. Duplicate default badge arrays (web/src/components/dashboard/config-editor.tsx:1233-1267) — Same 4-element fallback array repeated 4 times
📋 Fix-all prompt (copy → paste into AI agent)
Fix the following issues on branch feat/profile-command in VolvoxLLC/volvox-bot:
1. AGENTS.md — Add to the Key Files table (after the config.json row, around line 68):
| `src/commands/profile.js` | `/profile` command — shows user engagement stats (messages, reactions, days active, activity badge) |
| `src/modules/engagement.js` | Engagement tracking — fire-and-forget upserts for message/reaction activity |
| `migrations/008_user_stats.cjs` | Migration — creates `user_stats` table (guild_id + user_id PK, counters, timestamps) |
Add to the Database Tables section (after the afk_pings row, around line 143):
| `user_stats` | Per-guild user engagement stats — messages sent, reactions given/received, days active, first/last seen timestamps |
2. README.md line 19 — Add a bullet to the Features list after the Voice Activity entry:
- **📊 Engagement Profiles** — `/profile` command showing messages sent, reactions given/received, days active, and activity badges.
3. migrations/008_user_stats.cjs line 23 — Remove the redundant index:
Delete the line: pgm.sql('CREATE INDEX IF NOT EXISTS idx_user_stats_guild ON user_stats(guild_id)');
The composite PK (guild_id, user_id) already covers guild_id lookups.
4. src/modules/engagement.js lines 48 and 110 — Change `error: err.message` to:
error: err instanceof Error ? err.message : String(err)
5. web/src/components/dashboard/config-editor.tsx — Extract the default badge array
(repeated at lines ~1233, ~1246, ~1262) into a constant:
const DEFAULT_ACTIVITY_BADGES = [
{ days: 90, label: "👑 Legend" },
{ days: 30, label: "🌳 Veteran" },
{ days: 7, label: "🌿 Regular" },
{ days: 0, label: "🌱 Newcomer" },
];
Then replace all 4 occurrences of the inline fallback array with DEFAULT_ACTIVITY_BADGES.
Additional Comments (1)
|
Summary
Implements the
/profileslash command with engagement tracking (#44).Changes
migrations/008_user_stats.cjs— Newuser_statstable with guild/user PK, tracking messages sent, reactions given/received, days active, first seen, and last active timestamps.src/modules/engagement.js—trackMessage()andtrackReaction()exports. Fire-and-forget, upsert viaON CONFLICT DO UPDATE,days_activeincrements on new calendar day. Gated onconfig.engagement.enabled.src/commands/profile.js—/profile [user]command. Rich embed with all stats, activity badge (🌱 Newcomer / 🌿 Regular / 🌳 Veteran / 👑 Legend),#5865F2color. Defers then edits reply.src/modules/events.js— WiredtrackMessage()intomessageCreateandtrackReaction()intoMessageReactionAdd(both fire-and-forget).config.json— Addedengagementconfig block (enabled: falseby default), addedprofile: "everyone"permission.src/api/utils/configAllowlist.js— Added'engagement'toSAFE_CONFIG_KEYS.web/src/components/dashboard/config-editor.tsx— Added Engagement Tracking toggle to Community Features card.tests/commands/profile.test.js+tests/modules/engagement.test.js— Full coverage including all badge tiers, disabled config, no guild, no stats, db errors, self vs. other user.Test Results
Closes #44