seedproject-web/api/cli/qa-autofix.php
Carlos Arias 19c0cbb232 qa: auto-fix safe findings + per-finding instructions box
- qa-autofix.php queues Web Designer tasks for safe findings (alt, internal
  links) with 6h dedup; runQaFlow runs it after each pass and kicks the queue
  (QA_AUTOFIX env, on by default)
- adminqa fix() accepts optional human instructions; /admin/qa adds an
  instructions input per finding for the judgment calls
2026-07-24 10:39:15 +00:00

71 lines
2.6 KiB
PHP

<?php
/**
* Auto-queue Web Designer fix tasks for the SAFE QA findings of a run — the ones
* with a clear, low-risk fix (missing/empty alt text, broken internal links).
* Judgment calls (external links, images, SEO opinions) are left for the human
* via the admin's per-finding instructions box.
*
* php api/cli/qa-autofix.php --run=ID -> {"queued": N}
*
* Dedup: skips a finding if a matching fix task is already queued/running, or was
* created in the last 6 hours — so hourly heartbeats never spam or loop on the
* same issue.
*/
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../config.php';
const SAFE = ['a11y', 'link'];
$opts = getopt('', ['run:']);
$runId = (int) ($opts['run'] ?? 0);
if ($runId <= 0) { echo json_encode(['queued' => 0]); exit; }
$pdo = new PDO(
sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', DB_HOST, DB_NAME),
DB_USER,
DB_PASS,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
);
$in = implode(',', array_fill(0, count(SAFE), '?'));
$sel = $pdo->prepare(
"SELECT finding_id, check_type, url, detail, fix_hint
FROM cja_qa_findings
WHERE run_id = ? AND status = 'open' AND check_type IN ($in)"
);
$sel->execute([$runId, ...SAFE]);
$dupe = $pdo->prepare(
"SELECT COUNT(*) FROM cja_tasks
WHERE title = ? AND (status IN ('queued','running') OR created_at > (NOW() - INTERVAL 6 HOUR))"
);
$order = (int) $pdo->query('SELECT COALESCE(MAX(sort_order), 0) FROM cja_tasks')->fetchColumn();
$ins = $pdo->prepare(
'INSERT INTO cja_tasks (title, target_page, prompt, draft, assets, status, sort_order)
VALUES (?, ?, ?, "", ?, "queued", ?)'
);
$mark = $pdo->prepare("UPDATE cja_qa_findings SET status = 'fix_queued' WHERE finding_id = ?");
$queued = 0;
foreach ($sel as $f) {
$url = (string) $f['url'];
$title = 'QA fix: ' . $f['check_type'] . ' — ' . mb_substr($url, 0, 60);
$dupe->execute([$title]);
if ((int) $dupe->fetchColumn() > 0) continue;
$target = (str_starts_with($url, '/') && !str_contains($url, ',') && !str_contains($url, '://'))
? explode('#', explode('?', $url)[0])[0]
: '/';
$prompt = ($f['fix_hint'] !== null && $f['fix_hint'] !== '')
? (string) $f['fix_hint']
: "QA found a {$f['check_type']} issue on {$url}: {$f['detail']}. Fix it.";
$order += 10;
$ins->execute([mb_substr($title, 0, 200), mb_substr($target, 0, 200), $prompt, json_encode(['images' => [], 'videos' => []]), $order]);
$mark->execute([$f['finding_id']]);
$queued++;
}
echo json_encode(['queued' => $queued]);