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

Pao Laravel Package

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+.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin.

composer require laravel/pao --dev
composer require laravel/pao:^1.1 --dev   # prefer an explicit range

Pin a range rather than floating. Given the release history, a PAO bump is often a compatibility patch against upstream internals — you want that to be a deliberate action, not something that lands on composer update the night before a demo.

Verify the baseline before trusting the payload. Run your suite once in a plain terminal and once from inside your agent (Claude Code, Cursor, Devin, Gemini CLI) in the same working directory. Human output should be byte-for-byte unchanged; the agent should get a single JSON object with a tool key. If both runs look human, detection is false-negative and you're burning tokens silently. If both look like JSON, detection is false-positive — check that you're not piping through a wrapper that fakes agent conditions.

Where to look first. There is no configuration file and no public API, so "where to look" is the source. Two files matter:

  • vendor/laravel/pao/composer.json and the autoload.files entry — this is what injects the interception into PHPUnit/Pest/Paratest/PHPStan/Rector regardless of how the binary is invoked.
  • The agent-detection class itself — the README says "detects when your tools are running inside an AI agent" but never documents the mechanism or an override. Read it before you depend on it; this is the single most important unknown in the package.

Constraints to check against your stack before you spend time: PHP 8.3+, PHPUnit 12–13, Pest 4–5, Laravel 12+ for the Artisan cleanup path. Anything older and PAO is inert — recent releases specifically fixed it "not loading for non-supported Pest versions," which means an unsupported version degrades to silence, not an error.

First real use case: the failing-test remediation loop. Run vendor/bin/pest (or php artisan test) through the agent, let it read the compact failure JSON with file paths, line numbers, and messages, have it patch, re-run. That's the highest-leverage thing here and it's where the token math (up to 99.8% on large suites, constant-size output) actually changes how many iterations fit in a context window.

Implementation Patterns

Test loop — the core workflow. A 1,000-test suite collapses to roughly:

{ "tool": "phpunit", "result": "passed", "tests": 1002, "passed": 1002, "duration_ms": 321 }

Because the payload is constant-size, the size of your suite stops being a context-budget variable. Ten agent iterations on a 2,000-test suite costs about the same as ten on a 20-test suite. On failure you get file paths, line numbers, and messages, which is exactly the granularity an agent needs to patch without a second discovery pass.

Parallel runs. pest --parallel and Paratest are supported, including under PHPUnit 13.2.x. Use them — the whole point is that runtime collapses and output doesn't grow. The raw-dots leaks (0.1.6, 0.1.7) and the pest --parallel fatal/beforeEach bug (1.1.0) are historical; keep PAO current if you run parallel in agent sessions.

Static analysis loop, with a guardrail. PHPStan output is grouped by file:

{ "tool": "phpstan", "result": "failed", "errors": 2,
  "error_details": { "/app/Http/Controllers/Controller.php": [ ... ] } }

Grouping by file is the pattern that matters: it lets the agent work the codebase file-by-file rather than fixing one error at a time. Because output is capped at 30 errors, structure your loop as scoped analyses — phpstan analyse app/Http then app/Models — rather than one whole-repo pass the agent will treat as exhaustive.

Rector as review, not autopilot. file_diffs plus applied_rectors gives the agent the actual diff and the rule that produced it, so it can evaluate whether a transformation is safe. Feed it changed_files as the scope and file_diffs as the content. Don't run --dry-run and then --fix as separate agent turns; you already have the diff.

Coverage and profile keep working. Pest --coverage / --profile extra output is captured, ANSI-stripped, and passed through as a raw array of lines. So you can legitimately ask an agent to close a coverage gap without losing the coverage table. Note v1.1.3: PAO won't repeat output flags you already passed — if you want coverage, pass --coverage yourself and it appears in raw.

Artisan context priming. In a Laravel 12+ app, php artisan about, db:show, and migrate:status lose their dot leaders and box characters — a about run is cited at ~74% fewer tokens. This is the cheapest win in the package: an agent onboarding to an unfamiliar app can call about first for free context. Note it strips decoration only; Artisan output stays line-oriented text, not JSON.

Dispatch on .tool and .result, never on exit codes. Exit code handling has been patched more than once (0.1.4 fixed exit code 1 on phpunit; 1.1.1 fixed results displaying on runtime exits). If you're scripting around PAO, read .result and .tool.

vendor/bin/pest | jq -r 'select(.result=="failed") | .tool'

Don't leak it into human surfaces. PAO is a dev dependency for agent sessions. Keep it out of any pipeline that produces artifacts for people — dashboards, CI summaries, Slack notifications, PR comments. Those should see the human formatter. And don't parse PAO JSON in long-lived internal tooling: the schema has already churned within 1.x and there's no documented stability guarantee.

Prompt-level tip. Tell the agent up front that test and analysis commands emit a single JSON object keyed by tool, and that passed/failed lives in result. Otherwise agents waste turns re-running with --verbose or --testdox to make output "readable" — which defeats the purpose.

Gotchas and Tips

The PHPStan 30-error cap is a correctness hazard, not a cosmetic one. An agent will report "clean" or "fixed everything" while errors 31+ remain unseen. Guardrail: compare the errors count against the number of entries in error_details. If the details array is exactly 30, you are truncated — re-run scoped to a subdirectory before letting the agent declare victory. Bake this check into whatever wrapper or instruction set you give the agent.

Detection is undocumented and un-overridable (as far as the README goes). "Detects an AI agent" is a marketing statement until you've read the implementation. Three things to establish before rollout: what signals it checks (env vars, TTY, parent process), whether you can force it on or off per repo, and whether it behaves in CI. False negatives cost tokens quietly; false positives break any script that expects human-formatted output and will surprise developers who didn't opt in.

Version coupling is the real cost of ownership. The release history reads as a catalogue of upstream breakages: phpunit:^13.3.0 usage, Paratest with PHPUnit 13.2.x, unsupported Pest versions, missing PHPStan/Rector output on command paths other than the main analysis command (1.1.4), raw output rewritten by the formatter (1.1.5). PAO tracks undocumented internals of five fast-moving tools. Concretely: upgrade PAO and the underlying tool together, in the same PR, and read the PAO releases page as part of every phpunit/pest/phpstan bump.

Stay current on patch releases — several are silent-quality fixes. 1.1.5 fixed missing traces of test results; 1.1.4 fixed missing PHPStan/Rector output on non-analysis commands; 1.1.1 fixed env values not being read as booleans and results showing on runtime exits. These don't throw; they just give you a less complete payload. A three-minor-versions-behind install looks like it's working.

Deprecations, warnings, and notices are separate arrays — read them. Since 0.1.5 the payload carries deprecations, warnings, notices each with *_details (file, line, message). A suite can report "result": "passed" while carrying all three. If your agent only reads result, it will call a deprecation-laden run clean. Instruct it to treat non-empty detail arrays as work items.

"Constant-size" doesn't cover raw or file_diffs. Those scale with coverage tables and diff content. Coverage on a large app can be the biggest thing in the payload. Budget accordingly, or gate coverage runs to specific agent turns.

Artisan cleanup is scoped. Laravel 12+ only, and it's decoration stripping, not structuring. Symfony, Laminas, and vanilla PHP projects get test and static-analysis benefits but nothing for Artisan-like command output. Also note the cleanup applies to all Artisan commands in an agent session — including output your own commands produce, if you rely on box-drawing or dot leaders as a parsing format anywhere.

No extension points. PAO is an interception layer with no public API, no config file, no formatter registry. If you need a different shape, that's a fork or an upstream PR, not a setting. Treat it as infrastructure you adopt whole or not at all — and don't architect anything that assumes you'll be able to customize its output later.

Debugging checklist when it misbehaves: (1) compare the same command inside vs. outside the agent; (2) confirm the autoload.files hook is present in vendor/laravel/pao/composer.json; (3) in a Laravel app, confirm the service provider was auto-discovered in bootstrap/cache/packages.php; (4) confirm your tool versions are in the supported ranges — an unsupported Pest version degrades to no output rather than an error, which reads like a false-negative detection; (5) check PAO's own version against the releases page before filing anything, because most "bugs" here are already-fixed upstream drift.

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.
bernskioldmedia/laravel-matomo
bernskioldmedia/laravel-harvest
bernskiold/laravel-data-scrubber
bernskiold/laravel-currency-converter
bernskiold/laravel-campaign-monitor
bernskioldmedia/laravel-autotranslate
siberfx/telegram-bot
roy-sandip/laravel-view-extended
gregsphotography/site-builder-core
roy-sandip/laravel-view
anodyne/blade-fluentui-icons
webx-ui/module-catalog-collections
webx-ui/module-catalog-feeds
makidizajnerica/laravel-dto
matviib/scheduler
webx-ui/templates
anodyne/blade-tabler-icons
bref/laravel-websockets
anodyne/blade-hugeicons
1994/flatpack-statamic