Add an incident journal the troubleshooter reads back, and thinking for diagnosis
This commit is contained in:
@@ -781,6 +781,85 @@ function vv_ai_find_conf_key(string $key): array {
|
||||
return ['ok' => false];
|
||||
}
|
||||
|
||||
// One definition of a valid scope, used by every caller. A scope names something in this page's
|
||||
// own view state — a conf file, a script, a log. It reaches the model as text and, for the
|
||||
// troubleshooting profile, composes a path under LOG_DIR, so `..` is refused outright rather
|
||||
// than left for the containment check downstream to catch. That check stays: this is the first
|
||||
// gate, not the only one.
|
||||
function vv_ai_scope_ok(string $scope): bool {
|
||||
if (!preg_match('#^[A-Za-z0-9 ._/-]{1,80}$#', $scope)) return false;
|
||||
if (str_contains($scope, '..')) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── 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.
|
||||
//
|
||||
// Deliberately NOT part of the AI index. The index reads git-tracked files only, and this lives
|
||||
// under data/ which is gitignored — which is correct twice over: incident notes are about this
|
||||
// installation and should not be pushed, and retrieval by similarity is the wrong lookup here.
|
||||
// The right question is "what has gone wrong with THIS script before", which is an exact match
|
||||
// on the scope, not a vector search. Same reasoning as the standing memory file.
|
||||
//
|
||||
// Markdown rather than a delimited .db because the useful part is prose — a fix is a sentence,
|
||||
// not a field — and it stays hand-editable when a note turns out to be wrong.
|
||||
function vv_ai_incidents_path(): string {
|
||||
return DATA_DIR . '/ai_incidents.md';
|
||||
}
|
||||
|
||||
// One entry, appended. The symptom is captured from what was being asked; the fix is written by
|
||||
// the operator. That split matters: the model's diagnosis is a hypothesis, and writing a
|
||||
// hypothesis into institutional memory as fact is how a wrong answer outlives the incident.
|
||||
function vv_ai_incident_add(string $scope, string $symptom, string $fix): array {
|
||||
$scope = trim($scope);
|
||||
$symptom = trim($symptom);
|
||||
$fix = trim($fix);
|
||||
if ($scope === '' || $fix === '') return ['ok' => false, 'error' => 'scope and fix are required'];
|
||||
if (!vv_ai_scope_ok($scope)) return ['ok' => false, 'error' => 'bad scope'];
|
||||
|
||||
$entry = "\n## " . date('Y-m-d') . ' · ' . $scope . "\n"
|
||||
. ($symptom !== '' ? '**Symptom:** ' . mb_substr($symptom, 0, 400) . "\n" : '')
|
||||
. '**Fix:** ' . mb_substr($fix, 0, 1200) . "\n";
|
||||
|
||||
$p = vv_ai_incidents_path();
|
||||
if (!is_dir(dirname($p)) && !@mkdir(dirname($p), 0755, true)) {
|
||||
return ['ok' => false, 'error' => 'cannot create data dir'];
|
||||
}
|
||||
if (!file_exists($p)) {
|
||||
@file_put_contents($p, "# Incident journal\n\nWhat went wrong, and what actually fixed it."
|
||||
. " Written by the operator from the Scheduler assistant; fed back when the same"
|
||||
. " script is diagnosed again.\n");
|
||||
}
|
||||
if (@file_put_contents($p, $entry, FILE_APPEND | LOCK_EX) === false) {
|
||||
return ['ok' => false, 'error' => 'cannot write journal'];
|
||||
}
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
// Past entries for one scope, newest first. Capped hard: this rides in the prompt alongside a
|
||||
// 120-line log and retrieved passages, and the context budget is already the tight thing.
|
||||
function vv_ai_incidents_for(string $scope, int $max = 4): array {
|
||||
$p = vv_ai_incidents_path();
|
||||
if ($scope === '' || !is_file($p)) return [];
|
||||
|
||||
$blocks = preg_split('/^## /m', (string)@file_get_contents($p));
|
||||
$want = strtolower(trim($scope));
|
||||
$base = strtolower(basename(trim($scope)));
|
||||
$hits = [];
|
||||
foreach ($blocks as $b) {
|
||||
$b = trim($b);
|
||||
if ($b === '' || !str_contains($b, "\n")) continue;
|
||||
[$head] = explode("\n", $b, 2);
|
||||
$head = strtolower($head);
|
||||
// Match the scope as written, or its basename — the chip may carry a path where an
|
||||
// older entry carried only the script name.
|
||||
if (!str_contains($head, $want) && !str_contains($head, $base)) continue;
|
||||
$hits[] = '## ' . $b;
|
||||
}
|
||||
return array_slice(array_reverse($hits), 0, max(1, $max));
|
||||
}
|
||||
|
||||
function vv_ai_job_dir(): string {
|
||||
if (!is_dir(VV_AI_JOB_DIR)) @mkdir(VV_AI_JOB_DIR, 0700, true);
|
||||
return VV_AI_JOB_DIR;
|
||||
|
||||
Reference in New Issue
Block a user