File AI-detected Varaverk defects as deduped reports, surfaced on the AI tab and counted in the coffee report

This commit is contained in:
Gmer4Lfe
2026-08-05 20:48:54 -04:00
parent 9d4a62fa5e
commit 2c5e412d91
5 changed files with 213 additions and 1 deletions
+77
View File
@@ -792,6 +792,83 @@ function vv_ai_scope_ok(string $scope): bool {
return true;
}
// ── AI-filed bug reports ─────────────────────────────────────────────────────────────────────
// When the troubleshooter concludes the evidence shows a defect in Varaverk itself — not a
// setting — it files a report here rather than only saying so in a chat window that closes.
//
// Filed automatically, with the care put into the guard rather than into a review queue: a
// report requires a component and a quoted line of evidence, and identical findings collapse
// onto one record with a seen count. Without that, asking the same question three times files
// three bugs and the pile becomes noise within a week.
//
// Under data/ and therefore gitignored: these quote this installation's logs.
function vv_ai_bugs_dir(): string {
$d = DATA_DIR . '/ai_bugs';
if (!is_dir($d)) @mkdir($d, 0755, true);
return $d;
}
// Same component + same summary is the same finding. Hashing them means a recurring fault
// increments a counter instead of breeding files, and the count is itself the signal: seen 40
// times is a different problem from seen once.
function vv_ai_bug_file(array $r): string {
return vv_ai_bugs_dir() . '/' . substr(sha1(strtolower($r['component'] . '|' . $r['summary'])), 0, 12) . '.json';
}
function vv_ai_bug_write(string $component, string $summary, string $evidence, array $ctx = []): array {
$component = trim($component);
$summary = trim($summary);
$evidence = trim($evidence);
// Evidence is mandatory. A report that cannot quote the line it is based on is an opinion,
// and this file exists to hold findings that can be checked.
if ($component === '' || $summary === '' || $evidence === '') return ['ok' => false];
if (!vv_ai_scope_ok($component)) return ['ok' => false];
$rec = [
'component' => mb_substr($component, 0, 120),
'summary' => mb_substr($summary, 0, 300),
'evidence' => mb_substr($evidence, 0, 2000),
'host' => vv_detect_host(),
'first' => time(),
'last' => time(),
'seen' => 1,
'open' => true,
'context' => array_slice($ctx, 0, 12),
];
$p = vv_ai_bug_file($rec);
if (is_file($p)) {
$old = json_decode((string)@file_get_contents($p), true);
if (is_array($old)) {
$rec['first'] = $old['first'] ?? $rec['first'];
$rec['seen'] = (int)($old['seen'] ?? 0) + 1;
$rec['open'] = $old['open'] ?? true; // dismissing it stays dismissed
}
}
$rec['id'] = basename($p, '.json');
if (@file_put_contents($p, json_encode($rec, JSON_PRETTY_PRINT)) === false) return ['ok' => false];
return ['ok' => true, 'id' => $rec['id'], 'seen' => $rec['seen']];
}
function vv_ai_bugs_list(bool $openOnly = true): array {
$out = [];
foreach ((array)@glob(vv_ai_bugs_dir() . '/*.json') as $f) {
$r = json_decode((string)@file_get_contents($f), true);
if (!is_array($r) || ($openOnly && empty($r['open']))) continue;
$out[] = $r;
}
usort($out, fn($a, $b) => ($b['last'] ?? 0) <=> ($a['last'] ?? 0));
return $out;
}
function vv_ai_bug_set_open(string $id, bool $open): bool {
if (!preg_match('/^[0-9a-f]{12}$/', $id)) return false;
$p = vv_ai_bugs_dir() . '/' . $id . '.json';
$r = json_decode((string)@file_get_contents($p), true);
if (!is_array($r)) return false;
$r['open'] = $open;
return @file_put_contents($p, json_encode($r, JSON_PRETTY_PRINT)) !== false;
}
// ── Incident journal ─────────────────────────────────────────────────────────────────────────
// "We have seen this before, and here is what it was." Appended as you work through logs, and
// fed back the next time the same script is being diagnosed.