.claude/skills/ecotone-module-creator/SKILL.md
Scaffolds new Ecotone packages and modules: AnnotationModule pattern, module registration, Configuration building, and package template usage. Use when creating new framework modules, extending the module system, or scaffolding new packages.
npx skillsauth add ecotoneframework/ecotone-dev ecotone-module-creatorInstall 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.
This skill covers creating new Ecotone modules and packages. Use it when scaffolding a new package, implementing a module class with the AnnotationModule pattern, registering handlers/channels/converters in the messaging system, or accepting external configuration via #[ServiceContext].
Every Ecotone module follows the AnnotationModule pattern:
use Ecotone\AnnotationFinder\AnnotationFinder;
use Ecotone\Messaging\Attribute\ModuleAnnotation;
use Ecotone\Messaging\Config\Annotation\AnnotationModule;
use Ecotone\Messaging\Config\Annotation\ModuleConfiguration\NoExternalConfigurationModule;
use Ecotone\Messaging\Config\Configuration;
use Ecotone\Messaging\Config\ModuleReferenceSearchService;
use Ecotone\Messaging\Handler\InterfaceToCallRegistry;
#[ModuleAnnotation]
final class MyModule extends NoExternalConfigurationModule implements AnnotationModule
{
public static function create(
AnnotationFinder $annotationRegistrationService,
InterfaceToCallRegistry $interfaceToCallRegistry
): static {
return new self();
}
public function prepare(
Configuration $messagingConfiguration,
array $extensionObjects,
ModuleReferenceSearchService $moduleReferenceSearchService,
InterfaceToCallRegistry $interfaceToCallRegistry
): void {
// Register handlers, converters, channels, etc.
}
public function canHandle($extensionObject): bool
{
return false;
}
public function getModulePackageName(): string
{
return 'myPackage';
}
}
Key pieces:
#[ModuleAnnotation] -- marks class as a moduleAnnotationModule interface -- required contractNoExternalConfigurationModule -- extend when no external config needed// Find all classes with a specific attribute
$classes = $annotationRegistrationService->findAnnotatedClasses(MyAttribute::class);
// Find all methods with a specific attribute
$methods = $annotationRegistrationService->findAnnotatedMethods(MyHandler::class);
// Each result provides:
// - getClassName() -- fully qualified class name
// - getMethodName() -- method name
// - getAnnotationForMethod() -- the attribute instance
When your module accepts external configuration:
public function canHandle($extensionObject): bool
{
return $extensionObject instanceof MyModuleConfig;
}
public function prepare(
Configuration $messagingConfiguration,
array $extensionObjects,
...
): void {
$configs = ExtensionObjectResolver::resolve(MyModuleConfig::class, $extensionObjects);
foreach ($configs as $config) {
// Apply configuration
}
}
Users provide configuration via #[ServiceContext]:
class UserConfig
{
#[ServiceContext]
public function myModuleConfig(): MyModuleConfig
{
return new MyModuleConfig(setting: 'value');
}
}
Start from the package template directory:
<PackageTemplate>/
├── src/
│ └── Configuration/
│ └── <PackageTemplate>Module.php
├── tests/
├── composer.json
└── phpstan.neon
Steps:
packages/<YourPackage>/<YourPackage>ModuleEcotone\<YourPackage>composer.json (name, autoload)ModulePackageList (add constant + match case)composer.json for monorepopublic function test_module_registers_handlers(): void
{
$ecotone = EcotoneLite::bootstrapFlowTesting(
classesToResolve: [MyModule::class, TestHandler::class],
containerOrAvailableServices: [new TestHandler()],
);
$ecotone->sendCommand(new TestCommand());
// Assert expected behavior
}
#[ModuleAnnotation]finalNoExternalConfigurationModule when no user config is neededModulePackageList for skip supportAnnotationModule interface, NoExternalConfigurationModule base class, Configuration interface methods (registerMessageHandler, registerMessageChannel, registerConverter, etc.), AnnotationFinder interface methods, ModulePackageList constants and registration steps, package directory structure with composer.json template, and a full module with external configuration class. Load when you need exact interface signatures, the package template module code, composer.json boilerplate, or a complete module with external configuration.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.