create-module
Scaffold a new hexagonal module with Facade, ServiceProvider, and registration
SKILL.md.claude/skills/create-module/SKILL.md
GitHubNew Module
Scaffold modules/$ARGUMENTS/. PascalCase, must not clash with an existing directory — ask if missing.
First, challenge the need. A new module requires its own vocabulary, not just its own files. If it wraps one existing concept, it belongs inside an existing module. Layer rules: .claude/rules/architecture.md.
Context
!ls modules/
Steps
Read a small reference module —
modules/Faq/ormodules/UtxoTrace/— and mirror its layout.Structure:
modules/<Module>/ ├── Application/<Module>Facade.php ├── Domain/<Module>FacadeInterface.php ├── Domain/Repository/ ├── Infrastructure/Http/Controller/ ├── Infrastructure/Repository/ └── <Module>ServiceProvider.phpDomain first — the Facade interface is the module's public contract. Other modules depend on this and nothing else:
namespace Modules\<Module>\Domain; interface <Module>FacadeInterface { // public API — domain types in, domain types out }Facade — thin delegation to Actions, no logic of its own:
namespace Modules\<Module>\Application; final readonly class <Module>Facade implements <Module>FacadeInterface { public function __construct( // inject Action interfaces ) { } }ServiceProvider:
namespace Modules\<Module>; final class <Module>ServiceProvider extends ServiceProvider { /** @var array<class-string, class-string> */ public $singletons = [ <Module>FacadeInterface::class => <Module>Facade::class, ]; #[Override] public function register(): void { // contextual bindings only } }Bind the facade interface now. Three existing modules skipped this and their facades are dormant — see Known Debt in
CLAUDE.md.Register in
bootstrap/providers.php— no auto-discovery.Sharedstays first.Create
tests/Unit/<Module>/.Update docs — module table in
docs/architecture.mdandCLAUDE.md.composer test
Specific to new modules
- Keep
App\Models\*out of this module's Domain. The model-in-Domain trade is legacy inChat/OpenAI/Payment/UtxoTrace, not a target to replicate - The Facade never returns an Eloquent model or query builder
- No cycles — check what this module will import before creating it