laravel/pao
Laravel PAO detects when PHP tools like PHPUnit, Pest, PHPStan, Rector, and Artisan run inside AI agents such as Claude Code or Cursor, swapping verbose output for compact structured JSON. Zero config, works with any PHP project, PHP 8.3+.
Architecture fit PAO is an interception layer, not a library with an API surface. It registers through Composer's autoloader and hooks the output pipeline of PHPUnit, Pest, Paratest, PHPStan, Rector, and — via an auto-discovered service provider — Laravel Artisan. Developers never call it; it activates on environment detection at runtime, so it sits orthogonal to application code and to our domain architecture. That is the correct shape for the problem: output formatting is a cross-cutting concern and should not leak into test or application code.
The architecturally important property is the conditional switch — human terminal sessions get byte-for-byte identical output, agent sessions get compact structured JSON. Adoption is therefore not a workflow migration but a conditional serialization change, and it is a --dev dependency, so production blast radius is effectively zero. The trade-off is that the switch is invisible and implicit, which pushes all risk into the correctness of the detection heuristic and the fidelity of the emitter.
Integration feasibility
Genuinely low effort: one composer require --dev line, no config file, no CI pipeline changes. Constraints are PHP 8.3+, PHPUnit 12–13, Pest 4–5, Laravel 12+. Non-Laravel projects (Symfony, Laminas, vanilla PHP) get the test and static-analysis benefits but not Artisan cleanup. The raw pass-through array means Pest --coverage and --profile output is not lost, which removes the single biggest objection to adoption — information loss for plugin ecosystems. The tool key added in v1.0.2 means downstream consumers can dispatch on source without guessing.
Technical risk
The dominant risk is version coupling to internal output contracts. The release history is essentially a changelog of upstream breakages: a phpunit:^13.3.0 usage fix, Paratest with PHPUnit 13.2.x, non-supported Pest versions, missing PHPStan/Rector output on command paths other than the analyses command, raw output being rewritten by the formatter, and test results rendering on runtime exits. Each is a small patch, but collectively they demonstrate PAO is tracking undocumented internals of five fast-moving tools. Every tool upgrade in our stack becomes a PAO compatibility event, and our upgrade cadence becomes coupled to a dev dependency maintained by a small team.
Secondary risks worth naming explicitly:
raw (coverage/profile) or file_diffs (Rector), which scale with content.Key questions
Stack fit Best fit is a Laravel 12+ monorepo on PHP 8.3+ already running PHPUnit/Pest with PHPStan and Rector in CI. That is where all five tool hooks plus Artisan cleanup land. Second-best fit is a non-Laravel PHP service: we capture the test and static-analysis savings and forgo the Artisan benefit. Worst fit is a repo pinned to PHPUnit ≤11 or PHP 8.2 — there the package installs but does nothing useful, and worse, gives a false sense that agent output is optimized.
Migration path
Treat this as a pilot, not a rollout. Install in one high-velocity repository, keep it out of the golden CI template until the pilot produces numbers, and keep the change reversible by a single composer remove. Because it only activates inside agents, the human review experience is unchanged, which means the change requires no developer retraining to be safe.
Compatibility
Gate adoption on an explicit matrix: PHP version, PHPUnit/Pest major, presence of Paratest or Pest --parallel, PHPStan level, Rector version, and Laravel version for Artisan. Pin PAO to a known-good version in each repo rather than floating, and treat PAO as part of the toolchain upgrade blast radius — when we bump PHPUnit or Pest, PAO is in the same PR review checklist.
Sequencing
Maintenance Low hands-on maintenance, non-trivial coordination cost. There is no config to manage and no infrastructure to run, but there is a standing obligation to track PAO releases against our tool upgrades. Assign a named owner and a monthly review of PAO releases against our pinned toolchain; budget this as a recurring small overhead rather than a one-time install. Vendoring a fork is an option if upstream stalls, but the code is tightly coupled to upstream internals, so a fork is expensive to sustain — prefer version pinning plus a fast-disable runbook.
Support Backed by the Laravel organization and MIT-licensed, but the maturity evidence is README and releases only, with zero declared dependents and a short beta-to-1.0 history. Assume community support, no SLA, and no guaranteed fix turnaround. That is acceptable for dev tooling — the failure mode is degraded agent ergonomics, not production outage — but it must not be treated as a supported platform dependency.
Scaling
The economics scale favorably: token savings are per-invocation and constant-size for summaries, so cost reduction grows linearly with agent activity while output size stays flat as suites grow from hundreds to thousands of tests. Two caveats: raw coverage output and Rector file_diffs scale with content, so large coverage runs will not see the headline reduction; and in parallel/Paratest runs each worker's output path is a separate leak surface, historically the source of several fixes. In CI matrices, savings compound across shards but so does detection-consistency risk.
Failure modes
beforeEach/afterEach failures — a class of bug already fixed twice upstream, so re-verify after every bump.Ramp-up Developer ramp-up is minimal because human-facing output is unchanged, but trust ramp-up is real: engineers reading agent transcripts need to understand that the JSON is a lossy summary and where to look for full detail. Plan a short internal note covering what PAO does, how to disable it locally, what the exit codes mean, and the known PHPStan cap. For the platform team, the ramp-up is a documented pinning-and-upgrade checklist plus a rollback runbook; both should exist before this reaches the org-wide template.
How can I help you explore Laravel packages today?