.claude/skills/ecotone-contributor/SKILL.md
Guides Ecotone framework contributions: dev environment setup, monorepo navigation, running tests, PR workflow, and package split mechanics. TRIGGER whenever any code change is made to the Ecotone codebase — new features, bug fixes, refactors, or any modification to source files. Also use when setting up development environment, preparing PRs, validating changes, running tests across packages, or understanding the monorepo structure.
npx skillsauth add ecotoneframework/ecotone-dev ecotone-contributorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Tests use inline anonymous classes with PHP 8.1+ attributes, snake_case method names, and high-level behavioral assertions. Use this skill when writing or debugging any Ecotone test.
Every new feature or bug fix MUST start with a test written using Ecotone Lite (EcotoneLite::bootstrapFlowTesting() or EcotoneLite::bootstrapForTesting()). This is a blocking requirement — do not write production code before the test exists.
Tests must follow the userland perspective. Each test should read like an example of how an Ecotone end-user would use the feature in their application. The test sets up real handler classes (inline anonymous classes with attributes), sends commands/events/queries through the bus, and asserts on the outcome — exactly as a user would interact with the framework. Never test framework internals directly; instead, demonstrate the feature through its public API as seen by the user.
Workflow:
Example test-first skeleton (userland perspective with inline classes):
public function test_placing_order_publishes_event(): void
{
$ecotoneLite = EcotoneLite::bootstrapFlowTesting(
[new class {
private array $products = [];
#[CommandHandler]
public function placeOrder(#[Header('orderId')] string $orderId, #[Header('product')] string $product): void
{
$this->products[$orderId] = $product;
}
#[QueryHandler('order.getProduct')]
public function getProduct(string $orderId): string
{
return $this->products[$orderId];
}
}],
);
$ecotoneLite->sendCommandWithRoutingKey('placeOrder', metadata: ['orderId' => '123', 'product' => 'Book']);
$this->assertSame(
'Book',
$ecotoneLite->sendQueryWithRouting('order.getProduct', '123'),
);
}
Tests use inline anonymous classes defined directly inside the test method — never create separate fixture files. This keeps each test self-contained and readable as a complete usage example. Never use static properties or static methods in test classes — use instance properties and instance methods only. The test demonstrates how a user would use the feature — registering handlers, sending commands, and querying results. It does not test internal message routing, channel resolution, or framework wiring directly.
This applies to all code changes that add or modify behavior — features, bug fixes, refactors that change behavior. Pure refactors with no behavior change may skip this if existing tests already cover the behavior.
Start the Docker Compose stack:
docker compose up -d
Enter the main container:
docker compose exec app /bin/bash
PHP 8.2 container (for compatibility testing):
docker compose exec app8_2 /bin/bash
packages/
├── Ecotone/ # Core package -- foundation for all others
├── Amqp/ # RabbitMQ integration
├── Dbal/ # Database abstraction (DBAL)
├── PdoEventSourcing/ # Event sourcing with PDO
├── Laravel/ # Laravel framework integration
├── Symfony/ # Symfony framework integration
├── Sqs/ # AWS SQS integration
├── Redis/ # Redis integration
├── Kafka/ # Kafka integration
├── OpenTelemetry/ # Tracing / OpenTelemetry
└── ...
packages/<PackageName> is a separate Composer package, split to read-only repos on releaseRun these steps in order before submitting a PR:
docker compose up -d
docker compose exec -T app vendor/bin/phpunit --filter test_method_name
cd packages/<PackageName> && composer tests:ci
Every PHP file must have a licence comment:
/**
* licence Apache-2.0
*/
Enterprise files use:
/**
* licence Enterprise
*/
vendor/bin/php-cs-fixer fix
vendor/bin/phpstan analyse
snake_case test method names (enforced by PHP-CS-Fixer)@param/@return on public API methods! $var spacing (not !$var)Use the repository's PR template at .github/PULL_REQUEST_TEMPLATE.md.
Before creating the PR, generate a suggested description based on the changes made, focusing on why the change is needed and what benefits it provides to end users. Present this suggestion to the user and let them agree or modify it. Do not use the description without user confirmation.
Branch and PR title naming: Use conventional commit prefixes:
feat: for new features (e.g. feat: add support for delayed message publishing)fix: for bug fixes (e.g. fix: resolve race condition in saga handler)refactor: for refactoring (e.g. refactor: simplify interceptor resolution)docs: for documentation changestest: for test-only changesPR body must include:
```mermaid
sequenceDiagram
participant User
participant CommandBus
participant Handler
User->>CommandBus: PlaceOrder
CommandBus->>Handler: #[CommandHandler]
Handler-->>User: OrderPlaced event
```
| Rule | Example |
|------|---------|
| No comments | Use meaningful private method names instead |
| PHP 8.1+ features | Attributes, enums, named arguments, readonly |
| snake_case tests | public function test_it_handles_command() |
| Single quotes | 'string' not "string" |
| Trailing commas | In multiline arrays, parameters |
| Not operator spacing | ! $var not !$var |
| PHPDoc on public APIs | @param/@return with types |
| Licence headers | On every PHP file |
symplify/monorepo-builder for managing splitscomposer.json with real dependenciesphp-cs-fixer fix before committingsnake_casesnake_case method names (enforced by PHP-CS-Fixer)/**
* licence Apache-2.0
*/
final class OrderTest extends TestCase
{
public function test_placing_order_records_event(): void
{
// test body
}
}
development
Implements workflows in Ecotone: Sagas (stateful process managers), stateless workflows with InternalHandler and outputChannelName chaining, and Orchestrators (Enterprise) with routing slip pattern. Use when building Sagas, process managers, multi-step workflows, long-running processes, handler chaining, or Orchestrators.
development
Writes and debugs tests for Ecotone using EcotoneLite::bootstrapFlowTesting, aggregate testing, async-tested-synchronously patterns, projections, and common failure diagnosis. Use when writing tests, debugging test failures, adding test coverage, or implementing any new feature that needs tests. Should be co-triggered whenever a new handler, aggregate, saga, projection, or interceptor is being implemented.
testing
Sets up Ecotone in a Symfony project: composer installation, bundle registration, YAML configuration, Doctrine ORM integration, SymfonyConnectionReference for DBAL, Symfony Messenger channels, async consumer commands, and ServiceContext. Use when installing Ecotone in Symfony, configuring Symfony-specific connections, or setting up Symfony async consumers.
development
Implements message resiliency in Ecotone: RetryTemplateBuilder for retry strategies, error channels, ErrorHandlerConfiguration, DBAL dead letter queues, outbox pattern for guaranteed delivery, and FinalFailureStrategy for permanent failures. Use when handling failed messages, configuring retries, setting up dead letter queues, implementing outbox pattern, or managing error channels.