Phox\JevLint\Lint\Linter runs the catalogue over a query and hands back a Report. The
check command goes through it, so a caller here gets the report the CLI prints instead of a
second implementation of it.
use Phox\JevLint\Lint\Linter;
use Phox\JevLint\Query\Query;
use Phox\JevLint\Report\Severity;
$report = Linter::fromEnvironment()->check(Query::fromArray([
'state' => ['ticket' => 'I was charged twice for order A-104.'],
'questions' => [
'refund' => ['type' => 'noul', 'instructions' => 'Does the customer ask for a refund?'],
],
]));
foreach ($report->findings() as $finding) {
printf("%s %s %s\n", $finding->severity->value, $finding->checkId, $finding->message);
}
The query is the request body you would send, so a query built in memory is checked without
going to disk first. Query::fromFile reads one, Query::fromJson takes the body as a string,
and Query::fromArray takes it decoded. Prefer one of the first two where you have the JSON:
criteria written as a JSON object and as a JSON array are different requests and the API
rejects the wrong one, and json_decode to an associative array loses which one it was.
Starting one
Linter::make($client) |
asks its model checks through an SDK client you supply |
Linter::fromEnvironment() |
reads TYPESAFE_API_KEY from the environment or a .env in the working directory and builds its own |
Linter::rulesOnly() |
runs the rules, makes no calls and needs no key |
make takes any Phox\TypeSafe\Client, including the one FakeTypeSafe hands out, which is
how the tests here run the model path without calling anything:
$fake = FakeTypeSafe::make();
$fake->alwaysReply(FakeAnswers::make()->noul('question_compound_judgment', 0.93)->only());
$report = Linter::make($fake->client())->check($query);
A client is built when the run reaches a check that needs one, so fromEnvironment() narrowed
to rules asks for no key.
Narrowing a run
Each of these returns a new linter, so one can be configured once and reused:
->only([...]) |
carry these checks. An id the catalogue does not hold throws, instead of narrowing to nothing and reporting a clean query |
->forJev('1.13') |
the rules for one Jev build. Without it the run carries the newest version the catalogue covers |
->accepting(Config::load('.jevlint.json')) |
the acceptances that set findings aside. The CLI discovers this file beside the query; nothing here does it for you |
->withoutState() |
leave out the checks that read the query's state |
->repeats(3) |
ask each model check this many times, so the report carries the spread |
->reportingCleared() |
carry every model check that ran and cleared, not only the readings near a trigger |
->maxStateChars(20000) |
where a state stops being state and starts being a document |
Where you narrow the query itself with Query::only(), pass the whole query as the second
argument to check(). The checks that judge a state field against the whole query cannot answer
from part of it, so they are left out and the report records it:
$report = $linter->check($whole->only(['refund']), $whole);
Reading the report
$report->toArray() is the document in spec/report.schema.json,
and Output covers what every field means. Read as objects instead:
$report->findings($floor) |
the findings, ordered as the query wrote its questions |
$report->count(Severity::Error) |
how many at one severity, counted whole even where a floor hid them from the list |
$report->cleared(), $report->unstable(), $report->accepted() |
checks that ran without firing, could not decide, or were set aside by the config |
$report->notes(), $report->skippedNotes(), $report->unreachableNotes() |
what the run could not ask, and what it left out |
$report->isComplete(), $report->askedCount() |
whether every check was asked, and how many were |
$report->calls(), $report->tokens() |
what it cost |
A run that lost its calls is not a run that passed, and neither is one that asked nothing. The exit codes belong to the CLI; a caller gating on a report writes the same three conditions:
if (! $report->isComplete() || $report->askedCount() === 0) {
// the run did not check the query
}
if ($report->hasErrors()) {
// the query has a defect the API is documented to reject
}
if ($report->unstable() !== []) {
// a check answered on both sides of its trigger, so another run may differ
}
A report with no findings and askedCount() === 0 is the same object as a clean pass. Nothing
distinguishes them but that count.
What the CLI does that this does not
Reading the arguments, discovering .jevlint.json beside the query, formatting the text report
and turning a report into an exit code all live in Phox\JevLint\Console. The catalogue, the
narrowing, the order of the two linters, the notes saying what was left out and the reconciling
that makes a lost check visible are all in Linter and run wherever it does.