php artisan boost:add-skill symkit/bundle-ai-kit
Save this content to: .cursor/rules/php-code.mdc
# PHP Code Style — Composer Plugin
## Mandatory Header
Every PHP file starts with `declare(strict_types=1)`.
## Classes
- `final readonly class` for: value objects (`SyncContext`), resolvers
- `final class` (without `readonly`) for: `AiRulesInstaller`, `Plugin`
- `Plugin` is `final class` — Composer instantiates with no args, optional constructor params for tests
## Typing
- All parameters, return types, and properties must be typed
- No `mixed` except with a comment justifying why
- PHPStan level 9: `@param list<string>` for skill name lists
- Nullable optional params: `?SyncContextResolverInterface $contextResolver = null`
- Return `?SyncContext` from resolver
## Constructor Patterns
```php
final readonly class DefaultSyncContextResolver implements SyncContextResolverInterface
{
public function __construct(
private string $packageName,
) {
}
}
```
Plugin (tests may inject resolver/installer):
```php
public function __construct(
?SyncContextResolverInterface $contextResolver = null,
?AiRulesInstaller $installer = null,
) {
$this->contextResolver = $contextResolver ?? DefaultSyncContextResolver::forBundleAiKit();
$this->installer = $installer ?? new AiRulesInstaller();
}
```
## Language Features
- Native function prefix: `\is_dir()`, `\mkdir()`, `\copy()`, etc.
- Early return, trailing commas in multiline
## Namespace Organization
- `Contract/` — `SyncContextResolverInterface`
- `Context/` — `SyncContext`, resolver, `SkillNamesDiscoverer`
- `Installer/` — `AiRulesInstaller`
- Root — `Plugin` only
## Forbidden
- `new ConcreteService()` outside Plugin constructor (except tests)
- Setter injection
- `@phpstan-ignore` without comment
## After Every Change
`make cs-fix` then `make phpstan`
Every PHP file starts with declare(strict_types=1).
final readonly class for: value objects (SyncContext), resolversfinal class (without readonly) for: AiRulesInstaller, PluginPlugin is final class — Composer instantiates with no args, optional constructor params for testsmixed except with a comment justifying why@param list<string> for skill name lists?SyncContextResolverInterface $contextResolver = null?SyncContext from resolverfinal readonly class DefaultSyncContextResolver implements SyncContextResolverInterface
{
public function __construct(
private string $packageName,
) {
}
}
Plugin (tests may inject resolver/installer):
public function __construct(
?SyncContextResolverInterface $contextResolver = null,
?AiRulesInstaller $installer = null,
) {
$this->contextResolver = $contextResolver ?? DefaultSyncContextResolver::forBundleAiKit();
$this->installer = $installer ?? new AiRulesInstaller();
}
\is_dir(), \mkdir(), \copy(), etc.Contract/ — SyncContextResolverInterfaceContext/ — SyncContext, resolver, SkillNamesDiscovererInstaller/ — AiRulesInstallerPlugin onlynew ConcreteService() outside Plugin constructor (except tests)@phpstan-ignore without commentmake cs-fix then make phpstan
How can I help you explore Laravel packages today?