chore: snapshot pre-v0.2.0 WIP (pathfinder/reflex/metrics/skills improvements)
Baseline for the v0.2.0 self-learning iteration. All 205 tests pass on this state. Subsequent commits in this branch layer the knowledge base, post-mortem coach, and persona narration on top. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,27 +1,75 @@
|
||||
// Per-skill ok/fail counters. Aggregated for the lifetime of the bot
|
||||
// process (best-effort persistence is left for a future iteration —
|
||||
// today's counters reset on restart, which keeps the data store
|
||||
// simple while still being useful for incident bodies and the TUI).
|
||||
// Per-skill ok/fail counters. Persisted under state/<host>/ so the bot's
|
||||
// next run and auto-improvement prompts can learn from prior attempts,
|
||||
// not only the current process lifetime.
|
||||
|
||||
export function createSkillMetrics() {
|
||||
const counts = new Map(); // id → { ok, fail, lastTs }
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { stateDir } from "./config.js";
|
||||
|
||||
function record(id, ok) {
|
||||
const cur = counts.get(id) ?? { ok: 0, fail: 0, lastTs: 0 };
|
||||
const METRICS_PATH = path.join(stateDir, "skill-metrics.json");
|
||||
|
||||
function loadMetrics() {
|
||||
const counts = new Map();
|
||||
try {
|
||||
const raw = fs.readFileSync(METRICS_PATH, "utf8");
|
||||
const parsed = JSON.parse(raw);
|
||||
for (const [id, m] of Object.entries(parsed ?? {})) {
|
||||
counts.set(id, {
|
||||
ok: Number(m.ok ?? 0),
|
||||
fail: Number(m.fail ?? 0),
|
||||
lastTs: Number(m.lastTs ?? 0),
|
||||
lastCode: m.lastCode ?? null,
|
||||
lastDurationMs: Number(m.lastDurationMs ?? 0),
|
||||
totalDurationMs: Number(m.totalDurationMs ?? 0),
|
||||
});
|
||||
}
|
||||
} catch {}
|
||||
return counts;
|
||||
}
|
||||
|
||||
function saveMetrics(counts) {
|
||||
try {
|
||||
fs.mkdirSync(stateDir, { recursive: true });
|
||||
const out = {};
|
||||
for (const [id, m] of counts) out[id] = { ...m };
|
||||
const tmp = `${METRICS_PATH}.tmp`;
|
||||
fs.writeFileSync(tmp, JSON.stringify(out, null, 2));
|
||||
fs.renameSync(tmp, METRICS_PATH);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function createSkillMetrics({ persist = true } = {}) {
|
||||
const counts = persist ? loadMetrics() : new Map(); // id → { ok, fail, lastTs }
|
||||
|
||||
function record(id, ok, { code = null, durationMs = 0 } = {}) {
|
||||
const cur = counts.get(id) ?? { ok: 0, fail: 0, lastTs: 0, lastCode: null, lastDurationMs: 0, totalDurationMs: 0 };
|
||||
if (ok) cur.ok++;
|
||||
else cur.fail++;
|
||||
cur.lastTs = Date.now();
|
||||
cur.lastCode = code ?? cur.lastCode ?? null;
|
||||
cur.lastDurationMs = Math.max(0, Math.round(durationMs || 0));
|
||||
cur.totalDurationMs += cur.lastDurationMs;
|
||||
counts.set(id, cur);
|
||||
if (persist) saveMetrics(counts);
|
||||
}
|
||||
|
||||
function snapshot() {
|
||||
const out = {};
|
||||
for (const [id, m] of counts) out[id] = { ...m };
|
||||
for (const [id, m] of counts) {
|
||||
const total = (m.ok ?? 0) + (m.fail ?? 0);
|
||||
out[id] = {
|
||||
...m,
|
||||
avgDurationMs: total > 0 ? Math.round((m.totalDurationMs ?? 0) / total) : 0,
|
||||
};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
counts.clear();
|
||||
if (persist) {
|
||||
try { fs.unlinkSync(METRICS_PATH); } catch {}
|
||||
}
|
||||
}
|
||||
|
||||
return { record, snapshot, reset };
|
||||
|
||||
Reference in New Issue
Block a user