diff --git a/.cursor/rules/global.mdc b/.cursor/rules/global.mdc index 9d13f13d7..faee8ef68 100644 --- a/.cursor/rules/global.mdc +++ b/.cursor/rules/global.mdc @@ -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` 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; // 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 { + // 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 => { - // 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; - -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('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. \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index ff7519163..95920fcc7 100644 --- a/CLAUDE.md +++ b/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 diff --git a/apps/server/src/api/v2/slack/events.ts b/apps/server/src/api/v2/slack/events.ts index aeec38c8e..c96d53f8b 100644 --- a/apps/server/src/api/v2/slack/events.ts +++ b/apps/server/src/api/v2/slack/events.ts @@ -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 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, diff --git a/apps/trigger/src/tasks/slack-agent-task/slack-agent-task.ts b/apps/trigger/src/tasks/slack-agent-task/slack-agent-task.ts index f337e2da5..3c07ef44c 100644 --- a/apps/trigger/src/tasks/slack-agent-task/slack-agent-task.ts +++ b/apps/trigger/src/tasks/slack-agent-task/slack-agent-task.ts @@ -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({ diff --git a/apps/web/CLAUDE.md b/apps/web/CLAUDE.md new file mode 100644 index 000000000..c1038f765 --- /dev/null +++ b/apps/web/CLAUDE.md @@ -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 `` component from `@/components/typography` for text +- Import `` 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. \ No newline at end of file diff --git a/packages/rerank/.gitignore b/packages/rerank/.gitignore index 489a45f68..838458f20 100644 --- a/packages/rerank/.gitignore +++ b/packages/rerank/.gitignore @@ -1 +1 @@ -/dist/* \ No newline at end of file +/dist/ \ No newline at end of file diff --git a/packages/sandbox/.gitignore b/packages/sandbox/.gitignore new file mode 100644 index 000000000..838458f20 --- /dev/null +++ b/packages/sandbox/.gitignore @@ -0,0 +1 @@ +/dist/ \ No newline at end of file