Skip to content
Scaffolding
Agent

gacela

This project splits its code into modules with Gacela. Follow these rules when you add or change code. The full docs for the installed version are in `vendor/gacela-project/gacela/docs/`; read the page named in each section before doing something new.

From plugin
gacela
1331 skill1 agent

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

This project splits its code into modules with Gacela. Follow these rules when you add or change code. The full docs for the installed version are in `vendor/gacela-project/gacela/docs/`; read the page named in each section before doing something new.

Agent definition

gacela.md

Working in a Gacela project

This project splits its code into modules with Gacela. Follow these rules when you add or change code. The full docs for the installed version are in `vendor/gacela-project/gacela/docs/`; read the page named in each section before doing something new.

The shape of a module

A module is a directory whose classes share a namespace, entered through one class:

  • **Facade** (`extends AbstractFacade`): the module's public API. Other modules call only this.
  • **Factory** (`extends AbstractFactory`): builds the module's services. `create*()` methods.
  • **Config** (`extends AbstractConfig`): reads configuration with typed getters.
  • **Provider** (`extends AbstractProvider`): supplies what comes from outside the module, other modules' Facades included.

Pillars are found by name. In `App\Billing` they are `BillingFacade` (or `Facade`), `BillingFactory`, `BillingConfig`, `BillingProvider`. Do not give any other class one of those suffixes.

Create a module with the scaffolder, never by hand:

vendor/bin/gacela make:module App/Billing            # all four pillars
vendor/bin/gacela make:module App/Billing --minimal  # Facade and Factory only
vendor/bin/gacela make:file App/Billing Config       # add a pillar later

The rules

1. **Cross a module boundary only through its Facade.** Never `use` another module's Factory, domain class or repository. If you need something the Facade does not offer, add a Facade method in that module. 2. **Facade methods delegate.** A Facade method calls `$this->getFactory()->create...()->...` and nothing more. Logic lives in the module's domain classes. 3. **Build services in the Factory.** No `new` of a collaborator inside a domain class or a Facade. 4. **Get another module's Facade in a Factory** through the Provider: declare it with `#[Provides(CustomerFacade::class)]`, read it with `getProvidedDependency(CustomerFacade::class)`. The class name as the key is typed, so no `@var` above the call. 5. **Get a pillar outside the pillars** (a Controller, a Command) with `ServiceResolverAwareTrait` and `#[ServiceMap]`, not with a `@method` docblock. 6. **Read configuration in the module's Config**, through its typed getters (`getString()`, `getInt()`, ...). Expose intention-revealing methods; do not pass raw arrays around. 7. **Register infrastructure in `gacela.php`**, not inside modules: `addBinding()` for an interface, `addPluginStack()` for several implementations of one interface, `addHandlerRegistry()` for lookup by key, `tag()` for an untyped group. A class can also join a declared plugin stack with `#[Plugin(Contract::class)]`, or a tag with `#[Tag('name')]`, and a public method can listen to a module event with `#[AsListener]`.

Which way to get a dependency, case by case: `docs/getting-a-dependency.md`.

final class BillingProvider extends AbstractProvider
{
    #[Provides(CustomerFacade::class)]
    public function customerFacade(): CustomerFacade
    {
        return new CustomerFacade();
    }
}

final class BillingFactory extends AbstractFactory
{
    public function createInvoiceIssuer(): InvoiceIssuer
    {
        return new InvoiceIssuer($this->getProvidedDependency(CustomerFacade::class));
    }
}

Check your work

Run these before you say a change is done. Each answers "is this right?" in one call.

vendor/bin/gacela doctor --only-problems   # wiring, config, cache and module health; exit 1 on errors
vendor/bin/gacela debug:graph --check      # fails on a module dependency cycle
vendor/bin/gacela list:modules --json      # every module and its pillars, machine-readable
vendor/bin/gacela debug:module App/Billing # one module: resolved pillars, provided ids, public API
vendor/bin/gacela debug:plugins            # plugin stacks, tags and #[AsListener] methods, with where each is declared
vendor/bin/gacela debug:events --listened  # which events something listens to, #[AsListener] methods included

Static analysis carries the rules. The project's `phpstan.neon` should include:

includes:
    - vendor/gacela-project/gacela/phpstan-gacela.neon

That reports a Facade method that does more than delegate, a pillar that does not extend its base, and a Factory that reaches a Facade directly. The boundary rule, an import of another module's class that skips its Facade, is opt-in because it needs the namespace the modules live under:

services:
    -
        class: Gacela\PHPStan\Rules\CrossModuleViaFacadeRule
        tags: [phpstan.rules.rule]
        arguments:
            rootNamespace: App      # the namespace your modules sit under
            modulePathSegments: 1

If the project has it, keep it green. Do not silence these reports; fix the code. All options: `docs/module-boundaries.md` and `docs/static-analysis.md`.

Tests

Extend `Gacela\Framework\Testing\GacelaTestCase`. To test one module with its neighbours replaced:

$this->bootstrapModule(__DIR__, BillingFacade::class, doubles: [
    CustomerFacade::class => $this->createStub(CustomerFacade::class),
]);

See `docs/testing.md`.

Attributes

Attributes register a class or method where it lives, with no line in `gacela.php`. They are found by scanning the module paths inside `projectNamespaces`, and `doctor` checks them.

  • `#[Plugin(Contract::class, priority: 10)]` on a class joins a plugin stack. The stack must still be declared in `gacela.php`, empty if the attributes fill it: `addPluginStack(Contract::class, [])`.
  • `#[Tag('name')]` on a class joins a tag. No declaration needed.
  • `#[AsListener]` on a public method of a concrete class listens to the event its first parameter types. It hears events a module dispatches through `getProvidedDependency(EventDispatcherInterface::class)`, not Gacela's own.
  • `#[Provides(Factory::ID)]` on a Provider method declares a provided dependency.
  • `#[ServiceMap(method: 'getFacade', className: BillingFacade::class)]` on a class using `ServiceResolverAwareTr
Read more
Ships withgacela

Build modular PHP applications.

Get the whole plugin
Stats
133
Stars
8
Forks
Active
Maintenance
PHP
Language
19d ago
Last commit
5y ago
Created
9h ago
Added

Repo: gacela-project/gacela