Files
brain/tools/brain-retro-sanity-generator.mjs
T

89 lines
3.7 KiB
JavaScript

#!/usr/bin/env node
/**
* brain-retro sanity-check candidate generator (Phase 3 Task 19, spec §4.7).
*
* Pure deterministic — read-only, no fs, no LLM. Given the episodes of a
* /brain-retro period, emit up to 5 candidate sanity-check questions for the
* controller (главный Claude) to choose 3-4 from. Questions are asked via
* AskUserQuestion; comments pass through observer-pii-filter before being
* persisted to docs/observer/sanity-checks/YYYY-MM-DD.json.
*
* Threshold: a per-classification question fires when the corresponding
* volume crosses 10 episodes in the period (per spec §4.7).
*
* All questions are in Russian to match the controller-user dialogue.
*/
const MAX_QUESTIONS = 5;
const VOLUME_THRESHOLD = 10;
function classification(ep) {
if (!ep) return null;
return ep?.classifier_output?.task_type
?? ep?.primary_rationale?.task_classification
?? null;
}
const VOLUME_QUESTIONS = [
{
cls: 'bugfix',
q: 'За период было много багов. Что мешает увереннее их отдебагать с первой попытки — недостаток воспроизведения, недостаток observability, или нехватка времени на гипотезы?',
},
{
cls: 'feature',
q: 'За период было много новых фич. Где сейчас бутылочное горлышко — спецификация, code review, тесты, выкат?',
},
{
cls: 'planning',
q: 'За период было много задач на планирование. Это сигнал что план каждой задачи становится сложнее, или что задачи приходят без подготовленного скоупа?',
},
{
cls: 'refactor',
q: 'За период было много рефакторов. Они шли парами с фичами/багами, или это отдельные кампании? Какие самые болезненные участки кода остались?',
},
{
cls: 'security',
q: 'За период было много security-задач. Это плановые сканы перед выкатом, или реакция на находки? Где сейчас самый высокий риск?',
},
{
cls: 'marketing',
q: 'За период было много маркетинговых задач. Кампании окупились по KPI, или работа идёт без замера? Что хотим оптимизировать в следующий период?',
},
];
const META_QUESTIONS = [
'Что наблюдатель должен был засечь за период, но не засёк? Назови один конкретный кейс если есть.',
'За период случались моменты когда контроллер выбрал direct, хотя нужен был навык? Один пример достаточно.',
];
export function generateCandidateQuestions(episodes) {
const eps = Array.isArray(episodes) ? episodes : [];
const counts = new Map();
for (const ep of eps) {
const c = classification(ep);
if (!c) continue;
counts.set(c, (counts.get(c) || 0) + 1);
}
const ranked = [...counts.entries()]
.filter(([_, n]) => n > VOLUME_THRESHOLD)
.sort((a, b) => b[1] - a[1])
.map(([cls]) => cls);
const out = [];
for (const cls of ranked) {
const v = VOLUME_QUESTIONS.find((q) => q.cls === cls);
if (v) out.push(v.q);
if (out.length >= MAX_QUESTIONS) break;
}
for (const meta of META_QUESTIONS) {
if (out.length >= MAX_QUESTIONS) break;
out.push(meta);
}
return out.slice(0, MAX_QUESTIONS);
}