Application Bot — agent guide
This document orients AI coding agents to the application-bot (Discord “staff application” style bot). It complements README.md (human setup) with repo layout, runtime model, and change patterns.
What this project is
- A Discord bot built on Sapphire Framework (
@sapphire/framework, decorators,@sapphire/plugin-subcommands). - discord.js v14 for the Discord API.
- PostgreSQL via Drizzle ORM (
drizzle-orm,pg); migrations live underdrizzle/. - Redis (
ioredis) for auxiliary caching/state where managers use it. - TypeScript source; SWC compiles
src/→dist/(seepackage.jsonbuildandDockerfile).
The bot is oriented around guild applications (questions/answers, staff decisions), plus modules for blacklist, questions CRUD, reports, forums, transcripts, utilities, etc.
How it starts
src/index.tsconstructsApplicationClientfromsrc/lib/app-client.ts, sets Discord intents/partials, and passesenabledModules(whitelist of folder names undersrc/modules/).- Side-effect imports run before
login:@lib/config/register.js— loadsconfig.jsonfrom process cwd intocontainer.config.@sapphire/plugin-subcommands/register@lib/db-register.js— Postgres pool, Drizzle,migrate()from/app/drizzle(Docker path), seeds default guild row insettings, setscontainer.drizzle.@lib/redis-register.js—container.redis.
ApplicationClient.login()beforesuper.login():- For each enabled module: dynamically imports every
managers/*.jsfile, instantiates default export (must extendBaseManager), callsinit(), registers oncontainer[manager.name](name must be unique and not collide with existingcontainerkeys). - Registers the module path with
this.stores.registerPath(rootDir)so Sapphire loadscommands/,listeners/,interaction-handlers/,preconditions/from that module tree.
- For each enabled module: dynamically imports every
client.login(process.env.BOT_TOKEN).
Implication for agents: adding a new top-level feature area usually means a new folder under src/modules/<name>/ and adding <name> to enabledModules in src/index.ts, plus any new container types in src/augments.d.ts.
Configuration and secrets
| Source | Purpose |
|---|---|
.env | BOT_TOKEN, Postgres (POSTGRES_*), OWNER, REDIS_HOST, etc. (see README.md). |
config.json (repo root, cwd at runtime) | Guild IDs for channels/roles/tags/categories and initial guild string. Typed in src/lib/config/config.d.ts. README example may lag the TypeScript type (e.g. wiki, categories) — trust config.d.ts as source of truth for required shape. |
Config is read asynchronously in register.ts and assigned to container.config.
Database
- Schema:
src/schema.ts— Drizzle tables/enums (applications, blacklist, settings, questions, transcript/messages, etc.). - Migrations:
drizzle/*.sql+drizzle/meta/; config indrizzle.config.ts(hostpostgresmatches Docker service name). - Runtime DB handle:
container.drizzle(declared indb-register.tsmodule augmentation).
db-register.ts uses connection string host postgres — aligned with docker compose, not typical bare-metal localhost without adjustment.
Redis
container.redis—ioredisclient; host fromREDIS_HOSTorlocalhost.
Path aliases and imports
- TypeScript / SWC:
@lib/*resolves tosrc/lib/*.tsconfig.jsonusespathsonly (nobaseUrl— TypeScript 6);.swcrcstill usesbaseUrl+ shortpathsentries — both must keep the same logical alias. - ESM NodeNext resolution: source files generally import with
.jsextensions in import paths (e.g.@lib/app-client.js) even when authoring.tsfiles.
src/lib/ — shared infrastructure
| Area | Role |
|---|---|
app-client.ts | Custom SapphireClient: loads per-module managers + registerPath for each enabled module. |
config/ | Config load + Config types. |
db-register.ts / redis-register.ts | Wire ORM and Redis into container. |
db.utils.ts | SQL string helpers exposed as genSelect / genInsert / etc. on BaseManager; managers in this repo use Drizzle directly — these helpers may be unused legacy hooks. |
managers/base.manager.ts | Abstract manager: holds drizzle, enforces create/delete/get/update, exposes genSelect / genInsert / genUpdate / genDelete. |
command-utils/ | Reusable embeds/components/helpers for commands and interactions (grouped by domain: apply/, application/, question/, etc.). |
constants/ | e.g. custom-ids.ts — Discord customId strings as colon-separated segments (apply:…, app:…, q:…, rpt:…); also ForumCustomIDs and ShareCustomIDs for forums / share-your-bot pieces that import this file. |
precondition-util.ts, util.ts | Shared helpers. |
src/modules/ — feature modules
Each module is a Sapphire root: expected subfolders vary by feature but commonly include:
commands/— slash (or other) commands; use@ApplyOptions,Commandfrom Sapphire.listeners/— Discord client events.interaction-handlers/— buttons, modals, selects (Sapphire interaction handlers).preconditions/— named preconditions referenced from commands (e.g.ApplicationsEnabled,Blacklisted).managers/— optional; if present, each*.tscompiles to*.jsand default export is loaded byApplicationClientas described above.
Current enabledModules list (from src/index.ts): applications, blacklist, errors, misc, owner, questions, report, forums, utility, share-your-bot, transcripts.
Cross-cutting: src/modules/errors/ — central error/denied listeners.
src/preconditions/
Global preconditions (e.g. StaffOnly, OwnerOnly, ModOnly, RequiredRole, TrialSupportOnly) live here; module-specific ones live under src/modules/<module>/preconditions/.
Typings / container
src/augments.d.ts— augments@sapphire/piecesContainerwith managers andconfig. When adding a manager loaded byApplicationClient, update this file sothis.container.*is typed.- Some local imports in augments use
modules/...without@lib— follow existing file style when editing that file.
Build, run, and tooling
- Install: Yarn (see
package.json,.yarnrc.yml). - Compile:
yarn build— SWCsrc→distper.swcrc. - Docker:
docker compose up --buildis the documented way to run Postgres + Redis + bot (docker-compose.yml,Dockerfile). - Drizzle CLI:
drizzle-kitin devDependencies;drizzle.config.tspoints at./src/schema.ts.
JSON assets
json/base-questions.json— seed/reference question data used by the questions flow (see question manager/command usage when changing).
Conventions for edits (agent checklist)
- New module: create
src/modules/<module>/with Sapphire pieces + optionalmanagers/default export extendingBaseManagerwith a uniquename; add module string tosrc/index.tsenabledModules; extendsrc/augments.d.tsif a manager is added. - DB change: edit
src/schema.ts, generate/author migrations underdrizzle/, keepdrizzle/meta/_journal.jsonconsistent with project practice. - New slash command: place under the right module’s
commands/, use existing commands as templates (ApplyOptions, preconditions,registerApplicationCommands). - Buttons/modals/selects: prefer constants in
src/lib/constants/custom-ids.ts— colon-delimited ids; reuseForumCustomIDs/ShareCustomIDswhen touching those modules (or module-local constants if a pattern truly diverges). - Staff/guild assumptions: respect
container.configshape fromconfig.d.ts— IDs are strings in config; code often usesBigInt()where Discord snowflakes are required.
Documentation sync (do this whenever you change code)
Treat doc updates as part of the same change, not a follow-up. Before you finish a task that touched src/:
docs/module-map/<module>.md— If you added/removed/renamed commands, listeners, interaction handlers, managers, or materially changed flows, update that module’s map (paths, table rows, custom id prefixes). If you changedsrc/lib/pieces used by several modules (e.g.command-utils/,constants/custom-ids.ts), update every affected module map.README.md— If setup steps, env/config shape, or other user-facing overview text should change, update it in the same PR/session.AGENTS.md— Only if you changed global conventions (startup,enabledModules, config contract, agent checklist itself).
There is no separate docs/module-plans/ tree; long “roadmap” docs were removed. Prefer short, accurate module map updates over duplicate planning files.
Known gaps / TODOs
- None tracked centrally in
README.md; use issues or your own tracker for future work.
Cursor loads .cursor/rules/*.mdc for extra guidance; this file remains the detailed repo map.