# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Repository Overview ByteChef is an open-source, low-code API integration and workflow automation platform built on Spring Boot. It serves as both an automation solution and an embedded iPaaS (Integration Platform as a Service) for SaaS products. ## Essential Development Commands ### Server Development All server commands should be run from the project root directory: ```bash # Build and compile the project ./gradlew clean compileJava # Run the server locally (requires Docker infrastructure) cd server docker compose -f docker-compose.dev.infra.yml up -d cd .. ./gradlew -p server/apps/server-app bootRun # Code formatting (must run before commits) ./gradlew spotlessApply # Run checks and tests ./gradlew check ./gradlew test && ./gradlew testIntegration # Generate component documentation ./gradlew generateDocumentation ``` ### Client Development Client commands should be run from the `client/` directory: ```bash # Install dependencies npm install # Development server npm run dev # Code formatting npm run format # Linting and type checking npm run lint npm run typecheck # Full check (lint + typecheck) npm run check # Build for production npm run build # Run tests npm run test ``` ### Infrastructure Setup ```bash # Start PostgreSQL, Redis, and other services cd server docker compose -f docker-compose.dev.infra.yml up -d # Or run everything in Docker docker compose -f docker-compose.dev.server.yml up -d ``` ## Architecture Overview ### Core Technology Stack - **Backend**: Java 25 with Spring Boot 4.0.7 - **Frontend**: React 19.2 with TypeScript 5.9, Vite 8, TailwindCSS 3.4 - **Database**: PostgreSQL 15+ with Liquibase migrations - **Message Broker**: Memory(default), Redis, RabbitMQ, Kafka, JMS, AMQP, AWS SQS - **Build System**: Gradle 8+ with Kotlin DSL - **Code Execution**: GraalVM Polyglot 25.0.2 (Java, JavaScript, Python, Ruby) - **Testing**: JUnit 5, Vitest 4, Testcontainers - **Node.js**: Version 24+ required for client development - **Additional Tools**: MapStruct 1.6.3, Jackson 2.19 (transitioning to 3.x — both `com.fasterxml.jackson` and `tools.jackson` packages currently appear in deps), SpringDoc OpenAPI 3.0.3 ### Main Server Module Structure #### Core Modules (`server/libs/`) - **`atlas/`** - Workflow engine core - `atlas-coordinator/` - Orchestrates workflow execution - `atlas-execution/` - Manages workflow execution lifecycle - `atlas-worker/` - Task execution workers - `atlas-configuration/` - Workflow configuration management - **`automation/`** - iPaaS automation implementation - `automation-ai/` - AI-powered automation features, including `automation-ai-mcp-*` (MCP / Model Context Protocol integration) - `automation-configuration/` - Project and workflow configuration - `automation-data-table/` - Data table management - `automation-knowledge-base/` - Knowledge base integration - `automation-search/` - Search services for automation entities - `automation-swagger/` - OpenAPI/Swagger surface for automation APIs - `automation-task/` - Task management services - `automation-workflow/` - Workflow coordination and execution - **`platform/`** - Core infrastructure services - `platform-component/` - Component definition and management - `platform-connection/` - Connection handling - `platform-workflow/` - Workflow management - `platform-scheduler/` - Scheduling services - `platform-oauth2/` - OAuth2 authentication - `platform-webhook/` - Webhook handling - `platform-ai/` - AI integration services - **`core/`** - Foundational utilities - `evaluator/` - Expression evaluation - `file-storage/` - File storage abstraction - `encryption/` - Encryption services - `message/` - Message broker abstraction #### Component System Components are located in `server/libs/modules/components/` and follow this pattern: - Each component has a `ComponentHandler` class with `@AutoService` annotation - Components define actions (operations) and triggers (event initiators) - Connection definitions handle authentication and configuration - OpenAPI specifications are often included for API-based components ### Enterprise Edition (EE) Microservices The `server/ee/` directory contains microservices for distributed deployment: **EE Code Conventions:** - Use ByteChef Enterprise license header (not Apache 2.0) for all files under `server/ee/` - Add `@version ee` Javadoc tag to all classes under `server/ee/` - `api-gateway-app/` - API Gateway with routing - `ai-gateway-app/` - AI gateway service for routing model traffic - `ai-copilot-app/` - AI Copilot service for workflow assistance - `config-server-app/` - Spring Cloud Config server - `configuration-app/` - Configuration management service - `connection-app/` - Connection management service - `coordinator-app/` - Workflow coordination service - `execution-app/` - Workflow execution service - `scheduler-app/` - Scheduling service - `webhook-app/` - Webhook handling service - `worker-app/` - Task execution workers - `runtime-job-app/` - Runtime job execution ### Available Components ByteChef includes 250+ built-in components in `server/libs/modules/components/` covering CRM, project management, communication, e-commerce, cloud storage, AI/ML, databases, and custom code execution. ## Development Patterns ### Component Development When working on components in `server/libs/modules/components/`: 1. **Component Definition Pattern**: ```java @AutoService(ComponentHandler.class) public class ExampleComponentHandler implements ComponentHandler { private static final ComponentDefinition COMPONENT_DEFINITION = component("example") .title("Example Component") .connection(CONNECTION_DEFINITION) .actions(/* actions */) .triggers(/* triggers */); } ``` 2. **Testing Pattern**: - Component tests are in `./src/test/java/com/bytechef/component/` - Running tests auto-generates `.json` definition files in `./src/test/resources/definition/` - Delete existing `.json` files AND `build/resources/test/definition/` before running tests to regenerate them 3. **Documentation**: - Component documentation goes in `./src/main/resources/README.md` - Run `./gradlew generateDocumentation` to update docs ### Code Quality Requirements - **Spotless**: Code formatting is enforced. Run `./gradlew spotlessApply` before commits - **Checkstyle, PMD, SpotBugs**: Static analysis tools are configured - **Tests**: All new code should include appropriate tests - **Documentation**: Update component documentation when adding features ### File Structure Guidelines - Configuration files are in `server/libs/config/` - SDK components are in `sdks/backend/java/` - CLI tools are in `cli/` - Documentation source is in `docs/` ## Code Style and Best Practices ### Client ESLint sort-keys Rule - Object keys must be in natural ascending (alphabetical) order in client code - Applies to mock objects, hoisted state, test data, and component props - ESLint `--fix` does NOT auto-fix sort-keys - must be fixed manually - Example: `{content: 'x', id: 'y'}` not `{id: 'y', content: 'x'}` ### Client Interface Naming Convention - Interface names must end with `I` or `Props` (enforced by `@typescript-eslint/naming-convention`) - Example: `EnvironmentConfigI`, `BadgePropsType` — not `EnvironmentConfig` ### Client Import Destructure Sort Order - Named imports must be sorted alphabetically within `{}` (enforced by `bytechef/sort-import-destructures`) - `type` keyword imports sort by their name, not grouped separately - Example: `import {BoxIcon, CheckIcon, type LucideIcon, WrenchIcon} from 'lucide-react'` ### Non-null Assertion on Optional Chain (Client) - `@typescript-eslint/no-non-null-asserted-optional-chain` forbids `obj?.prop!` - Instead, filter nulls first, then assert: `.filter((item) => item?.id != null).map((item) => { const id = item!.id!; ... })` ### Ref Name Suffix (Client) - `useRef` variables must end with `Ref` suffix (enforced by `bytechef/ref-name-suffix`) — e.g., `fileInputRef`, `totalToUploadRef` ### Variable Naming - Do not use short or cryptic variable names on both the server and client sides; prefer clear, descriptive names that communicate intent. - Do not prefix private methods with `_` — use plain method names (e.g., `extractFrontmatter` not `_extractFrontmatter`) - This applies everywhere, including arrow function parameters and loop variables. - Examples: ```typescript // Bad const current = users.find((u) => u?.login === login); // Good const current = users.find((user) => user?.login === login); ``` ```java // Bad for (Order o : orders) { ... } // Good for (Order order : orders) { ... } ``` ### Lucide Icon Imports (Client) - Always import icons with the `Icon` suffix: `SearchIcon`, `DatabaseIcon`, `Loader2Icon` - Not: `Search`, `Database`, `Loader2` ### CSS Class Merging (Client) - Use `twMerge` from `tailwind-merge` for conditional class merging - Do not use `cn()` utility ### React Patterns (Client) - Use `fieldset` (with `border-0`) for semantic form grouping instead of `div` - Use `useMemo` for computed values instead of IIFEs in JSX - Prefer `||` over `??` for JSX fallbacks (e.g., `trigger || defaultTrigger`) ### React Hook Ordering (Client) - Order hooks in components/custom hooks: `useState` → `useRef` → custom store hooks → other custom hooks → derived values/`useMemo`/`useCallback` → `useEffect` → `return` - All `useEffect` calls go last, immediately before the `return` statement - Group multiple declarations of the same hook type consecutively (e.g., all `useRef` calls together, then `.current` assignments in a separate block) ### Graph dispatcher canvas (Client) A `graph/v1` dispatcher renders as a free-form `graphFrame` box, not as a chain. `layoutGraphFrames` is a PRE-PASS: it lays each frame's members out first, hands the outer engine one sized leaf node, and re-appends members and their routes afterwards - so nothing the outer layout does can disturb them, and a nested graph must be processed innermost-first. Member positions are FRAME-relative (`metadata.ui.nodePosition`); `toFrameChildPosition`/`fromFrameChildPosition` in `graphFrameGeometry` are the only sanctioned crossing to canvas coordinates - open-coding the header offset flings the group. Routing is an explicit `parameters.transitions: [{from, to, condition?}]` list, never a per-node `next`: declaration order within a `from` IS conditional priority, so a transition's index is its identity. `graphTransition`/`graphStart` edges are free-form routes, not chain links - a cyclic pair would corrupt dagre's and ELK's ranking, so both engines strip them, and chain walkers (`collectChainSuccessorNodes`) must skip them. `onConnect` is scoped by handle suffix (`resolveGraphConnection`): only graph transition handles connect, and only within one graph. ### Client Error Handling - `useFetchInterceptor.ts` provides centralized error handling for all fetch requests including GraphQL - GraphQL errors are automatically parsed and displayed as toast notifications - Individual `onError` handlers on mutations are therefore not necessary for basic error display - Only add per-mutation `onError` if you need custom behavior beyond the global toast (e.g., resetting form state) ### GraphQL Conventions - Enum values must use SCREAMING_SNAKE_CASE (e.g., `DELETE`, `GET`, `QUERY`, `PATH`) - Consistent with HttpMethod and other enums in `*.graphqls` files ### ID Generation - Avoid `hashCode()` for generating unique identifiers (collision risk) - Prefer SHA-256 with first 8 bytes for deterministic long IDs, or UUID for true uniqueness ### Blank Line Before Control Statements (Java) - Insert exactly one empty line before control statements to improve visual separation of logic: - Applies to: `if`, `else if`, `else`, `for`, enhanced `for`, `while`, `do { ... } while (...)`, `switch`, `try`/`catch`/`finally`. - Exceptions (do not add a blank line): - At the very start of a file, class, or method/block (immediately after an opening `{`). - When the control keyword directly continues the previous block on the same line (e.g., `} else {`, `} catch (...) {`, `} finally {`). - Immediately after another required blank line (avoid double blank lines). - Very short top-of-method guard clauses may omit the blank line for brevity when they appear immediately after the method signature. - If the automatic formatter (Spotless/Google Java Format) enforces a different layout, the formatter’s output wins. - Example: ```java void process(User user) { if (user == null) { return; } for (Order order : user.getOrders()) { // ... } try { doWork(); } catch (IOException e) { handle(e); } } ``` ### Blank Line After Variable Modification (Java) - Insert exactly one empty line between a variable modification and a subsequent statement that uses that variable - This improves readability by visually separating the setup from the usage - Example: ```java // Bad document.setStatus(KnowledgeBaseDocument.STATUS_PROCESSING); knowledgeBaseDocumentService.saveKnowledgeBaseDocument(document); // Good document.setStatus(KnowledgeBaseDocument.STATUS_PROCESSING); knowledgeBaseDocumentService.saveKnowledgeBaseDocument(document); ``` ### No Trailing Blank Line in Class Body (Java) - Do not add an empty line between the last method (or field) and the closing `}` of a class ### Method Chaining - Do not chain method calls except where this is natural and idiomatic - Allowed exceptions (non-exhaustive): - Builder patterns (including Lombok `@Builder`) - Java Stream API (`stream()`, `map()`, `filter()`, `collect()`) - `Optional` - Query DSLs and criteria builders: - Spring Data JPA `Specification` (`where(...).and(...).or(...)`) - JPA Criteria API (fluent `CriteriaBuilder`/`Predicate` construction) - QueryDSL (`JPAQueryFactory.select(...).from(...).where(...).orderBy(...)`) - jOOQ (`dsl.select(...).from(...).where(...).orderBy(...)`) - Reactive operators: Project Reactor `Mono`/`Flux` (e.g., `map`, `flatMap`, `filter`, `onErrorResume`) - HTTP client builder/request DSLs: Spring `WebClient`, OkHttp - Testing/assertion DSLs: AssertJ, Mockito BDD APIs - Logging fluent APIs: SLF4J 2.x `log.atXxx()` fluent logger - JSON builders and similar fluent APIs: Jackson `ObjectNode`/`ArrayNode`, JSON‑P `JsonObjectBuilder` - Formatting rules: - Break each chained step onto its own line for readability when there are 3+ operations or lines exceed the limit - Keep declarative chains (queries, reactive pipelines) as one logical block; prefer one operation per line - Avoid chaining when side effects are involved or intermediate values deserve names for clarity/debugging ### Temporal Dead Zone (TDZ) with Synchronous Callbacks - `const x = fn(callback)` — if `fn` calls `callback` synchronously, `x` is not yet assigned inside `callback` - Accessing `x` inside such a callback throws `ReferenceError: Cannot access 'x' before initialization` - Fix: defer access to `x` via `setTimeout` or store in a mutable ref before the call ### Code Quality Tool Patterns **SpotBugs**: - Don't use rough approximations of known constants (e.g., use `Math.PI` instead of `3.14`) - Always check return values of methods like `CountDownLatch.await(long, TimeUnit)` - returns boolean - Use try-with-resources for `Connection` objects to avoid resource leaks - Catch specific exceptions (`SQLException`) instead of generic `Exception` when possible **PMD**: - Use `@SuppressWarnings("PMD.UnusedFormalParameter")` for interface-required but unused parameters - Don't qualify static method calls with the class name when already inside that class (e.g., `builder()` not `ClassName.builder()`) **Checkstyle**: - Test method names must be camelCase without underscores (e.g., `testExecuteSuccess` not `testExecute_Success`) - Naming rule applies to ALL methods in test sources (including private helpers), not just `@Test` methods - Empty blocks are forbidden — a comment alone doesn't satisfy the `EmptyBlock` rule; add an executable statement - `TODO:` comments are forbidden (`TodoComment` rule) — rewrite as plain comments describing intent, or implement the work ### Agent HITL approvals (chat cards + tool gate) Two separate primitives: **Approval** (a decision with a boolean `approved`; a comment is valid on BOTH outcomes) and **AskUserQuestion** (clarification for the LLM). Typing in chat NEVER resolves an approval. Delivery goes out best-effort over 15 `APPROVAL_CHANNELS` elements: chat, Slack, Discord, Telegram, Mattermost, Rocket.Chat, Gmail, Outlook 365, SMTP email, WhatsApp via Meta/Twilio/Infobip, SMS via Twilio/Infobip, and approval task. The step fails only when every configured channel fails. Besides the chat card, five resolve in place through a signed interactivity callback: Slack, Discord, Telegram, WhatsApp (Meta) and Mattermost. The gate is the `aiAgentUtils/v1/approvalGate` cluster element. It wraps every tool nested in it in `ApprovalGateToolCallback`, which suspends via the sentinel protocol. On resume, an approved call runs the original arguments, and any calls the suspend deferred are issued again (`DeferredToolCallReplay`). A gate inside a gate, `requestApproval` inside a gate, and either of them under a subagent are rejected both by `WorkflowValidator` and at run time. The resume contract: - An approval resumes only with a signed token and a boolean `approved`. - A GET of an approval resume link, or a HEAD of any resume link, answers 405. - Restarting a run that waits on an approval answers 409, and stop cancels the approval. An expired approval FAILS the run, and its links answer 410. A synchronous run cannot pause: a workflow with a step that could pause it, an approval gate included, is rejected before it starts. Expiry runs a 15-minute platform-coordinator sweep, acting only on approval suspends. There are no approval reminders or escalation. Never add notification logic under `server/libs/atlas/`. See `.agents/hitl-approvals.md`. ### Spring Boot Project Conventions - **Integration Test Naming**: All integration test classes must end with "IntTest" suffix (e.g., `WorkflowFacadeIntTest.java`) - **A new task dispatcher must be registered in four places, not one.** Its own `@Bean`s cover the production coordinator, which autowires `List` - but `WorkflowTestConfiguration` (the editor's Test button), `WebhookConfiguration` and `WorkflowSubflowSyncExecutorConfiguration` (synchronous subflows) each build their chain from a literal `List.of(...)`. An unregistered type matches no resolver, falls through to the worker, and surfaces as `Component definition with name '' ... not found`. Adding the Gradle dependency is not enough - `WorkflowTestConfigurationTest`, `WebhookConfigurationTest` and `WorkflowSubflowSyncExecutorConfigurationTest` scan `com.bytechef.task.dispatcher` and fail when a dispatcher or completion handler on the classpath is missing from their list. - **Spring 7 Programmatic Bean Registration**: - Use `BeanRegistrar` + `@Import` instead of `BeanFactoryPostProcessor` for programmatic bean registration - Resolve collection dependencies via `context.beanProvider(Class).orderedStream().toList()` (replaces `beanFactory.getBeansOfType()`) - Resolve named beans via `context.bean("beanName", Class)` in supplier - Test `BeanRegistrar` specs by capturing `Consumer>` with `ArgumentCaptor`, applying to mock `Spec`, and verifying fluent calls - **Five executors, no queues.** `AsyncConfiguration` defines `taskExecutor` (default `@Async`, limit `bytechef.async.concurrency-limit`), `workerExecutor` (task handlers + trigger executions, limit `bytechef.worker.task.subscriptions.default`), `syncWorkerExecutor` (the API-Platform sync path and the editor's Test button, limit `bytechef.worker.task.sync-concurrency-limit`), `messageEventExecutor` (Spring-event→broker bridge, unbounded) and `webhookDeliveryExecutor` (outbound webhook POSTs: job status and task started webhooks, notification webhooks, Slack; unbounded). The sync lane is a bulkhead, not extra capacity: without it a burst of scheduled runs saturates the worker lane and an interactive caller blocks on a permit while holding a request thread. All are queue-less `SimpleAsyncTaskExecutor`s: a submitter waits for a permit. Anything that runs component code or blocks on I/O goes on `workerExecutor`, never the default. Work handed off by a thread that must not wait on a remote endpoint goes on an unbounded lane and bounds itself inside the task, as `WebhookDeliveryService` does with `bytechef.webhook-delivery.concurrency-limit` (one global permit per POST) and `max-pending-deliveries` (per endpoint); each endpoint receives one delivery at a time, so a slow endpoint cannot take every permit. On shutdown the executor interrupts its tasks and waits up to 10 s. On virtual threads the interrupt closes a blocked POST's socket; on platform threads `SimpleClientHttpRequestFactory` does not respond to it, so a POST blocked in its read (up to 30 s) is abandoned when the JVM exits. Virtual threads change only the thread factory — they do not lift a limit, which is why these are not `ThreadPoolTaskExecutor`s. The bridge stays unbounded because it only hands messages to the broker: starving it stalls every workflow, while the real work is already bounded at `workerExecutor` (and DB connections at Hikari's `maximum-pool-size`). Don't bound `AsyncMessageBroker`'s own dispatch executor either — it carries work-initiating and completion events together, and each `JobSyncExecutor` builds a private instance whose latch waits on events from it, so a bound there can wedge the API-Platform sync path and the editor's Test button. Its per-route ordered executors (single thread, bounded queue, blocking put) are a separate, deliberate case and stay bounded. `AsyncConfigurationTest` enumerates them, so it catches the decorator being dropped from `createExecutor` — a sixth executor built outside that factory isn't covered; every executor must go through `createExecutor`. The one deliberate exception is `WorkflowSubflowSyncExecutorConfiguration`, which builds its own unbounded executor: subflows nest through a `SubflowTaskDispatcher`, so a parent holds a permit while its child needs one, and a shared bounded lane would deadlock past the nesting depth. ## Access and Authentication ### Development Login Credentials - **Admin**: admin@localhost.com / admin - **User**: user@localhost.com / user ### Default Ports - **Server**: 8080 (main application) - **API**: 9555 (backend API server) - **Client**: 3000 (development server) - **PostgreSQL**: 5432 - **Redis**: 6379 - **Mailhog**: 1025 ## Common Development Workflows ### Adding a New Component 1. Create component directory in `server/libs/modules/components/` 2. Add component to `settings.gradle.kts` 3. Implement `ComponentHandler` with actions/triggers 4. Add tests and run to generate JSON definition 5. Add documentation in README.md 6. Run `./gradlew generateDocumentation` ### Working with Workflows - Workflows are defined in JSON format - Visual editor is available in the client application - Workflow execution is handled by the Atlas engine - Test workflows through the UI or API endpoints ### Database Changes - Use Liquibase for schema migrations - Migration files are in `server/libs/config/liquibase-config/` - Database changes are applied automatically on startup - After renaming migration files, delete stale copies from `build/resources/` — Liquibase sees both old and new on classpath ### New Spring Data JDBC Modules - Create `@AutoConfiguration` class with `@EnableJdbcRepositories(basePackages = "...")` + `@ConditionalOnBean(AbstractJdbcConfiguration.class)` - Register in `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` - Add `spring-boot-autoconfigure` dependency to `build.gradle.kts` ### Agent Component Handlers - `@AutoService(ComponentHandler.class)` — ServiceLoader discovery, no Spring DI available - `@Component("name_v1_ComponentHandler")` — Spring discovery, supports constructor injection (used by guardrails, RAG, chat-memory, and agent utils when Spring beans are needed) ### Lazy Component Loading & Build-time Index - `ComponentDefinitionRegistry` loads NO ServiceLoader components at Spring startup — all registry beans are `lazyInit`, and the registry constructor only captures suppliers - server-app's `generateComponentIndex` Gradle task writes `META-INF/bytechef/component-index.json` at build time; the components-list view is served from index stubs (zero handlers loaded), single components load on demand via recorded provider class names - **When an index is present it is authoritative**: a ServiceLoader component missing from the index is invisible (both in the list and per-name resolution). Apps that assemble their classpath differently (EE apps) must either run the generator or ship without an index — absent/corrupt index falls back transparently to full loading on first registry access - With an index, a by-name read loads only that component, while the no-arg `getComponentDefinitions()` and the first task/trigger handler resolution each trigger their own one-time full load (the registry loads from the index's providers; the task and trigger handler providers share a separate `ServiceLoader` sweep); stubs never reach detail/execution paths ### OpenAPI Development Workflow - Whenever you touch an `openapi.yaml` (by any means, including scripts or `sed`), immediately run `./gradlew -p generateOpenAPI` — full rules in `.claude/rules/openapi.md` ### GraphQL Development Workflow - Add schema path to `client/codegen.ts` `schema` array - Create operation `.graphql` files in `client/src/graphql//` - Run `cd client && npx graphql-codegen` to regenerate `src/shared/middleware/graphql.ts` - Commit operations and generated file separately ### Running Integration Tests Integration tests use Testcontainers to spin up real services: ```bash # Run all integration tests ./gradlew testIntegration # Run integration tests for a specific module ./gradlew :server:libs:platform:platform-workflow:platform-workflow-service:testIntegration # Run with specific Docker image versions (recommended) # Integration tests automatically use Testcontainers with PostgreSQL 15 ``` ### Debugging Workflows 1. **Enable Debug Logging**: Set logging level to DEBUG for specific packages in `application.yml` 2. **Use Workflow Test Mode**: Test workflows in the UI with step-by-step execution 3. **Check Execution Logs**: View workflow execution logs in the UI or database 4. **Inspect Variables**: Use the workflow editor to inspect variable values at each step 5. **Test Actions Individually**: Use the component test feature to test individual actions ### Commit Message Convention - Client-side changes: ` client - ` - Server-side changes: ` ` - Example client: `2898 client - Add EnvironmentSelect dropdown to automation page headers` - Example server: `2898 Add environment selection endpoint` ### Code Quality Workflow - When committing, only stage files directly modified by the current task — do not include pre-existing unstaged changes that are unrelated Before committing code, ensure: ```bash # Server-side ./gradlew spotlessApply # Format code ./gradlew check # Run all checks # Client-side cd client npm run format # Format code npm run check # Run lint, typecheck, and tests ``` ### Creating Custom Components via CLI ByteChef includes a CLI tool for scaffolding components: ```bash cd cli ./gradlew :cli-app:bootRun --args="component init openapi --name=my-component --openapi-path=/path/to/openapi.yaml" ``` ### Resolving PR Review Comments - Use `gh api graphql` with `resolveReviewThread` mutation to close threads programmatically - Get thread IDs via: `gh api graphql -f query='{ repository(owner: "X", name: "Y") { pullRequest(number: N) { reviewThreads(first: 20) { nodes { id isResolved path } } } }'` ## Build and Deployment ### Docker - `Dockerfile` for server application - `docker-compose.yml` for full stack - `docker-compose.dev.infra.yml` for development infrastructure - `docker-compose.dev.server.yml` for server-only development ### Kubernetes - Helm charts are in `kubernetes/helm/bytechef-monolith/` - Supports both monolith and microservices deployments ### CI/CD - GitHub Actions workflows for build and test - Automated component documentation generation - Code quality checks are enforced ## Testing Strategies ### Unit Testing - Write unit tests for all business logic in service classes - Mock external dependencies using Mockito - Test component actions and triggers in isolation - Aim for high code coverage (target: 80%+) - Unit test class names must end with `Test` suffix only (NOT `IntTest`) — e.g., `KnowledgeBaseFileStorageTest` - Drop `Impl` from test class names — test the interface contract, not the implementation detail ### Integration Testing - All integration test classes must end with `IntTest` suffix - Use `@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)` - Leverage Testcontainers for real service dependencies - Test configuration: `src/test/resources/config/application-testint.yml` - Example integration test structure: ```java @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("testint") class WorkflowServiceIntTest { @Autowired private WorkflowService workflowService; @Test void testWorkflowExecution() { // Test implementation } } ``` ### Component Testing - Component tests auto-generate JSON definition files in `src/test/resources/definition/` - Delete existing `.json` files AND `build/resources/test/definition/` before running tests to regenerate them (classpath serves from build output) - Test both actions and triggers - Verify connection configurations - Test error handling and edge cases ### Test ObjectMapper Setup - Use `@ExtendWith(ObjectMapperSetupExtension.class)` for tests that use `JsonUtils`, `MapUtils`, or `ConvertUtils` — do NOT manually call `setObjectMapper()` in test configurations ### Task Dispatcher Definition Snapshot Tests - `DefinitionFactoryTest` classes use `JsonFileAssert` (snapshot pattern): if the JSON file is missing, it's auto-generated; if present, it's compared - When task dispatcher definition models change (new fields), delete snapshot JSON files from BOTH `src/test/resources/definition/` and `build/resources/test/definition/`, then rerun tests ### EE Microservice Remote Client Pattern - EE apps (`server/ee/apps/`) use remote client stubs instead of local service implementations - When adding new SPI interfaces to platform modules, create corresponding `@Component @ConditionalOnEEVersion` stub classes in the relevant `*-remote-client` module (e.g., `automation-configuration-remote-client`) - Stubs throw `UnsupportedOperationException` — they satisfy Spring DI; actual work is done via REST calls - `@ConditionalOnEEVersion` requires `bytechef.edition=ee` in the app's config - For lightweight EE apps (e.g., `runtime-job-app`) that can't pull in full remote client modules, use `@TestConfiguration` with mock/stub beans in the integration test ### Component Integration Test Configuration - Component integration tests use `@ComponentIntTest` → `ComponentTestIntConfiguration` in `platform-component-test-int-support` - `ComponentTestIntConfiguration` only scans `com.bytechef.platform.component` — beans from other packages (e.g., `com.bytechef.file.storage`) must be manually registered - `Base64FileStorageService.getType()` returns `"JDBC"`, so test property `bytechef.file-storage.provider=jdbc` must be set to match ### Client-Side Testing ```bash cd client # Run tests in watch mode during development npm run test:watch # Run tests with coverage npm run test:coverage # Run all quality checks (includes tests) npm run check ``` #### Zustand Store Testing - Reset store state in `beforeEach` via `store.setState({...initial...})` — avoids cross-test leakage - Access store imperatively via `store.getState()` for assertions (no hook needed) - Export stores (e.g., `export const featureFlagsStore`) to enable direct state manipulation in tests - Use `renderHook` from `@testing-library/react` for hooks that wrap stores - **Never flush async store updates with a fixed sleep** (`await act(async () => { await new Promise(r => setTimeout(r, 10)); })`). A fixed delay races an async chain whose latency is unbounded under parallel vitest workers: it passes in isolation and fails intermittently in full runs, on a different test each time. Wait for the condition instead: `await waitFor(() => { expect(store.getState().someFlag).toBe(expected); }, {interval: 5, timeout: 5000})`. The timeout is a ceiling paid only on genuine failure, not a delay every test waits out. - A test that kicks off an async store update **must wait for it to settle before it ends**, even when the assertion only covers intermediate state — otherwise its pending `setTimeout` callbacks fire during a later test and mutate the shared store there (`beforeEach` reset cannot help; the timer outlives the reset). #### PostHog Mock - Global mock in `.vitest/setup.ts` — `onFeatureFlags: vi.fn()`, `isFeatureEnabled: vi.fn().mockReturnValue(false)` - `onFeatureFlags` returns `() => void` (unsubscribe); mock overrides must return a function: `return () => {}` - `import('posthog-js')` dynamic imports resolve to the same mock; multiple synchronous calls share one Promise - `useFeatureFlagsStore` resolves a flag through a four-hop chain: `import('posthog-js')` → promise microtask → `onFeatureFlags` callback → `setTimeout(…, 0)`. Tests must wait on the resulting store state (or on the `onFeatureFlags` subscription), never on elapsed time — see the Zustand note above. - Test files are isolated (vitest `forks` pool, `isolate: true`), so `featureFlagsStore` and the PostHog mock **cannot** leak between files; contamination is always within a single file. ### End-to-End Testing - Test complete workflows through the UI - Verify trigger activation and workflow execution - Test with real connections to external services (in staging) - Validate data transformations and error handling ### CI/CD Testing GitHub Actions workflows automatically run: - Server tests: `./gradlew check jacocoTestReport sonar` - Client tests: `npm run check` (lint + typecheck + tests) - SonarCloud analysis for code quality - Integration tests with Testcontainers ## Debugging Tips ### Server-Side Debugging #### Enable Debug Logging Edit `application.yml` or set environment variables: ```yaml logging: level: com.bytechef: DEBUG com.bytechef.platform.workflow: TRACE org.springframework.web: DEBUG ``` #### Remote Debugging Start the server with debug enabled: ```bash ./gradlew -p server/apps/server-app bootRun --debug-jvm # Connects on port 5005 by default ``` In IntelliJ IDEA: 1. Run → Edit Configurations 2. Add New Configuration → Remote JVM Debug 3. Set port to 5005 4. Start debugging #### Common Debugging Scenarios **Workflow Execution Issues:** - Check `atlas-execution` logs for execution details - Inspect workflow JSON definition for syntax errors - Verify component connections are properly configured - Check if triggers are enabled and properly configured **Component Action Failures:** - Enable DEBUG logging for `com.bytechef.component` - Verify input parameters match action definitions - Check connection credentials and permissions - Review component-specific logs in the execution logs **Database Issues:** - Check Liquibase changelog execution: `SELECT * FROM databasechangelog` - Verify connection pool settings if seeing connection timeouts - Check for transaction rollbacks in logs - Use `spring.jpa.show-sql=true` to see SQL queries (dev only) **Authentication/Authorization:** - Check JWT token validity and expiration - Verify user roles and permissions in database - Review Spring Security filter chain execution - Check CORS configuration for client-server communication ## Troubleshooting **Sudden or strange `./gradlew` failures** - Before reporting or investigating a sudden, unexpected `./gradlew` failure (e.g. `NoClassDefFoundError`, `zip file closed`, errors in code nobody changed), run `./gradlew --stop` and re-run the same command - A long-lived Gradle daemon keeps state in memory between builds (e.g. Spotless caches formatter classloaders), and that state can go stale; `--stop` throws it away (it does not clear `~/.gradle/caches`) - Only report or investigate the failure if it still happens in a fresh daemon. The full error output of a daemon build is in `~/.gradle/daemon//daemon-*.out.log` **Database schema issues** - Reset database: `docker compose -f server/docker-compose.dev.infra.yml down -v` - Check Liquibase logs for migration errors - Manually run migrations: `./gradlew liquibaseUpdate` **Workflow execution failures** - Check Atlas worker logs for task execution errors - Verify component connections are active - Check Redis connectivity for message broker - Review component-specific documentation for required parameters **Integration test failures** - Ensure Docker is running (required for Testcontainers) - Review test logs in `build/test-results/` ## Agent skills ### Issue tracker Issues and PRDs are tracked on GitHub at `bytechefhq/bytechef` (pinned explicitly because this clone has multiple remotes). See `docs/agents/issue-tracker.md`. ### Triage labels Five canonical triage roles mapped to this repo's labels — `needs triage` and `wontfix` reuse existing labels; `needs-info` / `ready-for-agent` / `ready-for-human` are new. See `docs/agents/triage-labels.md`. ### Domain docs Single-context: one `CONTEXT.md` + `docs/adr/` at the repo root. See `docs/agents/domain.md`.