forked from syntaxbullet/aurorabot
Update references to removed web/package.json: - CI workflow: Remove 'cd web && bun install' step - AGENTS.md: Remove web-specific dev commands (cd web && bun run dev/build) - AGENTS.md: Update schema location to reflect domain module split - Add Docker commands as recommended local dev approach All dependencies now installed from root package.json.
245 lines
7.1 KiB
Markdown
245 lines
7.1 KiB
Markdown
# AGENTS.md - AI Coding Agent Guidelines
|
|
|
|
## Project Overview
|
|
|
|
AuroraBot is a Discord bot with a web dashboard built using Bun, Discord.js, React, and PostgreSQL with Drizzle ORM.
|
|
|
|
## Build/Lint/Test Commands
|
|
|
|
```bash
|
|
# Development
|
|
bun --watch bot/index.ts # Run bot + web dashboard 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, web, and database
|
|
docker compose up app # Start just the app (bot + web)
|
|
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/ # React dashboard
|
|
├── src/pages/ # React pages
|
|
├── src/components/ # UI components (ShadCN/Radix)
|
|
└── src/hooks/ # React hooks
|
|
```
|
|
|
|
## 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<ReturnType> => {
|
|
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:** React 19 + Bun HTTP Server
|
|
- **Database:** PostgreSQL 16+ with Drizzle ORM
|
|
- **UI:** Tailwind CSS v4 + ShadCN/Radix
|
|
- **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` |
|