Extending Sheath

Custom Reporters

A reporter turns lint results into output. Sheath ships seven: stylish, json, compact, unix, checkstyle, github, and agent. A custom reporter uses the same process: implement the contract, register it under a name, and --format=<name> selects it.

#The Contract

namespace Forte\Sheath\Contracts;
use Forte\Sheath\Results\LintResult;
interface Reporter
{
public function format(LintResult $result): string;
/**
* @param array<LintResult> $results
*/
public function formatMany(array $results): string;
}

The lint command calls formatMany() once with every result from the run. format() handles a single file. The interface does not define a reporter name; you choose one when you register it.

Each method must return a complete value in the reporter's format. In particular, the built-in JSON reporter returns the same versioned envelope from both methods; format() supplies a one-element results array even when that file is clean.

A minimal reporter:

<?php
namespace App\Sheath\Reporters;
use Forte\Sheath\Contracts\Reporter;
use Forte\Sheath\Results\LintResult;
class CountReporter implements Reporter
{
public function format(LintResult $result): string
{
return sprintf('%s: %d', $result->filePath, count($result->violations));
}
public function formatMany(array $results): string
{
return implode("\n", array_map($this->format(...), $results));
}
}

Extending Forte\Sheath\Reporters\AbstractReporter instead gives you formatMany() for free (it concatenates the per-file output, skipping empty entries) plus a calculateTotals() helper for summary lines; you implement only format() and optionally override combineResults() for a footer.

Each LintResult exposes the file path, its Violation objects (rule ID, message, severity, positions, and whether a fix is available), and parse-error state. See the json reporter for complete result output and compact for a deliberately minimal report.

#Registering

Register in a service provider's boot(), the same place custom rules go:

use App\Sheath\Reporters\CountReporter;
use Forte\Sheath\Facades\Sheath;
public function boot(): void
{
Sheath::registerReporter('count', CountReporter::class);
}

Then:

php artisan sheath:lint --format=count
php artisan sheath:lint --format=count --output=storage/lint-count.txt

The name is the reporter's identity: it is what --format selects and what the reporter listing shows. Registering an existing name replaces it, which also means a project can deliberately swap out a built-in ('stylish' included) with its own.

Class-string registrations are constructed through Laravel's service container when Sheath runs inside an application, so a reporter can take constructor dependencies like any other bound class. You can also register a ready-made instance with Sheath::registerReporter('count', new CountReporter($x)). That instance is returned as-is every time.

#Receiving Run Context

Most reporters only need the results. A reporter whose output depends on run-level options implements one more, optional contract:

namespace Forte\Sheath\Contracts;
interface ReceivesRunContext
{
/**
* @param array<string, mixed> $context
*/
public function receiveRunContext(array $context): void;
}

The lint command checks for the interface and calls it before formatting. Known keys:

Key Type Meaning
maxWarnings int The --max-warnings threshold; -1 when unset

Ignore keys you do not recognize because later releases may add more. The built-in agent reporter reads maxWarnings so its pass/fail verdict matches the run's finding-based outcome. Operational I/O failures still exit with status 1 even when the diagnostic result is passed; use the process exit code to determine whether the command itself succeeded.

#See Also