# 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 with hot reload
bun --hot web/src/index.ts       # Run 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

# Web Dashboard
cd web && bun run build          # Build production web assets
cd web && bun run dev            # Development server
```

## 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 location: `shared/db/schema.ts`

## 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` |
