Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
Bundle Quality
symkit/crud-bundle
cursor
makefile
phpstan
php-cs-fixer
grumphp
deptrac
infection
quality-pipeline

Quality tooling configuration and CI pipeline for Symfony bundles

Install
php artisan boost:add-skill symkit/crud-bundle

Save this content to: .cursor/rules/bundle-quality.mdc

# Bundle Quality Tooling

## Makefile First

The Makefile is the SINGLE entry point for all quality operations.
Never call `vendor/bin/` directly — always use `make` targets:

- `make cs-fix` — fix code style
- `make cs-check` — check without fixing
- `make phpstan` — static analysis level 9
- `make test` — run PHPUnit
- `make deptrac` — architecture enforcement
- `make lint` — `composer validate --strict`
- `make infection` — mutation testing
- `make security-check` — `composer audit --abandoned=report`
- `make quality` — full pipeline (cs-check + phpstan + deptrac + lint + test + infection)
- `make ci` — security-check + quality
Never commit code that fails `make quality`.

## Commits (no self-attribution)

Do not add Co-authored-by, Signed-off-by from the AI, or `git commit --trailer`.

## Cleanup — Legacy commit-msg hook

If the project contains `scripts/git-hooks/commit-msg`, an `install-hooks` target in the Makefile, or `make install-hooks` references in README or docs, remove them. These artefacts are obsolete. See symfony-bundle.mdc § Cleanup.

## PHPStan Level 9

- **Always fix all PHPStan errors**, even pre-existing ones not introduced by the current change — leave the codebase in a better state
- **Never use baselines** (`phpstan-baseline.neon`) to ignore errors — fix them instead
- No `@phpstan-ignore` without explanatory comment
- No `mixed` without justification
- Typed collections: `/** @var Collection<int, Entity> */`
- Typed repository returns: `@return Entity[]`
- `@param array{key: type}` for shaped arrays (especially `loadExtension()` config)

## PHP-CS-Fixer

- `@Symfony` + `@PHP82Migration`
- `declare_strict_types` enforced
- `native_function_invocation` with `@compiler_optimized`
- `trailing_comma_in_multiline` on arguments, parameters, match, arrays
- `ordered_imports` alphabetical
- Always use `.php-cs-fixer.dist.php` (not `.php-cs-fixer.php`)

## GrumPHP

Pre-commit hooks must mirror CI (except security): phpstan, phpcsfixer, phpunit, deptrac, infection. Same thresholds as Makefile (e.g. infection `min_covered_msi: 65`). This way a commit that would fail CI (e.g. 0% MSI) is blocked locally.
Note: `composer_audit` is NOT a valid GrumPHP task — use `make security-check` separately.

### Base configuration (`grumphp.yml`)

```yaml
grumphp:
    tasks:
        make:
            task: deptrac
            triggered_by: [php]
        phpstan:
            configuration: phpstan.neon.dist
            memory_limit: 512M
            use_grumphp_paths: false
        phpcsfixer:
            config: .php-cs-fixer.dist.php
        phpunit:
            config_file: phpunit.xml.dist
        shell:
            scripts:
                - ['-c', 'make infection']
            triggered_by: [php]
```

> **Deptrac via `make` task**: Use the GrumPHP `make` task with `task: deptrac` to delegate to the Makefile target. This avoids needing a separate shell script.

## Composer allow-plugins

Always configure in `composer.json`:
- `infection/extension-installer: true`
- `phpro/grumphp: true`

## Deptrac

> **NEVER use `qossmic/deptrac`** — it is abandoned. Always use `deptrac/deptrac`.

- Entity depends on nothing (or Contract)
- Service never imports Controller
- Contract imports nothing internal
- Controller goes through Service, not Repository directly

## Infection

- MSI >= 70% (or 65% with documented justification, e.g. mutants escaping scope such as prependExtension path)
- `--only-covered`, threads max
- Always run after `make test`

## CI (GitHub Actions)

Default template — single `ci` job running `make ci` over the full PHP × Symfony matrix:

```yaml
name: CI

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

jobs:
  ci:
    name: PHP ${{ matrix.php }} / Symfony ${{ matrix.symfony }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        php: ['8.2', '8.3', '8.4']
        symfony: ['7.1.*', '7.2.*', '8.0.*']
        exclude:
          - { php: '8.2', symfony: '8.0.*' }
          - { php: '8.3', symfony: '8.0.*' }

    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: intl, mbstring, xml, dom
          coverage: none

      - name: Get Composer cache directory
        id: composer-cache
        run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache Composer
        uses: actions/cache@v4
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-composer-

      - name: Install dependencies
        run: composer update --no-progress --prefer-dist

      - name: Install Symfony version
        run: composer require "symfony/framework-bundle:${{ matrix.symfony }}" --no-update

      - name: Update with Symfony constraint
        run: composer update symfony/* --no-progress --prefer-dist

      - name: Run CI pipeline
        run: make ci
```

Rules:
- `fail-fast: false` so all matrix combinations are always reported
- PHP 8.4 + Symfony 8.0 is the only valid combination for Symfony 8 (exclude 8.2 and 8.3)
- Symfony deps in `composer.json`: always `^7.0 || ^8.0`
- `make ci` covers: `composer audit` + cs-check + phpstan + deptrac + lint + test + infection

## .gitignore essentials

```
/vendor/
composer.lock
.phpstan-cache/
.php-cs-fixer.cache
.deptrac.cache
.phpunit.result.cache
infection.log
infection-summary.log
phpunit.xml
phpstan.neon
config/reference.php
*.log
.agent/
.claude/
```

## Dependencies

- Deptrac: use `deptrac/deptrac` (not the abandoned `qossmic/deptrac`)
- Always use `composer audit --abandoned=report` so abandoned packages are reported but only security vulnerabilities fail the build

## Anti-Patterns

- Calling `vendor/bin/` directly instead of Makefile targets
- `phpstan.neon` instead of `.dist` (overridden locally)
- MSI < 50% (tests don't verify behavior)
- Ignoring deprecations
- No `composer audit` in CI
- No Deptrac (architecture erodes)
- `composer_audit` in grumphp.yml (task does not exist)
- `composer audit` without `--abandoned=report` (fails CI on abandoned transitive deps)
- Using `qossmic/deptrac` instead of `deptrac/deptrac` (abandoned)
- Using `phpstan-baseline.neon` to ignore errors instead of fixing them

Bundle Quality Tooling

Makefile First

The Makefile is the SINGLE entry point for all quality operations. Never call vendor/bin/ directly — always use make targets:

  • make cs-fix — fix code style
  • make cs-check — check without fixing
  • make phpstan — static analysis level 9
  • make test — run PHPUnit
  • make deptrac — architecture enforcement
  • make lint — composer validate --strict
  • make infection — mutation testing
  • make security-check — composer audit --abandoned=report
  • make quality — full pipeline (cs-check + phpstan + deptrac + lint + test + infection)
  • make ci — security-check + quality Never commit code that fails make quality.

Commits (no self-attribution)

Do not add Co-authored-by, Signed-off-by from the AI, or git commit --trailer.

Cleanup — Legacy commit-msg hook

If the project contains scripts/git-hooks/commit-msg, an install-hooks target in the Makefile, or make install-hooks references in README or docs, remove them. These artefacts are obsolete. See symfony-bundle.mdc § Cleanup.

PHPStan Level 9

  • Always fix all PHPStan errors, even pre-existing ones not introduced by the current change — leave the codebase in a better state
  • Never use baselines (phpstan-baseline.neon) to ignore errors — fix them instead
  • No @phpstan-ignore without explanatory comment
  • No mixed without justification
  • Typed collections: /** @var Collection<int, Entity> */
  • Typed repository returns: @return Entity[]
  • @param array{key: type} for shaped arrays (especially loadExtension() config)

PHP-CS-Fixer

  • @Symfony + @PHP82Migration
  • declare_strict_types enforced
  • native_function_invocation with @compiler_optimized
  • trailing_comma_in_multiline on arguments, parameters, match, arrays
  • ordered_imports alphabetical
  • Always use .php-cs-fixer.dist.php (not .php-cs-fixer.php)

GrumPHP

Pre-commit hooks must mirror CI (except security): phpstan, phpcsfixer, phpunit, deptrac, infection. Same thresholds as Makefile (e.g. infection min_covered_msi: 65). This way a commit that would fail CI (e.g. 0% MSI) is blocked locally. Note: composer_audit is NOT a valid GrumPHP task — use make security-check separately.

Base configuration (grumphp.yml)

grumphp:
    tasks:
        make:
            task: deptrac
            triggered_by: [php]
        phpstan:
            configuration: phpstan.neon.dist
            memory_limit: 512M
            use_grumphp_paths: false
        phpcsfixer:
            config: .php-cs-fixer.dist.php
        phpunit:
            config_file: phpunit.xml.dist
        shell:
            scripts:
                - ['-c', 'make infection']
            triggered_by: [php]

Deptrac via make task: Use the GrumPHP make task with task: deptrac to delegate to the Makefile target. This avoids needing a separate shell script.

Composer allow-plugins

Always configure in composer.json:

  • infection/extension-installer: true
  • phpro/grumphp: true

Deptrac

NEVER use qossmic/deptrac — it is abandoned. Always use deptrac/deptrac.

  • Entity depends on nothing (or Contract)
  • Service never imports Controller
  • Contract imports nothing internal
  • Controller goes through Service, not Repository directly

Infection

  • MSI >= 70% (or 65% with documented justification, e.g. mutants escaping scope such as prependExtension path)
  • --only-covered, threads max
  • Always run after make test

CI (GitHub Actions)

Default template — single ci job running make ci over the full PHP × Symfony matrix:

name: CI

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

jobs:
  ci:
    name: PHP ${{ matrix.php }} / Symfony ${{ matrix.symfony }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        php: ['8.2', '8.3', '8.4']
        symfony: ['7.1.*', '7.2.*', '8.0.*']
        exclude:
          - { php: '8.2', symfony: '8.0.*' }
          - { php: '8.3', symfony: '8.0.*' }

    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: intl, mbstring, xml, dom
          coverage: none

      - name: Get Composer cache directory
        id: composer-cache
        run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache Composer
        uses: actions/cache@v4
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-composer-

      - name: Install dependencies
        run: composer update --no-progress --prefer-dist

      - name: Install Symfony version
        run: composer require "symfony/framework-bundle:${{ matrix.symfony }}" --no-update

      - name: Update with Symfony constraint
        run: composer update symfony/* --no-progress --prefer-dist

      - name: Run CI pipeline
        run: make ci

Rules:

  • fail-fast: false so all matrix combinations are always reported
  • PHP 8.4 + Symfony 8.0 is the only valid combination for Symfony 8 (exclude 8.2 and 8.3)
  • Symfony deps in composer.json: always ^7.0 || ^8.0
  • make ci covers: composer audit + cs-check + phpstan + deptrac + lint + test + infection

.gitignore essentials

/vendor/
composer.lock
.phpstan-cache/
.php-cs-fixer.cache
.deptrac.cache
.phpunit.result.cache
infection.log
infection-summary.log
phpunit.xml
phpstan.neon
config/reference.php
*.log
.agent/
.claude/

Dependencies

  • Deptrac: use deptrac/deptrac (not the abandoned qossmic/deptrac)
  • Always use composer audit --abandoned=report so abandoned packages are reported but only security vulnerabilities fail the build

Anti-Patterns

  • Calling vendor/bin/ directly instead of Makefile targets
  • phpstan.neon instead of .dist (overridden locally)
  • MSI < 50% (tests don't verify behavior)
  • Ignoring deprecations
  • No composer audit in CI
  • No Deptrac (architecture erodes)
  • composer_audit in grumphp.yml (task does not exist)
  • composer audit without --abandoned=report (fails CI on abandoned transitive deps)
  • Using qossmic/deptrac instead of deptrac/deptrac (abandoned)
  • Using phpstan-baseline.neon to ignore errors instead of fixing them
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib