mirror of https://github.com/buster-so/buster.git
Enhance Slack event handling by adding hourglass reaction on app mentions and refactoring reaction management in slack-agent-task. Update CLAUDE.md and global.mdc with new guidelines for testing and development workflows. Adjust .gitignore for rerank package to ensure proper directory exclusion.
This commit is contained in:
parent
f9786c75c8
commit
ba04450469
|
@ -7,6 +7,8 @@ alwaysApply: true
|
|||
|
||||
This file provides guidance to Claude Code when working with code in this monorepo.
|
||||
|
||||
**Note**: Many packages and apps have their own CLAUDE.md files with specific implementation details and patterns. Always check for a local CLAUDE.md when working in a specific directory.
|
||||
|
||||
## Monorepo Structure
|
||||
|
||||
This is a pnpm-based monorepo using Turborepo with the following structure:
|
||||
|
@ -33,6 +35,7 @@ This is a pnpm-based monorepo using Turborepo with the following structure:
|
|||
- `packages/web-tools` - Web scraping and research tools
|
||||
- `packages/slack` - Standalone Slack integration (OAuth, messaging, channels)
|
||||
- `packages/supabase` - Supabase setup and configuration
|
||||
- `packages/sandbox` - Sandboxed code execution using Daytona SDK
|
||||
|
||||
## Development Workflow
|
||||
|
||||
|
@ -42,6 +45,8 @@ When writing code, follow this workflow to ensure code quality:
|
|||
- Create small, focused functions with single responsibilities
|
||||
- Design functions to be easily testable with clear inputs/outputs
|
||||
- Use dependency injection for external dependencies
|
||||
- **IMPORTANT: Write functional, composable code - avoid classes**
|
||||
- All features should be composed of testable functions
|
||||
- Follow existing patterns in the codebase
|
||||
|
||||
### 2. Build Features by Composing Functions
|
||||
|
@ -75,12 +80,18 @@ 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
|
||||
**Important**: Always run unit tests before completing any task to ensure code changes don't break existing functionality.
|
||||
|
||||
```bash
|
||||
# Run unit tests (always run these when working locally)
|
||||
turbo run test:unit
|
||||
|
||||
# Run unit tests for specific package
|
||||
turbo run test:unit --filter=@buster/ai
|
||||
|
||||
# Run integration tests ONLY for specific features/packages you're working on
|
||||
turbo run test:integration --filter=@buster/database
|
||||
|
||||
# Run specific test file
|
||||
pnpm run test path/to/file.test.ts
|
||||
|
@ -89,6 +100,28 @@ pnpm run test path/to/file.test.ts
|
|||
pnpm run test:watch
|
||||
```
|
||||
|
||||
### 6. Pre-Completion Checklist
|
||||
**IMPORTANT: Before finishing any task or creating PRs, always run:**
|
||||
```bash
|
||||
# 1. Run unit tests for the entire monorepo
|
||||
turbo run test:unit
|
||||
|
||||
# 2. Build the entire monorepo to ensure everything compiles
|
||||
turbo run build
|
||||
|
||||
# 3. Run linting for the entire monorepo
|
||||
turbo run lint
|
||||
```
|
||||
|
||||
**Key Testing Guidelines:**
|
||||
- **Always run unit tests, build, and lint** when working locally before considering a task complete
|
||||
- **Unit tests** should be run for the entire monorepo to catch any breaking changes
|
||||
- **Build** must pass for the entire monorepo to ensure type safety
|
||||
- **Integration tests** should only be run for specific packages/features you're working on (NOT the entire monorepo)
|
||||
- **Fix all failing tests, build errors, and lint errors** before completing any task
|
||||
- **Heavily bias toward unit tests** - they are faster and cheaper to run
|
||||
- **Mock everything you can** in unit tests for isolation and speed
|
||||
|
||||
## Code Quality Standards
|
||||
|
||||
### TypeScript Configuration
|
||||
|
@ -98,6 +131,12 @@ pnpm run test:watch
|
|||
- **No implicit returns** - All code paths must return
|
||||
- **Consistent file casing** - Enforced by TypeScript
|
||||
|
||||
### Type Safety and Zod Best Practices
|
||||
- We care deeply about type safety and we use Zod schemas and then export them as types
|
||||
- We prefer using type abstractions over `.parse()` method calls
|
||||
- Always export Zod schemas as TypeScript types to leverage static type checking
|
||||
- Avoid runtime type checking when compile-time type checks are sufficient
|
||||
|
||||
### Biome Rules (Key Enforcements)
|
||||
- **`useImportType: "warn"`** - Use type-only imports when possible
|
||||
- **`noExplicitAny: "error"`** - Never use `any` type
|
||||
|
@ -106,511 +145,75 @@ pnpm run test:watch
|
|||
- **`noConsoleLog: "warn"`** - Avoid console.log in production
|
||||
- **`useNodejsImportProtocol: "error"`** - Use `node:` prefix for Node.js imports
|
||||
|
||||
### Testing Practices
|
||||
### Logging Guidelines
|
||||
- **Never use `console.log`**
|
||||
- **Use appropriate console methods**:
|
||||
- `console.info` for general information
|
||||
- `console.warn` for warning messages
|
||||
- `console.error` for error messages
|
||||
|
||||
#### 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
|
||||
## Error Handling and Logging Philosophy
|
||||
- We care deeply about error handling and logging
|
||||
- Key principles for error management:
|
||||
- Catch errors effectively and thoughtfully
|
||||
- Consider the state errors put the system into
|
||||
- Implement comprehensive unit tests for error scenarios
|
||||
- Log errors strategically for effective debugging
|
||||
- Avoid over-logging while ensuring sufficient context for troubleshooting
|
||||
|
||||
#### 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
|
||||
## Hono API Development Guidelines
|
||||
|
||||
#### Example Test Structure
|
||||
### API Structure and Organization
|
||||
- **Version-based organization** - APIs are organized under `/api/v2/` directory
|
||||
- **Feature-based folders** - Each feature gets its own folder (e.g., `chats/`, `security/`)
|
||||
- **Separate handler files** - Each endpoint handler must be in its own file
|
||||
- **Functional handlers** - All handlers should be pure functions that accept request data and return response data
|
||||
|
||||
### Request/Response Type Safety
|
||||
- **Use shared types** - All request and response types must be defined in `@buster/server-shared`
|
||||
- **Zod schemas** - Define schemas in server-shared and export both the schema and inferred types
|
||||
- **zValidator middleware** - Always use `zValidator` from `@hono/zod-validator` for request validation
|
||||
- **Type imports** - Import types from server-shared packages for consistency
|
||||
|
||||
### Handler Pattern
|
||||
```typescript
|
||||
// user-service.ts
|
||||
export function getUserById(id: string) { /* ... */ }
|
||||
// Handler file (e.g., get-workspace-settings.ts)
|
||||
import type { GetWorkspaceSettingsResponse } from '@buster/server-shared/security';
|
||||
import type { User } from '@buster/database';
|
||||
|
||||
// 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'}`);
|
||||
}
|
||||
export async function getWorkspaceSettingsHandler(
|
||||
user: User
|
||||
): Promise<GetWorkspaceSettingsResponse> {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// ✅ 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
|
||||
}
|
||||
// Route definition (index.ts)
|
||||
.get('/workspace-settings', async (c) => {
|
||||
const user = c.get('busterUser');
|
||||
const response = await getWorkspaceSettingsHandler(user);
|
||||
return c.json(response);
|
||||
})
|
||||
```
|
||||
|
||||
## Test Utilities
|
||||
### Authentication and User Context
|
||||
- **Use requireAuth middleware** - Apply to all protected routes
|
||||
- **Extract user context** - Use `c.get('busterUser')` to get the authenticated user
|
||||
- **Type as User** - Import `User` type from `@buster/database` for handler parameters
|
||||
|
||||
The `@buster/test-utils` package provides shared testing utilities:
|
||||
## Database Operations
|
||||
|
||||
### 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
|
||||
});
|
||||
### Soft Delete and Upsert Practices
|
||||
- In our database, we never hard delete, we always use soft deletes with the `deleted_at` field
|
||||
- For update operations, we should almost always perform an upsert unless otherwise specified
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
**Test Running Guidelines**:
|
||||
- When running tests, use the following Turbo commands:
|
||||
- `turbo test:unit` for unit tests
|
||||
- `turbo test:integration` for integration tests
|
||||
- `turbo test` for running all tests
|
||||
# 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
|
||||
NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
|
21
CLAUDE.md
21
CLAUDE.md
|
@ -2,6 +2,8 @@
|
|||
|
||||
This file provides guidance to Claude Code when working with code in this monorepo.
|
||||
|
||||
**Note**: Many packages and apps have their own CLAUDE.md files with specific implementation details and patterns. Always check for a local CLAUDE.md when working in a specific directory.
|
||||
|
||||
## Monorepo Structure
|
||||
|
||||
This is a pnpm-based monorepo using Turborepo with the following structure:
|
||||
|
@ -28,6 +30,7 @@ This is a pnpm-based monorepo using Turborepo with the following structure:
|
|||
- `packages/web-tools` - Web scraping and research tools
|
||||
- `packages/slack` - Standalone Slack integration (OAuth, messaging, channels)
|
||||
- `packages/supabase` - Supabase setup and configuration
|
||||
- `packages/sandbox` - Sandboxed code execution using Daytona SDK
|
||||
|
||||
## Development Workflow
|
||||
|
||||
|
@ -37,6 +40,8 @@ When writing code, follow this workflow to ensure code quality:
|
|||
- Create small, focused functions with single responsibilities
|
||||
- Design functions to be easily testable with clear inputs/outputs
|
||||
- Use dependency injection for external dependencies
|
||||
- **IMPORTANT: Write functional, composable code - avoid classes**
|
||||
- All features should be composed of testable functions
|
||||
- Follow existing patterns in the codebase
|
||||
|
||||
### 2. Build Features by Composing Functions
|
||||
|
@ -91,20 +96,26 @@ pnpm run test:watch
|
|||
```
|
||||
|
||||
### 6. Pre-Completion Checklist
|
||||
**IMPORTANT: Before finishing any task, always run:**
|
||||
**IMPORTANT: Before finishing any task or creating PRs, always run:**
|
||||
```bash
|
||||
# Run unit tests for the entire monorepo
|
||||
# 1. Run unit tests for the entire monorepo
|
||||
turbo run test:unit
|
||||
|
||||
# Run linting for the entire monorepo
|
||||
# 2. Build the entire monorepo to ensure everything compiles
|
||||
turbo run build
|
||||
|
||||
# 3. Run linting for the entire monorepo
|
||||
turbo run lint
|
||||
```
|
||||
|
||||
**Key Testing Guidelines:**
|
||||
- **Always run unit tests and lint** when working locally before considering a task complete
|
||||
- **Always run unit tests, build, and lint** when working locally before considering a task complete
|
||||
- **Unit tests** should be run for the entire monorepo to catch any breaking changes
|
||||
- **Build** must pass for the entire monorepo to ensure type safety
|
||||
- **Integration tests** should only be run for specific packages/features you're working on (NOT the entire monorepo)
|
||||
- **Fix all failing tests and lint errors** before completing any task
|
||||
- **Fix all failing tests, build errors, and lint errors** before completing any task
|
||||
- **Heavily bias toward unit tests** - they are faster and cheaper to run
|
||||
- **Mock everything you can** in unit tests for isolation and speed
|
||||
|
||||
## Code Quality Standards
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { chats, db, slackIntegrations } from '@buster/database';
|
||||
import { chats, db, getSecretByName, slackIntegrations } from '@buster/database';
|
||||
import type { SlackEventsResponse } from '@buster/server-shared/slack';
|
||||
import { type SlackWebhookPayload, isEventCallback } from '@buster/slack';
|
||||
import { type SlackWebhookPayload, addReaction, isEventCallback } from '@buster/slack';
|
||||
import { tasks } from '@trigger.dev/sdk';
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
import type { Context } from 'hono';
|
||||
|
@ -188,6 +188,53 @@ export async function eventsHandler(payload: SlackWebhookPayload): Promise<Slack
|
|||
// Extract thread timestamp - if no thread_ts, this is a new thread so use ts
|
||||
const threadTs = event.thread_ts || event.ts;
|
||||
|
||||
// Add hourglass reaction immediately after authentication
|
||||
if (organizationId) {
|
||||
try {
|
||||
// Fetch Slack integration to get token vault key
|
||||
const slackIntegration = await db
|
||||
.select({
|
||||
tokenVaultKey: slackIntegrations.tokenVaultKey,
|
||||
})
|
||||
.from(slackIntegrations)
|
||||
.where(
|
||||
and(
|
||||
eq(slackIntegrations.organizationId, organizationId),
|
||||
eq(slackIntegrations.teamId, payload.team_id),
|
||||
eq(slackIntegrations.status, 'active')
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (slackIntegration.length > 0 && slackIntegration[0]?.tokenVaultKey) {
|
||||
// Get the access token from vault
|
||||
const vaultSecret = await getSecretByName(slackIntegration[0].tokenVaultKey);
|
||||
|
||||
if (vaultSecret?.secret) {
|
||||
// Add the hourglass reaction
|
||||
await addReaction({
|
||||
accessToken: vaultSecret.secret,
|
||||
channelId: event.channel,
|
||||
messageTs: event.ts,
|
||||
emoji: 'hourglass_flowing_sand',
|
||||
});
|
||||
|
||||
console.info('Added hourglass reaction to app mention', {
|
||||
channel: event.channel,
|
||||
messageTs: event.ts,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Log but don't fail the entire process if reaction fails
|
||||
console.warn('Failed to add hourglass reaction', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
channel: event.channel,
|
||||
messageTs: event.ts,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Find or create chat
|
||||
const chatId = await findOrCreateSlackChat({
|
||||
threadTs,
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
SlackMessagingService,
|
||||
addReaction,
|
||||
convertMarkdownToSlack,
|
||||
getReactions,
|
||||
getThreadMessages,
|
||||
removeReaction,
|
||||
} from '@buster/slack';
|
||||
|
@ -102,9 +101,7 @@ export const slackAgentTask: ReturnType<
|
|||
botUserId: integration.botUserId,
|
||||
});
|
||||
|
||||
// Step 3: We'll add reactions after we fetch messages and find the mention
|
||||
|
||||
// Step 4: Fetch all needed data concurrently
|
||||
// Step 3: Fetch all needed data concurrently
|
||||
const [slackMessages] = await Promise.all([
|
||||
getThreadMessages({
|
||||
accessToken,
|
||||
|
@ -141,59 +138,6 @@ export const slackAgentTask: ReturnType<
|
|||
throw new Error('No @Buster mention found in the thread');
|
||||
}
|
||||
|
||||
// Step 5: Add hourglass reaction to the message that mentioned @Buster
|
||||
try {
|
||||
// First, get existing reactions to see if we need to clean up
|
||||
const existingReactions = await getReactions({
|
||||
accessToken,
|
||||
channelId: chatDetails.slackChannelId,
|
||||
messageTs: mentionMessageTs,
|
||||
});
|
||||
|
||||
// Remove any existing reactions from the bot
|
||||
if (integration.botUserId && existingReactions.length > 0) {
|
||||
const botUserId = integration.botUserId;
|
||||
const botReactions = existingReactions.filter((reaction) =>
|
||||
reaction.users.includes(botUserId)
|
||||
);
|
||||
|
||||
for (const reaction of botReactions) {
|
||||
try {
|
||||
await removeReaction({
|
||||
accessToken,
|
||||
channelId: chatDetails.slackChannelId,
|
||||
messageTs: mentionMessageTs,
|
||||
emoji: reaction.name,
|
||||
});
|
||||
logger.log('Removed existing bot reaction', { emoji: reaction.name });
|
||||
} catch (error) {
|
||||
// Log but don't fail if we can't remove a reaction
|
||||
logger.warn('Failed to remove bot reaction', {
|
||||
emoji: reaction.name,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the hourglass reaction
|
||||
await addReaction({
|
||||
accessToken,
|
||||
channelId: chatDetails.slackChannelId,
|
||||
messageTs: mentionMessageTs,
|
||||
emoji: 'hourglass_flowing_sand',
|
||||
});
|
||||
|
||||
logger.log('Added hourglass reaction to message with @Buster mention', {
|
||||
messageTs: mentionMessageTs,
|
||||
});
|
||||
} catch (error) {
|
||||
// Log but don't fail the entire task if reaction handling fails
|
||||
logger.warn('Failed to manage Slack reactions', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
|
||||
// Find all bot messages in the thread to determine if this is a follow-up
|
||||
const previousBotMessages = slackMessages.filter(
|
||||
(msg) => msg.user === integration.botUserId && msg.ts < mentionMessageTs
|
||||
|
@ -300,7 +244,7 @@ export const slackAgentTask: ReturnType<
|
|||
prompt = `Please fulfill the request from this slack conversation:\n${formattedMessages}`;
|
||||
}
|
||||
|
||||
// Step 6: Create message
|
||||
// Step 4: Create message
|
||||
const message = await createMessage({
|
||||
chatId: payload.chatId,
|
||||
userId: payload.userId,
|
||||
|
@ -312,7 +256,7 @@ export const slackAgentTask: ReturnType<
|
|||
messageId: message.id,
|
||||
});
|
||||
|
||||
// Step 7: Trigger analyst agent task (without waiting)
|
||||
// Step 5: Trigger analyst agent task (without waiting)
|
||||
logger.log('Triggering analyst agent task', {
|
||||
messageId: message.id,
|
||||
});
|
||||
|
@ -325,7 +269,7 @@ export const slackAgentTask: ReturnType<
|
|||
runId: analystHandle.id,
|
||||
});
|
||||
|
||||
// Step 8: Send initial Slack message immediately after triggering
|
||||
// Step 6: Send initial Slack message immediately after triggering
|
||||
const messagingService = new SlackMessagingService();
|
||||
const busterUrl = process.env.BUSTER_URL || 'https://platform.buster.so';
|
||||
let progressMessageTs: string | undefined;
|
||||
|
@ -376,7 +320,7 @@ export const slackAgentTask: ReturnType<
|
|||
});
|
||||
}
|
||||
|
||||
// Step 9: Poll for analyst task completion
|
||||
// Step 7: Poll for analyst task completion
|
||||
let isComplete = false;
|
||||
let analystResult: { ok: boolean; output?: unknown; error?: unknown } | null = null;
|
||||
const maxPollingTime = 30 * 60 * 1000; // 30 minutes
|
||||
|
@ -448,7 +392,7 @@ export const slackAgentTask: ReturnType<
|
|||
);
|
||||
}
|
||||
|
||||
// Step 10: Fetch the response message and chat details from the database
|
||||
// Step 8: Fetch the response message and chat details from the database
|
||||
let responseText = "I've finished working on your request!";
|
||||
let chatFileInfo: {
|
||||
mostRecentFileId: string | null;
|
||||
|
@ -511,7 +455,7 @@ export const slackAgentTask: ReturnType<
|
|||
});
|
||||
}
|
||||
|
||||
// Step 11: Delete the initial message and send a new one with the response
|
||||
// Step 9: Delete the initial message and send a new one with the response
|
||||
if (progressMessageTs) {
|
||||
try {
|
||||
// First, delete the initial message
|
||||
|
@ -607,7 +551,7 @@ export const slackAgentTask: ReturnType<
|
|||
});
|
||||
}
|
||||
|
||||
// Step 12: Update reactions - remove hourglass, add checkmark on the mention message
|
||||
// Step 10: Update reactions - remove hourglass, add checkmark on the mention message
|
||||
try {
|
||||
// Remove the hourglass reaction
|
||||
await removeReaction({
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
# CLAUDE.md - Web App
|
||||
|
||||
This file provides guidance for working with the Next.js web application.
|
||||
|
||||
## TypeScript & React Standards
|
||||
|
||||
### TypeScript Configuration
|
||||
- Use TypeScript for all new code
|
||||
- **Strict mode enabled** - All strict checks are on
|
||||
- **No implicit any** - Always use specific types
|
||||
- **Type imports** - ALWAYS use `import type` when importing only types to minimize build size
|
||||
- Prefer template literals over string concatenation
|
||||
|
||||
### React Patterns
|
||||
- Prefer functional components and hooks over class components
|
||||
- Use React.memo() for performance optimization when appropriate
|
||||
- Prefer async/await over .then() for asynchronous operations
|
||||
- Use proper TypeScript types for all variables and functions
|
||||
|
||||
## Component Guidelines
|
||||
|
||||
### UI Components
|
||||
- Use components from `@/components/ui` folder whenever possible
|
||||
- For custom elements, use Tailwind CSS to define component structure
|
||||
- Import `<Text>` component from `@/components/typography` for text
|
||||
- Import `<Title>` component from `@/components/typography` for titles
|
||||
|
||||
### Routing
|
||||
- When using useRouter, import from `next/navigation` (not `next/router`)
|
||||
|
||||
## Directory Structure
|
||||
|
||||
### API Directory (`src/api/`)
|
||||
```
|
||||
src/api/
|
||||
├── asset_interfaces/ # TypeScript interfaces for assets and API responses
|
||||
├── buster_rest/ # REST API client implementation
|
||||
├── buster_socket/ # WebSocket client implementation
|
||||
├── buster_socket_query/ # WebSocket + React Query integration
|
||||
├── next/ # Next.js utilities and Axios instances
|
||||
├── other/ # External API utilities (rarely used)
|
||||
├── query_keys/ # TanStack Query keys by namespace
|
||||
├── request_interfaces/ # TypeScript interfaces for request payloads
|
||||
├── createInstance.ts # Main API instance creation
|
||||
└── createServerInstance.ts # Server-side API instance
|
||||
```
|
||||
|
||||
### API Integration Patterns
|
||||
- **Type Safety**: Import response types from `asset_interfaces/` and request types from `request_interfaces/`
|
||||
- **Query Keys**: Use consistent query keys from `query_keys/` for TanStack Query
|
||||
- **WebSocket**: Real-time features use `buster_socket/` with proper namespacing
|
||||
- **State Management**: WebSocket + React Query integration via `buster_socket_query/`
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Organization
|
||||
- Place test files in the same folder as the file being tested
|
||||
- Name test files with `.test.ts` or `.test.tsx` extension
|
||||
- Example: `text.ts` → `text.test.ts` in the same directory
|
||||
|
||||
### Testing Framework
|
||||
- **Use Vitest** for all tests (NOT Jest)
|
||||
- Follow the monorepo testing guidelines from the root CLAUDE.md
|
||||
|
||||
### Testing Commands
|
||||
```bash
|
||||
# Run tests for the web app
|
||||
turbo run test --filter=@buster-app/web
|
||||
|
||||
# Run specific test file
|
||||
pnpm run test path/to/file.test.ts
|
||||
|
||||
# Watch mode for development
|
||||
pnpm run test:watch
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Code Quality
|
||||
- Follow the monorepo-wide standards from the root CLAUDE.md
|
||||
- Use functional, composable code patterns
|
||||
- Create small, focused functions with single responsibilities
|
||||
- Mock external dependencies in unit tests
|
||||
|
||||
### Performance
|
||||
- Use dynamic imports for code splitting
|
||||
- Implement proper loading states
|
||||
- Optimize images with Next.js Image component
|
||||
- Use React.memo() and useMemo() where appropriate
|
||||
|
||||
### State Management
|
||||
- Use TanStack Query for server state
|
||||
- Local state with useState/useReducer
|
||||
- Context API for cross-component state when needed
|
||||
- Maintain consistent cache keys via `query_keys/`
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### API Calls
|
||||
```typescript
|
||||
// Import types
|
||||
import type { UserResponse } from '@/api/asset_interfaces/user';
|
||||
import type { UpdateUserRequest } from '@/api/request_interfaces/user';
|
||||
|
||||
// Use REST client
|
||||
import { busterRest } from '@/api/buster_rest';
|
||||
|
||||
// Make typed API call
|
||||
const updateUser = async (data: UpdateUserRequest): Promise<UserResponse> => {
|
||||
return busterRest.user.update(data);
|
||||
};
|
||||
```
|
||||
|
||||
### WebSocket Integration
|
||||
```typescript
|
||||
// Import socket client
|
||||
import { busterSocket } from '@/api/buster_socket';
|
||||
|
||||
// Use with React Query
|
||||
import { useBusterSocketQuery } from '@/api/buster_socket_query';
|
||||
|
||||
// Subscribe to real-time updates
|
||||
const { data, isLoading } = useBusterSocketQuery({
|
||||
namespace: 'metrics',
|
||||
event: 'update',
|
||||
queryKey: ['metrics', metricId],
|
||||
});
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
- Use `NEXT_PUBLIC_` prefix for client-side variables
|
||||
- Server-only variables don't need the prefix
|
||||
- Type environment variables in `env.d.ts`
|
||||
|
||||
Remember to always check the root CLAUDE.md for monorepo-wide standards and run the pre-completion checklist before finishing any task.
|
|
@ -1 +1 @@
|
|||
/dist/*
|
||||
/dist/
|
|
@ -0,0 +1 @@
|
|||
/dist/
|
Loading…
Reference in New Issue