PHPackages                             b7s/catraca - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. b7s/catraca

ActiveLibrary[Testing &amp; Quality](/categories/testing)

b7s/catraca
===========

PHP Quality Guardian — enforces the Catraca (ratchet) principle: quality metrics can only improve, never regress

v1.0.70(1w ago)10184↑550%4MITPHPPHP ^8.3CI passing

Since May 10Pushed 1w agoCompare

[ Source](https://github.com/b7s/catraca)[ Packagist](https://packagist.org/packages/b7s/catraca)[ RSS](/packages/b7s-catraca/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (10)Dependencies (16)Versions (20)Used By (4)

 [![Catraca Logo](logo.webp)](logo.webp)

Catraca
=======

[](#catraca)

PHP Quality Guardian that enforces the **Catraca (ratchet) principle**: quality metrics can only improve or stay the same, never regress.

> **Catraca** (Portuguese for "turnstile" / "ratchet") — like a turnstile at a subway station, quality can only move forward.

Install
-------

[](#install)

```
composer require --dev b7s/catraca
```

Dependencies
------------

[](#dependencies)

Catraca wraps your existing PHP quality tools. Install the ones you need:

```
# Code style
composer require --dev laravel/pint
# or
composer require --dev friendsofphp/php-cs-fixer

# Static analysis
composer require --dev phpstan/phpstan
# or
composer require --dev vimeo/psalm

# Test coverage
composer require --dev phpunit/phpunit
# or
composer require --dev pestphp/pest

# Duplication detection
# PHP 8.3
composer require --dev systemsdk/phpcpd:^8.0
# PHP 8.4+
composer require --dev systemsdk/phpcpd:^9.0

# Cyclomatic complexity
composer require --dev phpmetrics/phpmetrics
```

Any tool not installed is **skipped** (not failed). Security audit uses `composer audit` (built-in) and 14 source-code checks.

Usage
-----

[](#usage)

### `catraca init` — Initialize baseline

[](#catraca-init--initialize-baseline)

Creates `catraca_baseline.json` in your project root with default thresholds:

```
vendor/bin/catraca init
```

Default baseline:

SettingDefaultSource Dirs`["src", "app", "lib"]`Security0 advisories, 14 checks all enabledCode Style0 violationsStatic Analysis0 errors (level 5 if no `phpstan.neon`)Test Coverage85% minimumDuplication2% maximum, min 3 lines, min 30 tokensFile Size1000 lines maximum per fileComplexityBlock at CCN 50, warn at CCN 20Performance0 violationsYou can edit `catraca_baseline.json` directly to adjust thresholds.

### Configuration — `catraca_baseline.json`

[](#configuration--catraca_baselinejson)

```
{
    "source_dirs": {
        "paths": ["src", "app", "lib"]
    },
  "security": {
    "advisories": 0,
    "rules": {
      "hardcoded_secrets": true,
      "sql_injection": true,
      "command_injection": true,
      "csrf_protection": true,
      "path_traversal": true,
      "insecure_deserialization": true,
      "ssrf": true,
      "tls_verification": true,
      "insecure_rng": true,
      "gitignore_sensitive": true,
      "package_freshness": true,
      "weak_cryptography": true,
      "cors_config": true,
      "npm_audit": true
    },
    "fixers": []
  },
    "style": {
        "violations": 0
    },
    "static_analysis": {
        "errors": 0
    },
    "coverage": {
        "percentage": 85.0
    },
    "duplication": {
        "percentage": 2.0,
        "min_lines": 3,
        "min_tokens": 30
    },
    "file_size": {
        "max_lines": 1000
    },
    "complexity": {
        "max_ccn": 0
    },
    "performance": {
        "violations": 0,
        "rules": {
          "global_namespace_import": true,
          "no_unused_imports": true,
          "fully_qualified_strict_types": true,
          "lambda_not_used_import": true,
          "native_function_invocation": true,
          "no_redundant_readonly_property": true,
          "static_lambda": true,
          "array_push": true,
          "ereg_to_preg": true,
          "modernize_strpos": true,
          "pow_to_exponentiation": true,
          "random_api_migration": true,
          "set_type_to_cast": true,
          "autoload_optimization": true,
          "condition_order": true
        },
        "fixers": {
            "condition_order": false
        }
    }
}
```

**`source_dirs.paths`** — which directories Catraca scans for PHP files. Only directories that exist on disk are used. If none of the configured directories exist, the project root is used as fallback. Defaults to `["src", "app", "lib"]`.

### `catraca check` — Run quality gates

[](#catraca-check--run-quality-gates)

Runs all 8 gates and compares against baseline. If `catraca_baseline.json` doesn't exist, it is created automatically.

```
# Human-readable (default)
vendor/bin/catraca check

# Plain text (no colors)
vendor/bin/catraca check --plain

# JSON output for AI agents / CI
vendor/bin/catraca check --format=json
# or vendor/bin/catraca check --format=json-pretty

# GitHub Actions annotations
vendor/bin/catraca check --format=github

# Specify project path
vendor/bin/catraca check --path=/path/to/project

# Auto-fix issues if any gate fails, then verify
vendor/bin/catraca check --fix
```

> **AI Agent Detection:** When Catraca detects it is running inside an AI agent (Cursor, Claude Code, OpenCode, etc.), it automatically switches to `--format=json` for structured output. You can still override this by explicitly passing `--format`.

### `catraca fix` — Auto-fix issues

[](#catraca-fix--auto-fix-issues)

Runs auto-fixers for code style, performance, and autoload optimization.

```
vendor/bin/catraca fix

# Specify project path
vendor/bin/catraca fix --path=/path/to/project

# Skip the automatic check after fixing
vendor/bin/catraca fix --no-check
```

What it fixes:

FixerToolWhat it doesCondition OrderBuilt-inSwaps expensive conditions to come after cheaper ones in `&&` / `Code Style`pint` or `php-cs-fixer`Fixes all code style violationsPerformance`php-cs-fixer`Adds missing imports, removes unused imports, cleans FQCNs, optimizes native calls and moreAutoload`composer`Runs `composer dump-autoload -o` if not optimized### Exit Codes

[](#exit-codes)

CodeMeaning0All gates passed1One or more gates failedOutput Formats
--------------

[](#output-formats)

### Human (default)

[](#human-default)

Terminal-friendly output with ANSI colors:

```
  ┌──────────────────────────────────────────────────┐
  │ CATRACA — PHP Quality Gate Report                │
  └──────────────────────────────────────────────────┘
  ────────────────────────────────────────────────────────────
  ✔ Security Audit          PASS     0 total advisories, 0 critical/high
  ✔ Code Style              PASS     0 violations (baseline: 0)
  ✘ Static Analysis         FAIL     3 errors (baseline: 0)
  ✔ Test Coverage           PASS     85.00% (baseline: 85.00%)
  ✘ Duplication             FAIL     5.22% (baseline: 2.00%, 2 clones)
  ✔ File Size               PASS     0 files exceed 1000 lines
  ✔ Cyclomatic Complexity   PASS     max CCN 8, 0 violations (>50), 1 warnings (>20)
  ✔ Performance             PASS     No performance improvements needed
  ────────────────────────────────────────────────────────────
  RESULT: FAIL — 6/8 gates passed

  ┌──────────────────────────────────┐
  │ Required Actions                 │
  └──────────────────────────────────┘
  [1] FIX SA — Fix 3 PHPStan errors
      → app/Service.php:42
      → app/Repository.php:15
      → app/Controller.php:88
  [2] REFACTOR DUP — Duplication increased from 2.00% to 5.20%
      → src/A.php:10-50  src/B.php:100-140 (40L)

```

### JSON (for AI agents)

[](#json-for-ai-agents)

Use `catraca check --format=json` to get structured JSON output for AI agents. If you want it formatted, use `catraca check --format=json-pretty`. Note: Consumes more AI agent tokens.

Catraca auto-detects AI agents (Cursor, Claude Code, OpenCode, Gemini CLI, Codex, Augment, and others) and automatically uses JSON output — no flag needed.

```
{
  "schema": "catraca/v1",
  "result": "fail",
  "timestamp": "2025-05-08T10:30:00+00:00",
  "summary": {
    "total": 7,
    "passed": 5,
    "failed": 2,
    "skipped": 0
  },
  "gates": [
    {
      "name": "security",
      "label": "Security Audit",
      "status": "pass",
      "severity": "block",
      "message": "0 total advisories, 0 critical/high",
      "baseline": { "advisories": 0 },
      "current": { "advisories": 0, "critical": 0 }
    }
  ],
  "actions": [
    {
      "type": "FIX SA",
      "priority": 0,
      "message": "Fix 3 PHPStan errors",
      "files": ["app/Service.php:42", "app/Repository.php:15"]
    }
  ]
}
```

### GitHub Actions

[](#github-actions)

Uses `::error::`, `::warning::`, and `::group::` annotations for native GitHub integration.

Quality Gates
-------------

[](#quality-gates)

Gates run in order. A failure blocks the PR.

\#GateToolDefault Threshold1Security Audit`composer audit` + 14 built-in checks0 critical/high advisories, 0 findings2Code Style`pint` or `php-cs-fixer`0 violations3Static Analysis`phpstan` or `psalm`0 errors (level 5 if no config)4Test Coverage`phpunit` or `pest`85% minimum5Duplication`phpcpd`2% maximum6File SizeBuilt-in1000 lines per file7Cyclomatic Complexity`phpmetrics`Block at 50, warn at 208Performance`php-cs-fixer`0 violationsThe Security gate runs `composer audit` (always) plus 14 source-code checks (all enabled by default):

RuleWhat it detects`hardcoded_secrets`API keys, tokens, private keys, and other credentials in source code`sql_injection`Raw SQL with interpolated variables (`DB::select("...$var")`, `whereRaw`)`command_injection``exec`/`shell_exec`/`system`/`passthru` with unsanitized variables`csrf_protection`Missing `@csrf` in Laravel forms with POST/PUT/DELETE methods`path_traversal``file_get_contents`, `Storage::`, `include` with user-controlled paths`insecure_deserialization``unserialize()` with dynamic input or `base64_decode` chains`ssrf``Http::`, Guzzle, `curl_setopt(CURLOPT_URL)` with user-controlled URLs`tls_verification``withoutVerifying()`, `'verify' => false`, disabled `CURLOPT_SSL_VERIFYPEER``insecure_rng``rand()`/`mt_rand()`/`uniqid()` used for tokens/secrets (should use `random_bytes`)`gitignore_sensitive`Missing `.env`, `*.key`, `*.pem` entries in `.gitignore``package_freshness`Composer packages released less than 3 days ago (untested)`weak_cryptography``mcrypt_*`, ECB mode, DES/3DES/RC4, `md5`/`sha1` in security contexts`cors_config``Access-Control-Allow-Origin: *` with credentials in Laravel CORS config`npm_audit`Known vulnerabilities in npm packages (if `package-lock.json` exists)All rules are configurable in `catraca_baseline.json` under `security.rules`. Set any rule to `false` to disable it:

```
{
  "security": {
    "advisories": 0,
    "rules": {
      "hardcoded_secrets": true,
      "sql_injection": true,
      "command_injection": true,
      "csrf_protection": false,
      "path_traversal": true,
      "insecure_deserialization": true,
      "ssrf": true,
      "tls_verification": true,
      "insecure_rng": true,
      "gitignore_sensitive": true,
      "package_freshness": true,
      "weak_cryptography": true,
      "cors_config": true,
      "npm_audit": false
    }
  }
}
```

CSRF and CORS checks only apply to Laravel projects — they are **skipped** (not failed) when no Laravel directory structure is detected.

The Performance gate runs `php-cs-fixer` with configurable rules (all enabled by default):

RuleWhat it detects`global_namespace_import`Missing `use class/const` statements`no_unused_imports`Dead imports that slow parsing`fully_qualified_strict_types``\Foo\Bar` when `use Foo\Bar` already exists`lambda_not_used_import`Closures importing variables they don't use`native_function_invocation`Native function calls without `\` prefix optimization`no_redundant_readonly_property`Redundant readonly property declarations`static_lambda`Lambdas not using `$this` that should be `static``array_push``array_push()` calls — use `$arr[] =` instead`ereg_to_preg`Deprecated `ereg` function calls`modernize_strpos``strpos()` calls — use `str_contains`/`str_starts_with`/`str_ends_with``pow_to_exponentiation``pow()` calls — use `**` operator instead`random_api_migration``rand()`/`mt_rand()` calls — use `random_int()` instead`set_type_to_cast``settype()` calls — use type casting instead`autoload_optimization`Missing `composer dump-autoload -o``condition_order`Expensive conditions placed before cheaper ones in `&&` / `All rules are configurable in `catraca_baseline.json` under `performance.rules`. Set any rule to `false` to disable it, or `true` to enable it.

The `condition_order` check is **enabled by default** — it detects expensive conditions placed before cheaper ones. However, the auto-fix is **disabled by default** and marked as **experimental** because it modifies source code using AST transformations. Review all changes before committing. To enable automatic fixing, set `performance.fixers.condition_order` to `true`:

```
{
    "performance": {
        "rules": {
            "condition_order": true
        },
        "fixers": {
            "condition_order": true
        }
    }
}
```

### PHPStan Configuration

[](#phpstan-configuration)

If your project has a `phpstan.neon`, `phpstan.neon.dist`, or `phpstan.dist.neon`, Catraca uses it as-is. If no config file exists, it defaults to **level 5**.

CI/CD Integration
-----------------

[](#cicd-integration)

### GitHub Actions

[](#github-actions-1)

```
# .github/workflows/catraca.yml
name: Catraca Quality Gate

on:
  pull_request:
    branches: [main]

jobs:
  quality-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          coverage: pcov
          tools: composer, phpstan, pint, phpmetrics

      - run: composer install --no-interaction --prefer-dist
      - run: vendor/bin/catraca init --plain
        continue-on-error: true
      - run: vendor/bin/catraca check --format=github --plain
```

### GitLab CI

[](#gitlab-ci)

```
# .gitlab-ci.yml
stages:
  - test

catraca:
  stage: test
  image: php:8.3-cli
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - vendor/
  before_script:
    - apt-get update -qq && apt-get install -yqq unzip
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
    - composer install --no-interaction --prefer-dist
  script:
    - vendor/bin/catraca init --plain || true
    - vendor/bin/catraca check --plain
```

### Forgejo Actions

[](#forgejo-actions)

Uses the same `--format=github` output — Forgejo Runner supports GitHub Actions workflow commands (`::error::`, `::warning::`, `::group::`).

```
# .forgejo/workflows/catraca.yml
name: Catraca Quality Gate

on:
  pull_request:
    branches: [main]

jobs:
  quality-gate:
    runs-on: docker
    container:
      image: php:8.3-cli
    steps:
      - uses: https://code.forgejo.org/actions/checkout@v4

      - name: Install Composer
        run: |
          apt-get update -qq && apt-get install -yqq unzip
          curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

      - name: Install Dependencies
        run: composer install --no-interaction --prefer-dist

      - name: Init Baseline
        run: vendor/bin/catraca init --plain
        continue-on-error: true

      - name: Run Quality Gate
        run: vendor/bin/catraca check --format=github --plain
```

> **Note:** Adjust `runs-on` to match your runner's labels (e.g., `docker`, `ubuntu-latest`, `self-hosted`).

GrumPHP Integration
-------------------

[](#grumphp-integration)

Create a custom task in your project:

```
// app/GrumPHP/CatracaTask.php
use GrumPHP\Runner\TaskResult;
use GrumPHP\Task\AbstractExternalTask;
use GrumPHP\Task\Config\EmptyTaskConfig;
use GrumPHP\Task\Config\TaskConfigInterface;

class CatracaTask extends AbstractExternalTask
{
    public function getConfig(): TaskConfigInterface
    {
        return new EmptyTaskConfig;
    }

    public function run(): TaskResult
    {
        $process = $this->processBuilder->build(['vendor/bin/catraca', 'check', '--plain']);
        $process->run();

        if (!$process->isSuccessful()) {
            return TaskResult::createFailed($this, $this->getContext(), [
                $process->getOutput(),
            ]);
        }

        return TaskResult::createPassed($this, $this->getContext());
    }
}
```

Register it in `grumphp.yml`:

```
# grumphp.yml
grumphp:
  tasks:
    catraca: ~

services:
  CatracaTask:
    class: App\GrumPHP\CatracaTask
    arguments:
      - '@process_builder'
      - '@formatter.raw_process'
    tags:
      - { name: grumphp.task, task: catraca }
```

Programmatic Usage
------------------

[](#programmatic-usage)

```
use B7S\Catraca\Catraca;
use B7S\Catraca\Output\JsonFormatter;
use B7S\Catraca\Output\HumanFormatter;

$catraca = new Catraca('/path/to/project');
$result = $catraca->check();

if ($result->isPass()) {
    echo "All quality gates passed!\n";
} else {
    foreach ($result->getActions() as $action) {
        echo sprintf("[%s] %s\n", $action->type->value, $action->message);
    }
}

// Get structured JSON for AI agents
echo json_encode($result->toArray(), JSON_PRETTY_PRINT);
```

Tool Resolution
---------------

[](#tool-resolution)

Each tool is resolved in this order:

1. **Local** — `vendor/bin/` (project-level)
2. **Global** — `` in `$PATH`
3. **Composer global** — `~/.composer/vendor/bin/`
4. **Skip** — gate is skipped if tool not found

License
-------

[](#license)

MIT

###  Health Score

50

—

FairBetter than 95% of packages

Maintenance98

Actively maintained with recent releases

Popularity22

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity57

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

19

Last Release

8d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/288a61a41a1bfb6d403f2b322e19f3919ac32a316423ca339a8c4b52c72ccbf3?d=identicon)[bruno.souza](/maintainers/bruno.souza)

---

Top Contributors

[![b7s](https://avatars.githubusercontent.com/u/3808846?v=4)](https://github.com/b7s "b7s (102 commits)")

---

Tags

analysisanalyticsbabysitcodelaravelphpqualityratchetstatic analysiscoveragequalityRatchetcigrumphpduplicationcatracababysitgit-action

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/b7s-catraca/health.svg)

```
[![Health](https://phpackages.com/badges/b7s-catraca/health.svg)](https://phpackages.com/packages/b7s-catraca)
```

###  Alternatives

[matomo/matomo

Matomo is the leading Free/Libre open analytics platform

21.6k38.2k](/packages/matomo-matomo)[infection/infection

Infection is a Mutation Testing framework for PHP. The mutation adequacy score can be used to measure the effectiveness of a test set in terms of its ability to detect faults.

2.2k27.9M2.2k](/packages/infection-infection)[magento/magento2-functional-testing-framework

Magento2 Functional Testing Framework

15311.8M36](/packages/magento-magento2-functional-testing-framework)[laraveldaily/filacheck

Static analysis for Filament projects - detect deprecated patterns and code issues

11755.4k](/packages/laraveldaily-filacheck)[acquia/orca

A tool for testing a company's software packages together in the context of a realistic, functioning, best practices Drupal build

30914.4k](/packages/acquia-orca)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
