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.