buster/CLAUDE.md

12 KiB

CLAUDE.md

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:

Package Naming Convention:

  • All packages are scoped under @buster/{package-name} (e.g., @buster/ai, @buster/database)
  • All apps are scoped under @buster-app/{app-name} (e.g., @buster-app/web, @buster-app/server)

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
  • packages/sandbox - Sandboxed code execution using Daytona SDK

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
  • 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

  • Combine modular functions to create complete features
  • Keep business logic separate from infrastructure concerns
  • Use proper error handling at each level

3. Commit Code Frequently

  • Commit frequently - Make local commits often as you complete logical chunks of work
  • Write descriptive commit messages - Focus on what and why, not just what changed
  • Keep commits atomic - Each commit should represent one logical change
  • Use conventional commit format when possible:
    • feat: for new features
    • fix: for bug fixes
    • refactor: for code changes that neither fix bugs nor add features
    • docs: for documentation changes
    • test: for test additions or modifications
    • chore: for maintenance tasks
  • Do NOT include "Generated by Claude Code" or similar attribution in commit messages
  • Example commit messages:
    • feat: add OAuth authentication to user service
    • fix: resolve race condition in database connection pool
    • refactor: extract validation logic into separate functions

Environment Variables

This project uses a centralized environment variable system:

  1. All environment variables are defined at the root level in a single .env file
  2. Turbo passes these variables to all packages via the globalEnv configuration in turbo.json
  3. Individual packages validate their required environment variables using the shared @buster/env-utils package

Setting Up Environment Variables

  1. Copy .env.example to .env at the project root:

    cp .env.example .env
    
  2. Fill in the required values in .env

  3. All packages will automatically have access to these variables through Turbo

Adding New Environment Variables

When adding new environment variables:

  1. Add the variable to .env.example with a descriptive comment
  2. Add the variable name to the globalEnv array in turbo.json
  3. Update the package's validate-env.js script to include the new variable
  4. Update the package's env.d.ts file with the TypeScript type definition

Migrating Packages to Centralized Env

To migrate an existing package to use the centralized environment system:

  1. Remove any local .env files from the package
  2. Add @buster/env-utils as a dependency:
    "@buster/env-utils": "workspace:*"
    
  3. Update the package's scripts/validate-env.js to use the shared utilities:
    import { loadRootEnv, validateEnv } from '@buster/env-utils';
    
    loadRootEnv();
    
    const requiredEnv = {
      DATABASE_URL: process.env.DATABASE_URL,
      // ... other required variables
    };
    
    const { hasErrors } = validateEnv(requiredEnv);
    if (hasErrors) process.exit(1);
    

3. Ensure Type Safety

# Build entire monorepo to check types
turbo run build:dry-run

# Build specific package/app
turbo run build:dry-run --filter=@buster/ai
turbo run build:dry-run --filter=@buster-app/web

# Or
turbo run build

4. Run Linting & Formatting

# Run linting (checks and auto-fixes issues)
turbo run lint
turbo run lint --filter=@buster/ai

# Filter to specific package
turbo run lint --filter=@buster-app/web

5. Run Tests with Vitest

Important: Always run unit tests before completing any task to ensure code changes don't break existing functionality.

# 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

# Watch mode for development
turbo run test:watch

6. Pre-Completion Checklist

IMPORTANT: Before finishing any task or creating PRs, always run:

# 1. Run unit tests for the entire monorepo
turbo run test:unit

# 2. Build dry-run to ensure everything compiles (faster than full build)
turbo run build:dry-run

# 3. Run linting for the entire monorepo
turbo run lint

# Or run all checks simultaneously:
turbo run build:dry-run lint test:unit

Key Testing Guidelines:

  • Always run unit tests, build:dry-run, 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:dry-run 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

Filtering Commands to Specific Packages: All turbo commands support filtering to specific packages using the --filter flag:

  • turbo run test:unit --filter=@buster/ai
  • turbo run build:dry-run --filter=@buster-app/web
  • turbo run lint --filter=packages/database

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

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
  • 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

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

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

Hono API Development Guidelines

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

// Handler file (e.g., get-workspace-settings.ts)
import type { GetWorkspaceSettingsResponse } from '@buster/server-shared/security';
import type { User } from '@buster/database';

export async function getWorkspaceSettingsHandler(
  user: User
): Promise<GetWorkspaceSettingsResponse> {
  // Implementation
}

// Route definition (index.ts)
.get('/workspace-settings', async (c) => {
  const user = c.get('busterUser');
  const response = await getWorkspaceSettingsHandler(user);
  return c.json(response);
})

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

Database Operations

Query Organization

  • All database queries must be created as helper functions in @packages/database/src/queries/
  • Organize by table - Each table should have its own subdirectory (e.g., assets/, chats/, users/)
  • Type all queries - Every query function must have properly typed parameters and return types
  • Export from index - Each subdirectory should have an index.ts that exports all queries for that table
  • Reusable and composable - Write queries as small, focused functions that can be composed together

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

Test Running Guidelines

  • When running tests, use the following Turbo commands:
    • turbo run test:unit for unit tests
    • turbo run test:integration for integration tests
    • turbo run test for running all tests
    • Add --filter=<package-name> to run tests for specific packages

Database Access for Integration Testing

  • Direct database queries - You can run queries against the local database using psql with the following connection:
    DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres"
    
  • Usage example:
    psql "postgresql://postgres:postgres@127.0.0.1:54322/postgres" -c "SELECT * FROM users LIMIT 5;"
    
  • Purpose - This is primarily for writing and iterating on integration tests to verify database state and test query behavior

Pre-Completion Workflow

  • Always run turbo run test:unit, turbo run lint, and turbo run build:dry-run before making any pull request or finishing a feature, bugfix, etc. to ensure things make it through CI/CD
  • You can run all these checks simultaneously with turbo run build:dry-run lint test:unit