feat: /snippet code sharing command (#41)#103
Conversation
Add /snippet slash command for saving and sharing code snippets in Discord. Features: - /snippet save - Save a named code snippet with language + optional description - /snippet get - Retrieve a snippet as a syntax-highlighted code block, increments usage_count - /snippet search - ILIKE search across name, description, and code fields - /snippet list - Paginated embed, sortable by recent (default) or popular - /snippet delete - Author or moderator only - Autocomplete on snippet names (get/delete) and languages (save) Migration 004_snippets.cjs creates the snippets table with UNIQUE(guild_id, name) and indexes for guild and name lookups. 22 tests covering all subcommands, permission checks, and autocomplete. Closes #41
|
Claude finished @BillChirico's task in 3m 51s —— View job Code Review Complete
Review Summary — 5 issues found🔴 Critical (1)
🟡 Warning (2) 🔵 Nitpick (2) CI Status❌ CI failing: Branch coverage 79.68% < 80% threshold. Removing the unreachable length checks (issue #2) should fix this. 📋 AI Fix Prompt (copy-paste to fix all issues) |
|
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 (3)
📝 WalkthroughWalkthroughAdds a code snippet sharing feature with a new database migration creating a snippets table, a comprehensive /snippet slash command with subcommands for save, get, search, list, and delete operations, plus full test coverage for command functionality. Changes
🚥 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 |
There was a problem hiding this comment.
Review Summary — 6 issues found
🔴 Critical (1)
- MAX_CODE_LENGTH (4000) exceeds Discord's 2000-char limit —
handleGetbuilds plain-text replies thatsafeEditReplytruncates at 2000 chars. Users can save 4000-char snippets but only see ~1960 chars on retrieval. The rest is silently lost.
🟡 Warning (3)
- All subcommands are ephemeral —
deferReply({ ephemeral: true })on line 337 makesgetresponses only visible to the invoker, defeating the "sharing" purpose.getshould be public. - ILIKE wildcards not escaped — Search query
%and_chars are interpreted as ILIKE wildcards, causing unexpected results (e.g., searching%matches everything). - Missing
snippetin config.jsonallowedCommands— Per project conventions (AGENTS.md: "Add permission in config.json under permissions.allowedCommands"), the command is missing from config.json. Without it,hasPermission()defaults to admin-only.
🔵 Nitpick (1)
- Redundant index —
idx_snippets_nameon(guild_id, name)duplicates the implicit unique index from theUNIQUE(guild_id, name)constraint.
📝 Documentation
- AGENTS.md Key Files table not updated — Per AGENTS.md rules: "Added a new command → update Key Files table." Missing entries for
src/commands/snippet.jsandmigrations/004_snippets.cjs.
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@migrations/004_snippets.cjs`:
- Around line 26-27: The explicit index creation pgm.sql('CREATE INDEX IF NOT
EXISTS idx_snippets_name ON snippets(guild_id, name)') is redundant because the
UNIQUE(guild_id, name) constraint already creates an implicit unique index;
remove that pgm.sql line (or replace it with a DROP INDEX IF EXISTS
idx_snippets_name in a rollback step if you need to clean up an existing
redundant index) and keep the non-unique index pgm.sql('CREATE INDEX IF NOT
EXISTS idx_snippets_guild ON snippets(guild_id)') as-is.
In `@src/commands/snippet.js`:
- Line 337: The current call to interaction.deferReply({ ephemeral: true })
makes every subcommand reply ephemeral; change this to respect the subcommand or
a new public option: read the invoked subcommand via
interaction.options.getSubcommand() (or a new boolean option named "public"),
compute ephemeral = subcommand !== 'get' (or ephemeral = !public), and pass that
value into interaction.deferReply; ensure subsequent followUp/editReply calls
still work for public responses. Use the existing interaction.deferReply and
interaction.options.getSubcommand symbols and add the "public" option if you
prefer an explicit toggle.
- Around line 8-13: Imports in src/commands/snippet.js are not alphabetized;
reorder the import statements so they are sorted alphabetically (by module path
or import name per your project's import-sort rules) — locate the top-level
imports (EmbedBuilder, SlashCommandBuilder from 'discord.js'; getPool from
'../db.js'; error as logError and info from '../logger.js'; getConfig from
'../modules/config.js'; isModerator from '../utils/permissions.js';
safeEditReply from '../utils/safeSend.js') and reorder them accordingly, then
run the project's formatter/linter to apply the exact expected ordering.
- Around line 219-227: The SQL uses ILIKE with raw user `query` so `%`, `_`, and
`\` in the input act as wildcards or escape chars; escape these characters
before querying (replace `\` with `\\`, `%` with `\%`, and `_` with `\_`) and
pass the escaped string wrapped in `%...%` as the parameter, then add an
explicit ESCAPE clause (e.g., ILIKE $2 ESCAPE '\') in the pool.query that
selects from `snippets` (the query using SELECT name, language, description,
code ... ORDER BY usage_count) so wildcard chars are treated literally; apply
the same escaping and ESCAPE clause in the autocomplete function referenced
around line 391.
- Around line 148-160: Remove the redundant runtime length checks for
MAX_NAME_LENGTH and MAX_CODE_LENGTH in src/commands/snippet.js: delete the
if-blocks that test name.length and code.length and their corresponding return
await safeEditReply(interaction, ...) paths, since the SlashCommandBuilder
options already call
setMaxLength(MAX_NAME_LENGTH)/setMaxLength(MAX_CODE_LENGTH). Locate these checks
inside the execute handler (the blocks referencing MAX_NAME_LENGTH,
MAX_CODE_LENGTH, name, code, safeEditReply, and interaction) and remove them; if
you need a safeguard for non-discord inputs, replace with a single defensive
comment or a separate validation helper used only for non-slash input paths.
In `@tests/commands/snippet.test.js`:
- Around line 99-108: The test file formatting is out of sync; run your
project's formatter (e.g., pnpm format) to reformat
tests/commands/snippet.test.js (the block around the execute(...) test that
asserts mockPool.query and interaction.editReply) and commit the updated file so
CI passes; ensure you stage the reformatted file and verify the assertions
(expect.stringContaining('INSERT INTO snippets') and
expect.stringContaining('✅')) remain logically unchanged after formatting.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (3)
migrations/004_snippets.cjssrc/commands/snippet.jstests/commands/snippet.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). (3)
- GitHub Check: Greptile Review
- GitHub Check: Agent
- GitHub Check: claude-review
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use ESM modules withimport/exportsyntax; never userequire()
Always usenode:protocol for Node.js builtins (e.g.,import { readFileSync } from 'node:fs')
Always use semicolons in code
Use single quotes for strings (enforced by Biome)
Use 2-space indentation (enforced by Biome)
Files:
tests/commands/snippet.test.jssrc/commands/snippet.js
tests/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
tests/**/*.{js,ts,jsx,tsx}: All new code must include tests; test coverage must maintain 80% threshold on statements, branches, functions, and lines
Use Vitest for testing; runpnpm testbefore every commit andpnpm test:coverageto verify 80% coverage threshold
Files:
tests/commands/snippet.test.js
src/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{js,ts,jsx,tsx}: Always use Winston for logging viaimport { info, warn, error } from '../logger.js'; never useconsole.log,console.warn,console.error, or anyconsole.*method in src/ files
Pass structured metadata to Winston logging calls (e.g.,info('Message processed', { userId, channelId }))
Use custom error classes fromsrc/utils/errors.jsfor error handling
Always log errors with context before re-throwing
UsegetConfig(guildId?)fromsrc/modules/config.jsto read configuration values
UsesetConfigValue(path, value, guildId?)fromsrc/modules/config.jsto update configuration at runtime
UsesafeSend()utility for all outgoing Discord messages to enforce allowedMentions and prevent mention spam
UsesanitizeMentions()to strip@everyone/@here from outgoing text via zero-width space insertion before sending
UsesplitMessage()utility to handle Discord's 2000-character message limit
onConfigChangecallbacks receive(newValue, oldValue, fullPath, guildId)as parameters
Files:
src/commands/snippet.js
src/commands/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/commands/**/*.{js,ts,jsx,tsx}: Slash commands must export adataproperty with a SlashCommandBuilder and an asyncexecute(interaction)function
ExportadminOnly = truefor moderator-only slash commands
Duration-based commands (timeout, tempban, slowmode) must useparseDuration()fromsrc/utils/duration.jsfor parsing duration arguments
Always callcheckHierarchy(moderator, target)before executing moderation actions to prevent moderating users with equal or higher roles
Files:
src/commands/snippet.js
🧠 Learnings (3)
📚 Learning: 2026-02-26T22:59:10.394Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-26T22:59:10.394Z
Learning: Applies to tests/**/*.{js,ts,jsx,tsx} : Use Vitest for testing; run `pnpm test` before every commit and `pnpm test:coverage` to verify 80% coverage threshold
Applied to files:
tests/commands/snippet.test.js
📚 Learning: 2026-02-26T22:59:10.394Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-26T22:59:10.394Z
Learning: Applies to src/commands/**/*.{js,ts,jsx,tsx} : Slash commands must export a `data` property with a SlashCommandBuilder and an async `execute(interaction)` function
Applied to files:
src/commands/snippet.js
📚 Learning: 2026-02-26T22:59:10.394Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-26T22:59:10.394Z
Learning: Applies to src/commands/**/*{ban,kick,warn,timeout,mute}*.{js,ts,jsx,tsx} : Moderation commands must follow the pattern: deferReply() → validate → sendDmNotification() → execute Discord action → createCase() → sendModLogEmbed() → checkEscalation()
Applied to files:
src/commands/snippet.js
🧬 Code graph analysis (1)
src/commands/snippet.js (5)
src/db.js (1)
getPool(142-147)src/utils/safeSend.js (1)
safeEditReply(178-185)src/modules/config.js (2)
err(94-94)getConfig(282-313)src/logger.js (1)
info(230-232)src/utils/permissions.js (1)
isModerator(143-173)
🪛 GitHub Actions: CI
tests/commands/snippet.test.js
[error] 99-108: Formatter would adjust test expectations in this test file. Apply formatting to align with updated assertions.
src/commands/snippet.js
[error] 8-11: assist/source/organizeImports FIXABLE: The imports are not sorted. Safe fix: organize imports.
🔇 Additional comments (5)
migrations/004_snippets.cjs (1)
1-33: Migration structure looks good overall.The table schema correctly implements the requirements from issue
#41with appropriate fields for snippets, usage tracking, and timestamps. The unique constraint ensures snippet names are unique per guild.tests/commands/snippet.test.js (1)
1-498: Comprehensive test coverage for the snippet command.The test suite thoroughly covers:
- Command data structure validation
- All subcommands (save, get, search, list, delete)
- Permission checks (author vs moderator vs unauthorized)
- Autocomplete for both snippet names and languages
- Error handling for database failures
Good use of mock isolation and the
createInteractionhelper for reducing boilerplate.src/commands/snippet.js (3)
49-137: Command definition is well-structured.The
SlashCommandBuildersetup is comprehensive with proper subcommands, options, autocomplete flags, and max length constraints. All required fields are marked appropriately.
293-328: Delete permission logic is correctly implemented.The check properly allows deletion by either the snippet author or a moderator, with appropriate error messaging for unauthorized users.
371-406: Autocomplete implementation handles errors gracefully.Good pattern: returning an empty array on error prevents the autocomplete from breaking. The language filtering and snippet name lookup both work correctly.
There was a problem hiding this comment.
Pull request overview
This PR implements the /snippet slash command for saving and sharing code snippets in Discord with syntax highlighting support. The implementation adds a new database table, a fully-featured command with five subcommands (save, get, search, list, delete), autocomplete functionality, and comprehensive test coverage. This feature allows server members to create a community-owned knowledge base of reusable code snippets, similar to GitHub Gist.
Changes:
- Added database migration (004_snippets.cjs) creating the
snippetstable with guild-scoped unique snippet names, usage tracking, and optimized indexes - Implemented
/snippetcommand with CRUD operations, search functionality, pagination, and permission controls - Added 22 comprehensive tests covering all subcommands, error cases, permissions, and autocomplete
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| migrations/004_snippets.cjs | Creates snippets table with UNIQUE(guild_id, name) constraint and indexes for fast lookups |
| src/commands/snippet.js | Implements all five snippet subcommands with autocomplete, permission checks, and error handling |
| tests/commands/snippet.test.js | Comprehensive test suite with 22 tests covering all functionality and edge cases |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
| Filename | Overview |
|---|---|
| migrations/004_snippets.cjs | Creates snippets table with proper constraints, indexes, and down migration |
| src/commands/snippet.js | Implements /snippet command with proper SQL escaping, error handling, and autocomplete; config.json missing entry will make command admin-only by default |
| tests/commands/snippet.test.js | Comprehensive test suite with 22 tests covering all subcommands, permissions, autocomplete, and error handling |
| config.json | Missing snippet: "everyone" entry in allowedCommands causes command to default to admin-only |
Last reviewed commit: 287dd0a
Additional Comments (1)
Prompt To Fix With AIThis is a comment left during a code review.
Path: config.json
Line: 155-156
Comment:
missing `snippet` command in `allowedCommands` — without this, the command defaults to admin-only access (see `src/utils/permissions.js:98-100`). should add `"snippet": "everyone"` to make it accessible to all users like other utility commands
```suggestion
"modlog": "moderator",
"announce": "moderator",
"snippet": "everyone"
```
How can I resolve this? If you propose a fix, please make it concise. |
…E wildcard escape
There was a problem hiding this comment.
Review Summary — 5 issues found
🔴 Critical (1)
- Missing
snippetin config.jsonallowedCommands— Without this entry,hasPermission()(src/utils/permissions.js:98-99) defaults to admin-only viaisGuildAdmin(). Add"snippet": "everyone"toconfig.jsonunderpermissions.allowedCommands. This file wasn't changed in the PR, which is exactly the problem.
🟡 Warning (2)
- Redundant length checks cause CI failure (snippet.js:142-154) —
setMaxLength()on the SlashCommandBuilder already enforces limits. These runtime checks are unreachable dead code with untested branches, dragging global branch coverage to 79.68% (below the 80% threshold). CI is failing because of this. - Autocomplete ILIKE not escaped (snippet.js:403) — Search handler correctly escapes wildcards (line 224) but autocomplete does not. Inconsistent handling.
🔵 Nitpick (2)
- Redundant index (004_snippets.cjs:27) —
idx_snippets_nameon(guild_id, name)duplicates the implicit unique index fromUNIQUE(guild_id, name). interaction.guildIdinconsistency (snippet.js:320) — Rest of the file usesinteraction.guild.id.
CI Status
- ❌ CI failing: Branch coverage 79.68% < 80% threshold. Removing the unreachable length checks (issue #2) should fix this.
See inline comments for suggested fixes.
Additional Comments (1)
the permission system (src/utils/permissions.js:98-100) defaults commands not in this list to admin-only, but |
Summary
Adds the
/snippetslash command for saving and sharing code snippets in Discord with syntax highlighting.Changes
Migration (
migrations/004_snippets.cjs)snippetstable withUNIQUE(guild_id, name)constraintguild_idand(guild_id, name)for fast lookupsCommand (
src/commands/snippet.js)/snippet save name language code [description]— save a named snippet (max 64-char name, max 4000-char code)/snippet get name— retrieve snippet as syntax-highlighted code block, incrementsusage_count/snippet search query— ILIKE search across name, description, and code; returns top 10 as embed/snippet list [sort]— paginated embed (10/page), sortable byrecent(default) orpopular/snippet delete name— author or moderator onlyget/delete, language list forsaveTests (
tests/commands/snippet.test.js)22 tests covering all subcommands, duplicate rejection, permission checks, autocomplete, and error handling.
Test Results
Closes #41