# AGENTS.md - AI Coding Agent Guidelines ## Project Overview AuroraBot is a Discord bot with a REST API built using Bun, Discord.js, and PostgreSQL with Drizzle ORM. ## Build/Lint/Test Commands ```bash # Development bun --watch bot/index.ts # Run bot + API server with hot reload # Testing bun test # Run all tests bun test path/to/file.test.ts # Run single test file bun test --watch # Watch mode bun test shared/modules/economy # Run tests in directory # Database bun run generate # Generate Drizzle migrations (Docker) bun run migrate # Run migrations (Docker) bun run db:push # Push schema changes (Docker) bun run db:push:local # Push schema changes (local) bun run db:studio # Open Drizzle Studio # Docker (recommended for local dev) docker compose up # Start bot, API, and database docker compose up app # Start just the app (bot + API) docker compose up db # Start just the database ``` ## Project Structure ``` bot/ # Discord bot ├── commands/ # Slash commands by category ├── events/ # Discord event handlers ├── lib/ # Bot core (BotClient, handlers, loaders) ├── modules/ # Feature modules (views, interactions) └── graphics/ # Canvas image generation shared/ # Shared between bot and web ├── db/ # Database schema and migrations ├── lib/ # Utils, config, errors, types └── modules/ # Domain services (economy, user, etc.) web/ # API server └── src/routes/ # API route handlers ``` ## Import Conventions Use path aliases defined in tsconfig.json: ```typescript // External packages first import { SlashCommandBuilder } from "discord.js"; import { eq } from "drizzle-orm"; // Path aliases second import { economyService } from "@shared/modules/economy/economy.service"; import { UserError } from "@shared/lib/errors"; import { users } from "@db/schema"; import { createErrorEmbed } from "@lib/embeds"; import { handleTradeInteraction } from "@modules/trade/trade.interaction"; // Relative imports last import { localHelper } from "./helper"; ``` **Available Aliases:** - `@/*` - bot/ - `@shared/*` - shared/ - `@db/*` - shared/db/ - `@lib/*` - bot/lib/ - `@modules/*` - bot/modules/ - `@commands/*` - bot/commands/ ## Naming Conventions | Element | Convention | Example | | ---------------- | ----------------------- | ---------------------------------------- | | Files | camelCase or kebab-case | `BotClient.ts`, `economy.service.ts` | | Classes | PascalCase | `CommandHandler`, `UserError` | | Functions | camelCase | `createCommand`, `handleShopInteraction` | | Constants | UPPER_SNAKE_CASE | `EVENTS`, `BRANDING` | | Enums | PascalCase | `TimerType`, `TransactionType` | | Services | camelCase singleton | `economyService`, `userService` | | Types/Interfaces | PascalCase | `Command`, `Event`, `GameConfigType` | | DB tables | snake_case | `users`, `moderation_cases` | | Custom IDs | snake_case with prefix | `shop_buy_`, `trade_accept_` | ## Code Patterns ### Command Definition ```typescript export const commandName = createCommand({ data: new SlashCommandBuilder() .setName("commandname") .setDescription("Description"), execute: async (interaction) => { await interaction.deferReply(); // Implementation }, }); ``` ### Service Pattern (Singleton Object) ```typescript export const serviceName = { methodName: async (params: ParamType): Promise => { return await withTransaction(async (tx) => { // Database operations }); }, }; ``` ### Module File Organization - `*.view.ts` - Creates Discord embeds/components - `*.interaction.ts` - Handles button/select/modal interactions - `*.types.ts` - Module-specific TypeScript types - `*.service.ts` - Business logic (in shared/modules/) - `*.test.ts` - Test files (co-located with source) ## Error Handling ### Custom Error Classes ```typescript import { UserError, SystemError } from "@shared/lib/errors"; // User-facing errors (shown to user) throw new UserError("You don't have enough coins!"); // System errors (logged, generic message shown) throw new SystemError("Database connection failed"); ``` ### Standard Error Pattern ```typescript try { const result = await service.method(); await interaction.editReply({ embeds: [createSuccessEmbed(result)] }); } catch (error) { if (error instanceof UserError) { await interaction.editReply({ embeds: [createErrorEmbed(error.message)] }); } else { console.error("Unexpected error:", error); await interaction.editReply({ embeds: [createErrorEmbed("An unexpected error occurred.")], }); } } ``` ## Database Patterns ### Transaction Usage ```typescript import { withTransaction } from "@/lib/db"; return await withTransaction(async (tx) => { const user = await tx.query.users.findFirst({ where: eq(users.id, discordId), }); await tx .update(users) .set({ coins: newBalance }) .where(eq(users.id, discordId)); await tx.insert(transactions).values({ userId: discordId, amount, type }); return user; }, existingTx); // Pass existing tx if in nested transaction ``` ### Schema Notes - Use `bigint` mode for Discord IDs and currency amounts - Relations defined separately from table definitions - Schema modules: `shared/db/schema/*.ts` (users, inventory, economy, quests, moderation) ## Testing ### Test File Structure ```typescript import { describe, it, expect, mock, beforeEach } from "bun:test"; // Mock modules BEFORE imports mock.module("@shared/db/DrizzleClient", () => ({ DrizzleClient: { query: mockQuery }, })); describe("serviceName", () => { beforeEach(() => { mockFn.mockClear(); }); it("should handle expected case", async () => { // Arrange mockFn.mockResolvedValue(testData); // Act const result = await service.method(input); // Assert expect(result).toEqual(expected); expect(mockFn).toHaveBeenCalledWith(expectedArgs); }); }); ``` ## Tech Stack - **Runtime:** Bun 1.0+ - **Bot:** Discord.js 14.x - **Web:** Bun HTTP Server (REST API) - **Database:** PostgreSQL 16+ with Drizzle ORM - **UI:** Discord embeds and components - **Validation:** Zod - **Testing:** Bun Test - **Container:** Docker ## Key Files Reference | Purpose | File | | ------------- | ---------------------- | | Bot entry | `bot/index.ts` | | DB schema | `shared/db/schema.ts` | | Error classes | `shared/lib/errors.ts` | | Config loader | `shared/lib/config.ts` | | Environment | `shared/lib/env.ts` | | Embed helpers | `bot/lib/embeds.ts` | | Command utils | `shared/lib/utils.ts` |