agent/php/Services/MantisClient.php
Snider 8117d89287 feat(agent/php): CaptureDispatchResultJob + MantisClient + ShaExtractor (#828)
Phase 3 lane: queueable Job that normalises Hermes fetch/event payloads,
extracts the first commit SHA via ShaExtractor (matches both forge URL
and bare 7-40 hex forms), builds a close-note with optional
?\Mod\AgentProfile profile reference, posts the Mantis note via
MantisClient->note(), then transitions the ticket to closed/fixed via
MantisClient->close().

ShaExtractor: extract($modelOutput): {?sha, ?repo, ?forge_url} regex
matcher for forge.lthn.sh commit URLs + bare SHAs.

MantisClient: thin Guzzle wrapper around tasks.lthn.sh REST API
(note + close), Authorization header, base URL via config.

Pest Feature test: 8 tests / 17 assertions covering forge URL parsing,
bare SHA parsing, note/close ordering, repo-hint fallback, missing-SHA
RuntimeException path. Verified via temp Composer harness (no checked-in
vendor/ at this repo level).

Closes tasks.lthn.sh/view.php?id=828

Co-authored-by: Codex <noreply@openai.com>
2026-04-26 00:44:23 +01:00

69 lines
1.7 KiB
PHP

<?php
// SPDX-License-Identifier: EUPL-1.2
declare(strict_types=1);
namespace Core\Mod\Agentic\Services;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use RuntimeException;
class MantisClient
{
public function __construct(
private ?string $baseUrl = null,
private ?string $token = null,
) {}
public function note(int $ticketId, string $text): void
{
$response = $this->request()->post("/api/rest/issues/{$ticketId}/notes", [
'text' => $text,
]);
if (! $response->successful()) {
throw new RuntimeException("Mantis note failed: {$response->status()}");
}
}
public function close(int $ticketId, string $resolution = 'fixed'): void
{
$response = $this->request()->patch("/api/rest/issues/{$ticketId}", [
'status' => [
'name' => 'closed',
],
'resolution' => [
'name' => $resolution,
],
]);
if (! $response->successful()) {
throw new RuntimeException("Mantis close failed: {$response->status()}");
}
}
private function request(): PendingRequest
{
return Http::acceptJson()
->baseUrl($this->resolveBaseUrl())
->withHeaders([
'Authorization' => $this->resolveToken(),
])
->timeout(15);
}
private function resolveBaseUrl(): string
{
return rtrim(
$this->baseUrl ?? (string) config('agentic.mantis.base_url', 'https://tasks.lthn.sh'),
'/',
);
}
private function resolveToken(): string
{
return $this->token ?? (string) config('agentic.mantis.token');
}
}