659f2b0757
Brain-retro #5 за период 2026-05-24T13:18Z .. 2026-05-26T05:09Z (202 эпизода).
Первый ненулевой reviewer-pass в истории brain-governance (раньше 0/414).
Key findings:
• 184 episodes reviewed via Opus 4.7 ProxyAPI, 18 errors (~$9 cost)
• outcome_reviewed: success 24.5% / soft_success 64.1% / rework 11.4%
• node_quality: correct 30% / disputable 59% / wrong_node 9% / over+under 1.6%
• 93.5% no_self_assessment — confirms self-assessment bug fixed in 752d80af
• Top ignored nodes (wrong_node): #19 Superpowers (5), #18 Pest (3),
#33 claude-md-management (2), #25 Semgrep (2)
• Discipline regressed in long session: regulated 19% → 4.5%
Artifacts:
• tools/brain-retro-batch-reviewer.mjs (new) — direct API batch driver
for retros >50 episodes (canonical Task() spawn impractical at scale).
• docs/observer/notes/2026-05-26-brain-retro.md (new) — full retro note
with 4 candidates A/B/C/D for owner review.
• docs/observer/sanity-checks/2026-05-26.json (new) — sanity Q&A.
• docs/observer/episodes-2026-05.jsonl — 184 episodes mutated with
review.* / outcome_reviewed / outcome_reviewed_source fields.
• docs/observer/STATUS.md — refreshed.
• docs/observer/.pii-counters.json / .read-counter.json / .self-retrospect-counter.json
— bumped by procedure.
Spec: brain-retro skill .claude/skills/brain-retro/SKILL.md.
91 lines
3.2 KiB
JavaScript
91 lines
3.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Brain-retro batch reviewer (one-off, not part of canonical procedure).
|
|
*
|
|
* Reads docs/observer/episodes-YYYY-MM.jsonl, filters episodes in period and
|
|
* without outcome_reviewed, samples N (or all), calls reviewViaDirectApi on
|
|
* each (Opus 4.7 via ProxyAPI), and writes review.* fields + outcome_reviewed
|
|
* + outcome_reviewed_source = "direct_api_batch" back into the JSONL file
|
|
* (in-place line replacement, preserves forward-only forward fields).
|
|
*
|
|
* Usage:
|
|
* node tools/brain-retro-batch-reviewer.mjs <jsonl-path> <cutoff-iso> [limit] [concurrency]
|
|
*
|
|
* Example:
|
|
* node tools/brain-retro-batch-reviewer.mjs docs/observer/episodes-2026-05.jsonl 2026-05-24T13:18:00Z 30 5
|
|
*/
|
|
|
|
import { readFileSync, writeFileSync } from 'fs';
|
|
import { reviewViaDirectApi } from './brain-retro-opus-reviewer.mjs';
|
|
|
|
const [, , filePath, cutoff, limitStr = '30', concStr = '5'] = process.argv;
|
|
if (!filePath || !cutoff) {
|
|
console.error('usage: <jsonl-path> <cutoff-iso> [limit=30] [concurrency=5]');
|
|
process.exit(1);
|
|
}
|
|
const limit = parseInt(limitStr, 10);
|
|
const concurrency = parseInt(concStr, 10);
|
|
|
|
const raw = readFileSync(filePath, 'utf-8');
|
|
const lines = raw.split('\n');
|
|
const lineCount = lines.length;
|
|
|
|
const targets = []; // { idx, episode }
|
|
for (let i = 0; i < lineCount; i++) {
|
|
const line = lines[i];
|
|
if (!line.trim()) continue;
|
|
let ep;
|
|
try { ep = JSON.parse(line); } catch { continue; }
|
|
if (ep.observer_error) continue;
|
|
if (!ep.timestamps?.started_at) continue;
|
|
if (ep.timestamps.started_at < cutoff) continue;
|
|
if (ep.outcome_reviewed) continue;
|
|
targets.push({ idx: i, episode: ep });
|
|
}
|
|
|
|
const total = targets.length;
|
|
const slice = targets.slice(0, limit);
|
|
console.error(`[batch-reviewer] total in period unreviewed: ${total}, processing first ${slice.length} with concurrency ${concurrency}`);
|
|
|
|
let done = 0;
|
|
let errors = 0;
|
|
const startTs = Date.now();
|
|
|
|
async function reviewOne({ idx, episode }) {
|
|
try {
|
|
const review = await reviewViaDirectApi(episode);
|
|
if (review && !review.reviewer_error) {
|
|
episode.review = review;
|
|
episode.outcome_reviewed = review.outcome_reviewed ?? null;
|
|
episode.outcome_reviewed_source = 'direct_api_batch';
|
|
lines[idx] = JSON.stringify(episode);
|
|
done++;
|
|
} else {
|
|
errors++;
|
|
console.error(`[batch-reviewer] ${idx}: null/error from API`);
|
|
}
|
|
} catch (e) {
|
|
errors++;
|
|
console.error(`[batch-reviewer] ${idx}: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
async function runBatched() {
|
|
for (let i = 0; i < slice.length; i += concurrency) {
|
|
const batch = slice.slice(i, i + concurrency);
|
|
await Promise.all(batch.map(reviewOne));
|
|
const elapsed = ((Date.now() - startTs) / 1000).toFixed(1);
|
|
console.error(`[batch-reviewer] progress ${done + errors}/${slice.length} (${elapsed}s)`);
|
|
}
|
|
}
|
|
|
|
await runBatched();
|
|
|
|
// Write file back. Note: we re-serialize EVERY line we mutated, but other lines
|
|
// are kept verbatim (no re-serialization that could alter ordering/escaping).
|
|
writeFileSync(filePath, lines.join('\n'), 'utf-8');
|
|
|
|
const elapsed = ((Date.now() - startTs) / 1000).toFixed(1);
|
|
console.error(`[batch-reviewer] done: ${done} reviewed, ${errors} errors, ${elapsed}s wall-clock`);
|
|
process.exit(0);
|