buster/.cursor/rules/global.mdc

617 lines
18 KiB
Plaintext

---
description:
globs:
alwaysApply: true
---
# CLAUDE.md
This file provides guidance to Claude Code when working with code in this monorepo.
## Monorepo Structure
This is a pnpm-based monorepo using Turborepo with the following structure:
### Apps (`@buster-app/*`)
- `apps/web` - Next.js frontend application
- `apps/server` - Node.js/Hono backend server
- `apps/trigger` - Background job processing with Trigger.dev v3
- `apps/electric-server` - Electric SQL sync server
- `apps/api` - Rust backend API (legacy)
- `apps/cli` - Command-line tools (Rust)
### Packages (`@buster/*`)
- `packages/ai` - AI agents, tools, and workflows using Mastra framework
- `packages/database` - Database schema, migrations, and utilities (Drizzle ORM)
- `packages/data-source` - Data source adapters (PostgreSQL, MySQL, BigQuery, Snowflake, etc.)
- `packages/access-controls` - Permission and access control logic
- `packages/stored-values` - Stored values management
- `packages/rerank` - Document reranking functionality
- `packages/server-shared` - Shared server types and utilities
- `packages/test-utils` - Shared testing utilities
- `packages/vitest-config` - Shared Vitest configuration
- `packages/typescript-config` - Shared TypeScript configuration
- `packages/web-tools` - Web scraping and research tools
- `packages/slack` - Standalone Slack integration (OAuth, messaging, channels)
- `packages/supabase` - Supabase setup and configuration
## Development Workflow
When writing code, follow this workflow to ensure code quality:
### 1. Write Modular, Testable Functions
- Create small, focused functions with single responsibilities
- Design functions to be easily testable with clear inputs/outputs
- Use dependency injection for external dependencies
- Follow existing patterns in the codebase
### 2. Build Features by Composing Functions
- Combine modular functions to create complete features
- Keep business logic separate from infrastructure concerns
- Use proper error handling at each level
### 3. Ensure Type Safety
```bash
# Build entire monorepo to check types
turbo run build
# Build specific package/app
turbo run build --filter=@buster/ai
turbo run build --filter=@buster-app/web
# Type check without building
turbo run typecheck
turbo run typecheck --filter=@buster/database
```
### 4. Run Biome for Linting & Formatting
```bash
# Check files with Biome
pnpm run check path/to/file.ts
pnpm run check packages/ai
# Auto-fix linting, formatting, and import organization
pnpm run check:fix path/to/file.ts
pnpm run check:fix packages/ai
```
### 5. Run Tests with Vitest
```bash
# Run all tests
pnpm run test
# Run tests for specific package
turbo run test --filter=@buster/ai
# Run specific test file
pnpm run test path/to/file.test.ts
# Watch mode for development
pnpm run test:watch
```
## Code Quality Standards
### TypeScript Configuration
- **Strict mode enabled** - All strict checks are on
- **No implicit any** - Always use specific types
- **Strict null checks** - Handle null/undefined explicitly
- **No implicit returns** - All code paths must return
- **Consistent file casing** - Enforced by TypeScript
### Biome Rules (Key Enforcements)
- **`useImportType: "warn"`** - Use type-only imports when possible
- **`noExplicitAny: "error"`** - Never use `any` type
- **`noUnusedVariables: "error"`** - Remove unused code
- **`noNonNullAssertion: "error"`** - No `!` assertions
- **`noConsoleLog: "warn"`** - Avoid console.log in production
- **`useNodejsImportProtocol: "error"`** - Use `node:` prefix for Node.js imports
### Testing Practices
#### Test File Naming & Location
- **Unit tests**: `filename.test.ts` (alongside the source file)
- **Integration tests**: `filename.int.test.ts` (alongside the source file)
- Never separate tests into their own folders - keep them with the code they test
#### Testing Strategy
1. **Prioritize mocking** for unit tests after understanding API/DB structure
2. **Integration tests** should focus on single connection confirmations
3. **Mock external dependencies** appropriately
4. **Use descriptive test names** that explain the behavior
5. **Write tests alongside implementation** for better coverage
#### Example Test Structure
```typescript
// user-service.ts
export function getUserById(id: string) { /* ... */ }
// user-service.test.ts (same directory)
import { describe, it, expect, vi } from 'vitest';
import { getUserById } from './user-service';
describe('getUserById', () => {
it('should return user when found', async () => {
// Test implementation
});
});
// user-service.int.test.ts (integration test)
import { describe, it, expect } from 'vitest';
import { getUserById } from './user-service';
describe('getUserById integration', () => {
it('should connect to database successfully', async () => {
// Single connection test
});
});
```
## Code Style Preferences
### Type Safety
- **Zod-First Approach** - Use Zod schemas as the single source of truth for both validation and types
- **Use `z.infer<typeof schema>` for types** - Prefer inferred types over separate interfaces
- **Never use `any`** - Biome enforces this with `noExplicitAny: "error"`
- **Avoid `unknown` unless necessary** - Prefer specific types or properly typed unions
- **Handle null/undefined explicitly** - TypeScript strict mode enforces this
- **Safe array access** - Use validation helpers when needed
- **Type-only imports** - Use `import type` for better performance
#### Zod-First Type Safety Pattern
```typescript
// ✅ Good: Zod schema as single source of truth
const userSchema = z.object({
id: z.string().min(1),
email: z.string().email(),
role: z.enum(['admin', 'user']),
});
type User = z.infer<typeof userSchema>; // Inferred type
// ✅ Good: Safe runtime validation
const validatedUser = userSchema.parse(rawData);
// ✅ Good: Safe array access when needed
import { validateArrayAccess } from '@buster/ai/utils/validation-helpers';
const firstItem = validateArrayAccess(array, 0, 'user processing');
// ❌ Avoid: Separate interface + unsafe access
interface User {
id: string;
email: string;
}
const user = rawData as User; // Unsafe type assertion
const firstItem = array[0]!; // Non-null assertion not allowed
```
### Import Organization
- Use **type-only imports** when importing only types: `import type { SomeType } from './types'`
- Biome automatically organizes imports with `pnpm run check:fix`
- Use Node.js protocol: `import { readFile } from 'node:fs'`
- Follow path aliases defined in each package's tsconfig.json
### String Handling
- **Prefer template literals** over string concatenation for better readability
- Use template literals for multi-line strings and string interpolation
#### String Handling Patterns
```typescript
// ✅ Good: Template literals
const message = `User ${userId} not found`;
const multiLine = `This is a
multi-line string`;
const path = `${baseUrl}/api/users/${userId}`;
// ❌ Avoid: String concatenation
const message = 'User ' + userId + ' not found';
const path = baseUrl + '/api/users/' + userId;
```
### Error Handling
- **Always use try-catch blocks** for async operations and external calls
- **Never use `any` in catch blocks** - Biome enforces this
- **Validate external data** with Zod schemas before processing
- **Provide meaningful error messages** with context for debugging
- **Handle errors at appropriate levels** - don't let errors bubble up uncaught
- **Use structured logging** for error tracking
#### Error Handling Patterns
```typescript
// ✅ Good: Comprehensive error handling
async function processUserData(userId: string) {
try {
const user = await getUserById(userId);
if (!user) {
throw new Error(`User not found: ${userId}`);
}
const validatedData = UserSchema.parse(user);
return await processData(validatedData);
} catch (error) {
logger.error('Failed to process user data', {
userId,
error: error instanceof Error ? error.message : 'Unknown error',
stack: error instanceof Error ? error.stack : undefined
});
throw new Error(`User data processing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
// ✅ Good: Database operations with error handling
async function createResource(data: CreateResourceInput) {
try {
const validatedData = CreateResourceSchema.parse(data);
return await db.transaction(async (tx) => {
const resource = await tx.insert(resources).values(validatedData).returning();
await tx.insert(resourceAudit).values({
resourceId: resource[0].id,
action: 'created',
createdAt: new Date()
});
return resource[0];
});
} catch (error) {
if (error instanceof ZodError) {
throw new Error(`Invalid resource data: ${error.errors.map(e => e.message).join(', ')}`);
}
logger.error('Database error creating resource', { data, error });
throw new Error('Failed to create resource');
}
}
// ❌ Avoid: Unhandled async operations
async function badExample(userId: string) {
const user = await getUserById(userId); // No error handling
return user.data; // Could fail if user is null
}
```
## Test Utilities
The `@buster/test-utils` package provides shared testing utilities:
### Environment Helpers
```typescript
import { setupTestEnvironment, withTestEnv } from '@buster/test-utils/env-helpers';
// Manual setup/teardown
beforeAll(() => setupTestEnvironment());
afterAll(() => cleanupTestEnvironment());
// Or use the wrapper
await withTestEnv(async () => {
// Your test code here
});
```
### Mock Helpers
```typescript
import { createMockFunction, mockConsole, createMockDate } from '@buster/test-utils/mock-helpers';
// Create vitest mock functions
const mockFn = createMockFunction<(arg: string) => void>();
// Mock console methods (allowed in tests)
const consoleMock = mockConsole();
// Test code that logs...
consoleMock.restore();
// Mock dates for time-sensitive tests
const dateMock = createMockDate(new Date('2024-01-01'));
// Test code...
dateMock.restore();
```
### Database Test Helpers
```typescript
import { createTestChat, cleanupTestChats } from '@buster/test-utils/database/chats';
import { createTestMessage, cleanupTestMessages } from '@buster/test-utils/database/messages';
// Create test data
const chat = await createTestChat({
userId: 'test-user',
title: 'Test Chat'
});
const message = await createTestMessage({
chatId: chat.id,
role: 'user',
content: 'Test message'
});
// Cleanup after tests
await cleanupTestMessages(chat.id);
await cleanupTestChats('test-user');
```
## Quick Command Reference
### Building & Type Checking
```bash
# Build all packages
turbo run build
# Build specific package/app
turbo run build --filter=@buster/ai
turbo run build --filter=@buster-app/web
# Type check only
turbo run typecheck
turbo run typecheck --filter=@buster/database
```
### Linting & Formatting
```bash
# Check and auto-fix with Biome
pnpm run check:fix path/to/file.ts
pnpm run check:fix packages/ai
# Check only (no fixes)
pnpm run check path/to/file.ts
```
### Testing
```bash
# Run all tests
pnpm run test
# Run tests for specific package
turbo run test --filter=@buster/ai
# Run specific test file
pnpm run test path/to/file.test.ts
# Watch mode
pnpm run test:watch
```
### Database Commands
```bash
pnpm run db:generate # Generate types from schema
pnpm run db:migrate # Run migrations
pnpm run db:push # Push schema changes
pnpm run db:studio # Open Drizzle Studio
```
## Helper Organization Pattern
When building helper functions, follow this organizational pattern:
### Database Helpers (in `packages/database/`)
```
packages/database/src/helpers/
├── index.ts # Export all helpers
├── messages.ts # Message-related helpers
├── users.ts # User-related helpers
├── chats.ts # Chat-related helpers
└── {entity}.ts # Entity-specific helpers
```
### Package-Specific Utilities
```
packages/{package}/src/utils/
├── index.ts # Export all utilities
├── {domain}/ # Domain-specific utilities
│ ├── index.ts
│ └── helpers.ts
└── helpers.ts # General helpers
```
### Key Principles
- **Co-locate helpers** with the schema/types they operate on
- **Group by entity** (one file per database table/domain object)
- **Export from package root** for easy importing
- **Use TypeScript** with proper types (no `any`)
- **Follow naming conventions** that clearly indicate purpose
### Example Usage
```typescript
// ✅ Good: Clear, typed helpers exported from package root
import { getRawLlmMessages, getMessagesForChat } from '@buster/database';
// ❌ Avoid: Direct database queries scattered throughout codebase
import { db, messages, eq } from '@buster/database';
const result = await db.select().from(messages).where(eq(messages.chatId, chatId));
```
## Background Job Processing (Trigger.dev)
The `apps/trigger` package provides background job processing using **Trigger.dev v3**.
### 🚨 CRITICAL: Always Use v3 Patterns
```typescript
// ✅ CORRECT - Always use this pattern
import { task } from '@trigger.dev/sdk/v3';
export const myTask = task({
id: 'my-task',
run: async (payload: InputType): Promise<OutputType> => {
// Task implementation
},
});
```
### Essential Requirements
1. **MUST export every task** from the file
2. **MUST use unique task IDs** within the project
3. **MUST import from** `@trigger.dev/sdk/v3`
4. **Use Zod schemas** for payload validation
### Common Task Patterns
#### Schema-Validated Task (Recommended)
```typescript
import { schemaTask } from '@trigger.dev/sdk/v3';
import { z } from 'zod';
// Define schema for type safety
export const TaskInputSchema = z.object({
userId: z.string(),
data: z.record(z.unknown()),
});
export type TaskInput = z.infer<typeof TaskInputSchema>;
export const processUserTask = schemaTask({
id: 'process-user',
schema: TaskInputSchema,
maxDuration: 300, // 5 minutes
run: async (payload) => {
// Payload is validated and typed
return { success: true };
},
});
```
#### Triggering Tasks
```typescript
import { tasks } from '@trigger.dev/sdk/v3';
import type { processUserTask } from '@buster-app/trigger/tasks';
// Trigger from API routes
const handle = await tasks.trigger<typeof processUserTask>('process-user', {
userId: 'user123',
data: {}
});
```
### Development Commands
```bash
# Development server
pnpm run trigger:dev
# Run tests
pnpm run trigger:test
# Deploy
pnpm run trigger:deploy
```
**See `apps/trigger/CLAUDE.md` for complete Trigger.dev guidelines.**
## Key Dependencies
- **Turborepo** - Monorepo orchestration and caching
- **pnpm** - Fast, disk space efficient package manager
- **Biome** - Fast linting and formatting (replaces ESLint/Prettier)
- **TypeScript** - Strict type checking across all packages
- **Vitest** - Fast unit testing framework
- **Zod** - Runtime validation and type inference
- **Mastra** - AI agent framework for LLM workflows
- **Trigger.dev v3** - Background job processing
- **Drizzle ORM** - Type-safe database toolkit
- **Braintrust** - LLM observability and evaluation
## Complete Development Workflow Example
When implementing a new feature:
```bash
# 1. Write your modular, testable functions
# 2. Compose them into the feature
# 3. Write tests alongside the code
# 4. Ensure type safety
turbo run build --filter=@buster/ai
# or for all packages:
turbo run build
# 5. Fix linting and formatting
pnpm run check:fix packages/ai
# 6. Run tests
turbo run test --filter=@buster/ai
# or specific test:
pnpm run test packages/ai/src/feature.test.ts
# 7. If all passes, commit your changes
git add .
git commit -m "feat: add new feature"
```
## Slack Package (@buster/slack)
The `@buster/slack` package is a **standalone Slack integration** with no database dependencies. It provides:
### Features
- **OAuth 2.0 Authentication** - Complete OAuth flow with state management
- **Channel Management** - List, validate, join/leave channels
- **Messaging** - Send messages, replies, updates with retry logic
- **Message Tracking** - Interface for threading support
- **Type Safety** - Zod validation throughout
### Architecture
The package uses **interface-based design** where consuming applications must implement:
- `ISlackTokenStorage` - For token persistence
- `ISlackOAuthStateStorage` - For OAuth state management
- `ISlackMessageTracking` - For message threading (optional)
### Usage Pattern
```typescript
// All functions accept tokens as parameters
const channels = await channelService.getAvailableChannels(accessToken);
const result = await messagingService.sendMessage(accessToken, channelId, message);
```
### Testing
```bash
# Run tests
turbo run test --filter=@buster/slack
# Build
turbo run build --filter=@buster/slack
# Type check
turbo run typecheck --filter=@buster/slack
```
### Key Principles
- **No database dependencies** - Uses interfaces for storage
- **Token-based** - All functions accept tokens as parameters
- **Framework-agnostic** - Works with any Node.js application
- **Comprehensive error handling** - Typed errors with retry logic
## Important Notes
- **Never use `any`** - Biome will error on this
- **Always handle errors** properly with try-catch
- **Write tests alongside code** - not in separate folders
- **Use Zod for validation** - single source of truth
- **Run type checks** before committing
- **Follow existing patterns** in the codebase
This ensures high code quality and maintainability across the monorepo.
## Common Biome Overrides
Test files have relaxed rules to allow:
- `console.log` for debugging tests
- Non-null assertions (`!`) in test scenarios
- `any` type when mocking (though prefer proper types)
Database package allows `any` for Drizzle ORM compatibility.
## Environment Variables
The monorepo uses a strict environment mode. Key variables include:
- Database connections (Supabase, PostgreSQL, etc.)
- API keys (OpenAI, Anthropic, etc.)
- Service URLs and configurations
See `.env.example` files in each package for required variables.
# important-instruction-reminders
Do what has been asked; nothing more, nothing less.
NEVER create files unless they're absolutely necessary for achieving your goal.
ALWAYS prefer editing an existing file to creating a new one.
NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
## Biome Linting Instructions
### Linting Rules
- Always use `pnpm run check` or `pnpm run check:fix`
- **Rule: `I don't want Claude to ever run a biome lint fix only biome lint`**
- This means ONLY use `pnpm run check` (linting without auto-fixing)
- Do NOT use `pnpm run check:fix`
- Claude should understand to ONLY run lint checks, never auto-fix