Model or dataset
laravel/pao avatar
laravel/pao

Laravel PAO turns PHPUnit and Artisan output into agent-sized JSON

PAO is agent-optimized output for PHP testing tools.

727 stars38 forksPHPMIT

At a glance

What is it?
Laravel PAO is a dev dependency that detects when a PHP tool runs inside an AI agent and replaces the human-readable output with compact JSON. It covers PHPUnit, Pest, Paratest, PHPStan, Rector and Laravel Artisan, and it leaves terminal output untouched for people.
Who is it for?
Adopt Laravel PAO when your team drives PHPUnit, Pest, PHPStan or Rector from an AI coding agent and the token cost of dotted progress output is the bottleneck, and when you can guarantee the agent environment variables reach the container. Do not adopt it to read test results yourself in a terminal, since the README states that human output is unchanged, and do not adopt it on a project pinned to PHP below 8.3 or PHPUnit below 12.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why dotted progress output is expensive for an agent

The problem PAO addresses is not test failure, it is output volume. A PHPUnit run over a thousand tests prints a line of dots for every test, and an agent reading that transcript pays for the decoration as well as the facts. The README frames the tool as agent-optimized output for PHP tools and says it replaces the verbose, human-readable output with compact, structured JSON. The claimed saving is up to 99.8 percent fewer AI tokens, and the README makes a stronger structural point: the output is constant-size regardless of how many tests there are. The audience is therefore narrow and specific. It is for engineers driving a PHP test suite from Claude Code, Cursor, Devin, Gemini CLI or a similar tool, and for the CI step that an agent invokes. It is not a test runner, not a reporter with a web interface, and not something a human reads to enjoy the progress bar.

Six tool entry points reached through Composer

PAO plugs into existing tools rather than replacing them. The README states that it works with any PHP project, naming Laravel, Symfony, Laminas, vanilla PHP or anything else that uses PHPUnit, Pest, Paratest, PHPStan, Rector or Laravel Artisan, and that it hooks into PHPUnit, Pest, Paratest, PHPStan and Rector automatically through Composer's autoloader. For Laravel projects, a service provider is auto-discovered to clean Artisan command output. Each tool maps to a different output shape, which the README shows concretely: PHPUnit and Pest report test counts and duration, PHPStan reports an error count plus per-file details carrying a line number, a message and an identifier such as return.type, and Rector is run with its native JSON output format, so it reports changed files and the diffs with applied rector names. The repository layout backs this up: src/, tests/, testbench.yaml, phpunit.xml.dist, phpstan.neon.dist, rector.php and pint.json sit at the root, so the project tests itself with the same toolchain it intercepts.

Installing PAO and seeing the first structured result

Installation is one Composer command, and the README insists on a floor: PHP 8.3 or newer, working with PHPUnit 12 to 13, Pest 4 to 5, Paratest, PHPStan, Rector and Laravel 12 or newer. Add the package as a development dependency.

bash
composer require laravel/pao --dev

From there the README describes no configuration step. PAO hooks into the test and analysis tools through the autoloader, and in a Laravel application the service provider is discovered automatically. The output you should expect is the compact form: a tool name, a result, the test count, the passed count and the duration in milliseconds, with the same keys whichever runner produced the output.

Agent detection is the switch that decides everything

The design decision that shapes the whole package is detection. PAO activates only when it notices an AI agent in the environment, and the README names the agents it looks for: Claude Code, Cursor, Devin, Gemini CLI and others. When a person or a team runs the tools directly in a terminal, the output is completely unchanged, with the same colors, same formatting and the same experience, which the README describes as zero impact on human workflows. That is the sensible default, and it is also the main operational risk. If your agent does not set the variables PAO looks for, you get the old verbose output and no error, so a quiet install is not evidence that detection worked. The escape hatch is documented: set PAO_FORCE=true on the commands your agent runs to activate PAO without detection.

Forwarding detection into Docker, Sail and DDEV

Because detection reads environment variables, containers need those variables forwarded, otherwise a containerised tool run looks like a human run. The README lists Docker, Sail and DDEV as the container setups it expects you to handle, and shows a compose file with the relevant variable names passed into the app service.

yaml
# compose.yaml
services:
    app:
        environment:
            - AI_AGENT
            - CLAUDECODE
            - CURSOR_AGENT

The same effect can be applied per command, which is the form to reach for when only one invocation needs the compact output.

bash
docker compose exec -e AI_AGENT -e CLAUDECODE app vendor/bin/pest

The variable names in these examples are the contract to copy. If your editor exposes a different marker, the compose file has to carry that name too, since the mechanism is a plain environment variable check rather than a handshake with any particular agent.

What the compact form keeps and what it discards

The trade-off is visible in the Artisan example. The README compares an about command before and after PAO, with the undecorated version reported as 2,111 characters and the cleaned one as 535 characters, and says that commands such as about, db:show and migrate:status save up to 75 percent of tokens with the same information and no decoration. What gets removed is presentation: ANSI colors, box-drawing characters, dot separators and excess whitespace. What is kept for failures is the part that matters for fixing code, since failing runs include file paths, line numbers and failure messages, and the PHPStan output carries a per-file error list with an identifier for each entry. Extra output from Pest plugins such as --coverage or --profile is captured, cleaned of ANSI codes and decorations, and included in a raw array, so coverage percentages survive without the terminal art.

Version floors, MIT licensing and the 1.x branch

Two constraints deserve a decision before you install. The first is the version floor: PHP 8.3+, PHPUnit 12 to 13, Pest 4 to 5 and Laravel 12 or newer, which rules the package out of older applications outright. The second is the version ceiling of the tooling, since PHPUnit 11 or earlier is outside the documented range. The licence is MIT, and the README states that the Laravel PAO is open-sourced software licensed under the MIT license, with the text in LICENSE.md. The package is versioned in the 1.x range, with tags v1.1.5 from 2026-09-07, v1.1.4 from 2026-08-10 and v1.1.3 from 2026-07-29, and the default branch is 1.x, so the series is still moving through patch releases. The repository is not archived and the last push was on 2026-10-18. The alternative to PAO is to keep the native output and let the agent parse it, or to use the JSON output that some tools already provide, and the difference is coverage: Rector ships native JSON, while PAO normalises the rest of the toolchain into one shape.

Editorial conclusion

Adopt Laravel PAO when your team drives PHPUnit, Pest, PHPStan or Rector from an AI coding agent and the token cost of dotted progress output is the bottleneck, and when you can guarantee the agent environment variables reach the container. Do not adopt it to read test results yourself in a terminal, since the README states that human output is unchanged, and do not adopt it on a project pinned to PHP below 8.3 or PHPUnit below 12. Before installing, check which environment variables your agent sets, confirm the compose file forwards them, and decide whether PAO_FORCE=true is acceptable for the commands that run without detection.

Frequently asked questions

What is Laravel PAO and what does it do?

Laravel PAO is agent-optimized output for PHP tools. It detects when your tools run inside an AI agent such as Claude Code, Cursor, Devin or Gemini CLI and replaces the verbose human-readable output with compact structured JSON.

How do I install Laravel PAO in a PHP project?

The README shows a single Composer command, composer require laravel/pao --dev. PAO then hooks into PHPUnit, Pest, Paratest, PHPStan and Rector automatically through Composer's autoloader, with a service provider auto-discovered for Laravel Artisan cleanup.

Does Laravel PAO change output for developers running tests in a terminal?

No. The README states that PAO only activates when it detects an AI agent, and that when your team runs tools directly in the terminal the output is completely unchanged, with the same colors and formatting.

How do I use Laravel PAO inside Docker or Sail?

PAO detects agents through environment variables, so the README says to forward them into the container, either in the compose file environment list or per command with docker compose exec -e AI_AGENT -e CLAUDECODE app vendor/bin/pest. If forwarding is not an option, set PAO_FORCE=true on the commands your agent runs.

What PHP and tool versions does Laravel PAO need?

The README states that PHP 8.3 or newer is required, and that it works with PHPUnit 12 to 13, Pest 4 to 5, Paratest, PHPStan, Rector and Laravel 12 or newer.

Official sources

  1. Issues
  2. laravel/pao on GitHub
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/laravel-pao.svg)](https://hysenlabs.com/projects/laravel-pao)

Was this useful?