Files
MtoRagSystem/src/Command/ConfigPatternAuditCommand.php
2026-05-01 19:29:01 +02:00

126 lines
4.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Command;
use App\Config\CorePatternAuditProvider;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'mto:agent:config:audit-patterns',
description: 'Audit remaining core pattern and token usage for developer-policy review'
)]
final class ConfigPatternAuditCommand extends Command
{
public function __construct(private readonly CorePatternAuditProvider $provider)
{
parent::__construct();
}
protected function configure(): void
{
$this
->addOption('json', null, InputOption::VALUE_NONE, 'Render the full audit result as JSON.')
->addOption('details', null, InputOption::VALUE_NONE, 'Render detailed warning rows in the console summary.')
->addOption('all', null, InputOption::VALUE_NONE, 'Also include lower-priority REVIEW findings.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = $this->provider->audit((bool) $input->getOption('all'));
if ((bool) $input->getOption('json')) {
$json = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$output->writeln(is_string($json) ? $json : '{}');
return Command::SUCCESS;
}
$this->renderSummary(
new SymfonyStyle($input, $output),
$result,
(bool) $input->getOption('details'),
(bool) $input->getOption('all')
);
return Command::SUCCESS;
}
/** @param array<string, mixed> $result */
private function renderSummary(SymfonyStyle $io, array $result, bool $details, bool $includeAll): void
{
$io->title('RetrieX core pattern audit');
$summary = is_array($result['summary'] ?? null) ? $result['summary'] : [];
$io->definitionList(
['status' => (string) ($result['status'] ?? 'UNKNOWN')],
['source_files' => (string) ($summary['source_files'] ?? 0)],
['scanned_files' => (string) ($summary['scanned_files'] ?? 0)],
['skipped_files' => (string) ($summary['skipped_files'] ?? 0)],
['warning_findings' => (string) ($summary['warning_findings'] ?? 0)],
['review_findings' => (string) ($summary['review_findings'] ?? 0)],
['total_reported_findings' => (string) ($summary['total_reported_findings'] ?? 0)]
);
$warnings = is_array($result['warnings'] ?? null) ? $result['warnings'] : [];
if ($warnings !== []) {
$io->section('Warnings');
foreach ($warnings as $warning) {
$io->writeln('- ' . (string) $warning);
}
}
if (!$details) {
$note = 'Use --details for warning rows or --json for the complete machine-readable audit.';
if (!$includeAll) {
$note .= ' Add --all to include lower-priority REVIEW findings.';
}
$io->note($note);
return;
}
$this->renderFindingTable($io, 'Warning findings', $result['warning_findings'] ?? []);
if ($includeAll) {
$this->renderFindingTable($io, 'Review findings', $result['review_findings'] ?? []);
}
}
/** @param mixed $findings */
private function renderFindingTable(SymfonyStyle $io, string $title, mixed $findings): void
{
if (!is_array($findings) || $findings === []) {
return;
}
$rows = [];
foreach ($findings as $finding) {
if (!is_array($finding)) {
continue;
}
$rows[] = [
(string) ($finding['severity'] ?? ''),
(string) ($finding['path'] ?? ''),
(string) ($finding['line'] ?? ''),
implode(', ', is_array($finding['calls'] ?? null) ? $finding['calls'] : []),
implode(', ', is_array($finding['markers'] ?? null) ? $finding['markers'] : []),
(string) ($finding['snippet'] ?? ''),
];
}
if ($rows === []) {
return;
}
$io->section($title);
$io->table(['Severity', 'Path', 'Line', 'Calls', 'Markers', 'Snippet'], $rows);
}
}