PHPackages                             voku/phpstan-agent-format - 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. voku/phpstan-agent-format

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

voku/phpstan-agent-format
=========================

PHPStan custom error formatter optimized for coding-agent repair loops.

0.1.1(2mo ago)220MITPHP ^8.2

Since Apr 17Compare

[ Source](https://github.com/voku/phpstan-agent-format)[ Packagist](https://packagist.org/packages/voku/phpstan-agent-format)[ RSS](/packages/voku-phpstan-agent-format/feed)WikiDiscussions Synced 3w ago

READMEChangelogDependencies (2)Versions (14)Used By (0)

voku/phpstan-agent-format
=========================

[](#vokuphpstan-agent-format)

> 🚮 Stop feeding AI agents console garbage. Give them structured repair envelopes instead.

`voku/phpstan-agent-format` adds a custom PHPStan formatter named `agent` that emits compact, deterministic repair envelopes for coding agents. The default output uses [TOON](https://github.com/helgesverre/toon) for token-efficient LLM repair loops.

---

⚡ Quick start
-------------

[](#-quick-start)

```
composer require --dev voku/phpstan-agent-format
```

Add the extension to your `phpstan.neon`:

```
includes:
    - vendor/voku/phpstan-agent-format/extension.neon

parameters:
    level: max
    paths:
        - src
```

Run PHPStan with the `agent` formatter:

```
vendor/bin/phpstan analyse --error-format=agent
```

That is enough to get agent-ready TOON output.

---

🤖 Use as an agent skill
-----------------------

[](#-use-as-an-agent-skill)

Paste this into your coding agent's system prompt or context:

> You are fixing PHPStan issues in this repository. Run `vendor/bin/phpstan analyse --error-format=agent`. Read the TOON envelope. Pick **one cluster** at a time. Use `rootCauseSummary`, `repairStrategySummary`, `symbolContext`, snippets, and `contextTrace` to understand the root cause. Make the **smallest safe code change** that removes the reported issue without changing unrelated behavior. Re-run PHPStan after each change and confirm the cluster disappears before moving on.

Recommended agent loop:

1. Read `summary` — understand total issue count and how many clusters were found.
2. Pick one cluster — use `kind`, `ruleIdentifier`, and `affectedFiles` to locate it.
3. Read the representative issue — `symbolContext` and the code snippet show where to act.
4. Make the smallest safe change, then re-run PHPStan.
5. Confirm the cluster is gone before picking the next one.

When an agent needs JSON instead of TOON (e.g. for structured tool output), set `agentFormat.outputMode: json`.

---

🚮 Why this exists
-----------------

[](#-why-this-exists)

### The slot machine

[](#the-slot-machine)

We take PHPStan output, dump it into CI, copy it into an AI prompt, and then act surprised when the agent starts fixing symptoms instead of the problem.

❌ **bad:**

```
vendor/bin/phpstan analyse
```

Then copy 500 lines of terminal output into an AI tool and ask:

```
please fix

```

Congratulations. You built a slot machine.

✅ **better:**

```
vendor/bin/phpstan analyse --error-format=agent
```

Then give the agent structured repair information:

```
Read the summary.
Pick one cluster.
Understand the root cause.
Make the smallest safe change.
Run PHPStan again.

```

This is not magic. This is basic tooling hygiene.

### The problem with unstructured output

[](#the-problem-with-unstructured-output)

Static analysis output for humans is not automatically good input for agents.

Humans can read noisy CI logs and guess the hidden connection between ten errors. We can say: *"Ah, this is probably one nullable value leaking through the service layer."* Then we fix the source.

Agents are not there yet unless we give them better input.

Without structure, the agent sees this:

```
error
error
error
error
error

```

And then it does this:

```
change
change
change
change
break

```

That is not refactoring. That is automated panic.

### What structured output looks like

[](#what-structured-output-looks-like)

❌ **bad** (three separate errors, same root cause, no context):

```
src/UserMailer.php:42  — Parameter #1 $email expects string, string|null given.
src/UserMailer.php:84  — Parameter #1 $email expects string, string|null given.
src/UserRepository.php:129 — Parameter #1 $id expects int, int|null given.

```

Agent reaction: *Make everything nullable?*

No. Bad agent. Sit.

✅ **better** (one cluster, two files, clear repair path):

```
kind: nullable-propagation
rootCauseSummary: Nullable value reaches a non-null expectation.
repairStrategySummary: Constrain nullability earlier or widen the target type if the domain allows it.
affectedFiles:
  - src/UserMailer.php:42
  - src/UserMailer.php:84
  - src/UserRepository.php:129

```

Now we can work.

`voku/phpstan-agent-format` groups related findings, deduplicates repeated symptoms, and includes compact context traces — giving the agent something closer to a repair plan instead of a wall of terminal sadness.

And yes, this is still your job as a developer. The agent should not *decide architecture*. The agent should repair one cluster, with one small change, and then PHPStan should confirm that the problem disappeared.

Same rule as with legacy code: **first think, then change, then check**.

---

⚙️ Configure
------------

[](#️-configure)

```
includes:
    - vendor/voku/phpstan-agent-format/extension.neon

parameters:
    level: max
    paths:
        - src

    agentFormat:
        outputMode: toon
        maxClusters: 30
        maxIssuesPerCluster: 3
        snippetLinesBefore: 2
        snippetLinesAfter: 3
        includeDocblock: false
        includeRelatedDefinition: true
        tokenBudget: 12000
        redactPatterns:
            - '(?i)password\s*=\s*.+'
            - '(?i)api[_-]?key\s*=\s*.+'
            - '(?i)secret\s*=\s*.+'
```

Run:

```
vendor/bin/phpstan analyse --error-format=agent
```

### Configuration options

[](#configuration-options)

OptionDefaultDescription`outputMode``toon`Serializer: `toon`, `json`, `ndjson`, `markdown`, `compact``maxClusters``30`Maximum number of issue clusters in the report`maxIssuesPerCluster``3`Representative issues shown per cluster`snippetLinesBefore``2`Source context lines before the reported line`snippetLinesAfter``3`Source context lines after the reported line`includeDocblock``false`Include nearby docblocks for extra type context`includeRelatedDefinition``true`Attach related class/method/function definition`tokenBudget``12000`Cap report size; reduction is deterministic when exceeded`redactPatterns``[]`Regex patterns to redact secrets from snippets---

📋 Output modes
--------------

[](#-output-modes)

ModeAliasBest for`agentToon``toon`Default — token-efficient agent repair loops`agentJson``json`Structured tool output, JSON-consuming agents`agentNdjson``ndjson`Streaming / line-delimited pipelines`agentMarkdown``markdown`Chat-based flows, paste into Copilot/Claude/ChatGPT`agentCompact``compact`Ultra-compact text for tight token budgetsSwitch the mode per workflow:

```
parameters:
    agentFormat:
        outputMode: markdown
```

```
vendor/bin/phpstan analyse --error-format=agent > phpstan-agent.md
```

Then hand the markdown report to your agent:

```
Read the phpstan-agent-format report.
Treat each cluster as one underlying problem.
Fix root causes instead of patching duplicates.
Keep changes minimal and preserve behavior.
Rerun PHPStan after each fix.

```

---

📐 Envelope shape (TOON)
-----------------------

[](#-envelope-shape-toon)

```
tool: phpstan-agent-format
phpstanVersion: 2.1.50
summary:
  totalIssues: 3
  clusters: 1
  suppressedDuplicates: 2
  tokenStats:
    estimatedTokens: 420
    tokenBudget: 12000
    wasReduced: false
clusters[1]{clusterId,kind,ruleIdentifier,rootCauseSummary,repairStrategySummary,confidence,affectedFiles,representativeIssues,suppressedDuplicateCount}:
  6fdafecf6214,nullable-propagation,argument.type,Nullable value reaches a non-null expectation.,Constrain nullability earlier or widen the target type to accept null.,0.7,[3]: src/UserMailer.php:42 src/UserMailer.php:84 src/UserRepository.php:129,[0]:,2

```

📐 Envelope shape (JSON)
-----------------------

[](#-envelope-shape-json)

Representative issues include structured repair hints inside `symbolContext`, including the targeted parameter/property plus expected and inferred types when PHPStan exposes them.

```
{
  "tool": "phpstan-agent-format",
  "phpstanVersion": "2.1.50",
  "summary": {
    "totalIssues": 3,
    "clusters": 1,
    "suppressedDuplicates": 2,
    "tokenStats": {
      "estimatedTokens": 420,
      "tokenBudget": 12000,
      "wasReduced": false
    }
  },
  "clusters": [
    {
      "clusterId": "6fdafecf6214",
      "kind": "nullable-propagation",
      "ruleIdentifier": "argument.type",
      "rootCauseSummary": "Nullable value reaches a non-null expectation.",
      "repairStrategySummary": "Constrain nullability earlier or widen the target type to accept null.",
      "confidence": 0.7,
      "affectedFiles": ["src/UserMailer.php:42", "src/UserMailer.php:84", "src/UserRepository.php:129"],
      "representativeIssues": [
        {
          "id": "a1b2c3d4",
          "message": "Parameter #1 $email expects string, string|null given.",
          "ruleIdentifier": "argument.type",
          "location": { "file": "src/UserMailer.php", "line": 42 },
          "symbolContext": {
            "className": "UserMailer",
            "methodName": "send",
            "parameterName": "$email",
            "expectedType": "string",
            "inferredType": "string|null"
          }
        }
      ],
      "suppressedDuplicateCount": 2
    }
  ]
}
```

---

🧩 Clustering strategy
---------------------

[](#-clustering-strategy)

First-pass clustering groups by:

- same PHPStan error-identifier family when available, otherwise same rule identifier
- same symbol context when detected
- same file + nearby line bucket
- same type-origin hint

Cluster kinds include:

- 🔴 nullable propagation
- 🟡 missing type declaration
- 🟠 generic/template drift
- 🟣 array shape drift
- 🔵 undefined member from inferred type
- ⚪ invalid offset access
- 🟤 stale ignore/baseline noise
- ⚫ fallback: repeated same-rule same-symbol

---

💰 Token budget reduction strategy
---------------------------------

[](#-token-budget-reduction-strategy)

When estimated tokens exceed `tokenBudget`, degradation is deterministic and ordered:

1. Remove verbose/secondary details (secondary locations)
2. Shrink snippets to focused lines
3. Reduce representative issues per cluster
4. Keep root cause + repair strategy summaries last

---

🔒 Security and privacy
----------------------

[](#-security-and-privacy)

Snippets are redacted using configurable regex patterns before serialization.

```
parameters:
    agentFormat:
        redactPatterns:
            - '(?i)password\s*=\s*.+'
            - '(?i)api[_-]?key\s*=\s*.+'
```

---

📁 Example outputs
-----------------

[](#-example-outputs)

See `/examples/`:

- `agent-toon-example.toon`
- `agent-json-example.json`
- `agent-ndjson-example.ndjson`
- `agent-markdown-example.md`
- `agent-compact-example.txt`

---

🔄 Reformat existing JSON exports
--------------------------------

[](#-reformat-existing-json-exports)

If you already produce `phpstan --error-format=json`, reformat that payload without rebuilding your pipeline:

```
$result = AgentErrorFormatter::formatPhpstanJsonExport($jsonString, $config);
```

The repository CI dogfoods both modes: PHPStan runs once with the default formatter, once with `--error-format=agent`, and again against committed fixture configs that exercise the agent envelope on real output.

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance83

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

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 ~0 days

Total

2

Last Release

87d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6456fe693db197c458272cb758bf78958bc7d3e787ccd59db4bf3cf41654316a?d=identicon)[voku](/maintainers/voku)

### Embed Badge

![Health badge](/badges/voku-phpstan-agent-format/health.svg)

```
[![Health](https://phpackages.com/badges/voku-phpstan-agent-format/health.svg)](https://phpackages.com/packages/voku-phpstan-agent-format)
```

###  Alternatives

[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.5k55.4M9.2k](/packages/larastan-larastan)[phpstan/phpstan-symfony

Symfony Framework extensions and rules for PHPStan

79475.7M2.3k](/packages/phpstan-phpstan-symfony)[phpstan/phpstan-doctrine

Doctrine extensions for PHPStan

67372.8M1.5k](/packages/phpstan-phpstan-doctrine)[shipmonk/dead-code-detector

Dead code detector to find unused PHP code via PHPStan extension. Can automatically remove dead PHP code. Supports libraries like Symfony, Doctrine, PHPUnit etc. Detects dead cycles. Can detect dead code that is tested.

4853.5M98](/packages/shipmonk-dead-code-detector)[tomasvotruba/cognitive-complexity

PHPStan rules to measure cognitive complexity of your classes and methods

1635.6M295](/packages/tomasvotruba-cognitive-complexity)[symplify/phpstan-rules

Set of Symplify rules, type extensions and error formatter for PHPStan

26912.7M343](/packages/symplify-phpstan-rules)

PHPackages © 2026

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