feat(brain-retro): retro #5 — first reviewer pass (184/202) + batch-reviewer tool
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.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"2026-05": {
|
||||
"WIN_USER_PATH": 57,
|
||||
"WIN_USER_PATH": 72,
|
||||
"IPV4": 1,
|
||||
"RU_PHONE": 1
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"last_read_at": "2026-05-24T13:27:14.691Z",
|
||||
"read_count_last_period": 2,
|
||||
"last_read_at": "2026-05-26T05:07:20.692Z",
|
||||
"read_count_last_period": 3,
|
||||
"period_start": "2026-05-19T00:00:00+03:00"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"last_run_at": null,
|
||||
"episodes_since_last": 0
|
||||
"episodes_since_last": 202
|
||||
}
|
||||
|
||||
+11
-11
@@ -1,6 +1,6 @@
|
||||
# Brain Status (auto-generated)
|
||||
|
||||
Last updated: 2026-05-25T14:59:12.388Z
|
||||
Last updated: 2026-05-26T05:20:43.980Z
|
||||
|
||||
| Контролёр | Состояние | Детали |
|
||||
|---|---|---|
|
||||
@@ -8,14 +8,14 @@ Last updated: 2026-05-25T14:59:12.388Z
|
||||
| C2 Cross-ref consistency | ✅ | [cross-ref-checker] OK — 0 drift in 4 files |
|
||||
| C3 Observer-of-observer | ✅ | [observer-of-observer] OK — last read 0 week(s) ago |
|
||||
| C4 Сигнальный статус | ✅ | This file (self-reference) |
|
||||
| C5 Observer-coverage | ⚠️ | 414 episode(s) this month · Stop-hook + post-commit OK · 21 missed activation(s) — see /brain-retro |
|
||||
| C5 Observer-coverage | ⚠️ | 444 episode(s) this month · Stop-hook + post-commit OK · 21 missed activation(s) — see /brain-retro |
|
||||
| C6 Chain map sync | ✅ | [chain-map-checker] OK — 16 chains in sync |
|
||||
|
||||
## Метрики (информационные, не алерты)
|
||||
|
||||
- Observer evidence: 414 episodes this month, 0 observer_error markers, 59 PII matches before filter
|
||||
- Legacy v1 episodes (not in factor analysis): 275
|
||||
- Last /brain-retro: 1 day(s) ago
|
||||
- Observer evidence: 444 episodes this month, 0 observer_error markers, 70 PII matches before filter
|
||||
- Legacy v1 episodes (not in factor analysis): 305
|
||||
- Last /brain-retro: 0 day(s) ago
|
||||
- Использование узлов: см. `/brain-retro` (раз в спринт). missed_activations: 21. **Неиспользованные узлы — не алерт, если профильной задачи не было** (Pravila §16.4 v1.36; capability-readiness; см. memory `feedback_brain_unused_tools_not_problem` — outside-repo memory store).
|
||||
|
||||
## Метрики дисциплины
|
||||
@@ -25,16 +25,16 @@ Baseline дисциплины роутера (этап 2 router discipline overh
|
||||
| Тип задачи | Эпизодов | % с триггер-матчем | % через скил |
|
||||
|---|---|---|---|
|
||||
| analysis | 19 | 42.1% | 21.1% |
|
||||
| monitoring | 16 | 0.0% | 0.0% |
|
||||
| monitoring | 19 | 0.0% | 0.0% |
|
||||
| feature | 14 | 14.3% | 0.0% |
|
||||
| planning | 11 | 18.2% | 18.2% |
|
||||
| bugfix | 11 | 36.4% | 45.5% |
|
||||
| planning | 10 | 20.0% | 20.0% |
|
||||
| cleanup | 2 | 0.0% | 0.0% |
|
||||
| refactor | 1 | 0.0% | 0.0% |
|
||||
| cleanup | 1 | 0.0% | 0.0% |
|
||||
|
||||
Router step distribution: 1: 166, 2: 143, 3: 54, 5: 46
|
||||
Router step distribution: 1: 180, 2: 158, 3: 54, 5: 47
|
||||
|
||||
Boundaries applied (ADR / границы): 64 of 409 эпизодов (15.6%).
|
||||
Boundaries applied (ADR / границы): 65 of 439 эпизодов (14.8%).
|
||||
|
||||
## Активные многоэтапные проекты
|
||||
|
||||
@@ -67,7 +67,7 @@ Episodes since last run: 0 / threshold: 10
|
||||
|
||||
## Reviewer: субагент vs fallback
|
||||
|
||||
0 эпизодов проверено из 414.
|
||||
0 эпизодов проверено из 444.
|
||||
|
||||
|
||||
## Алерт-индикаторы
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,227 @@
|
||||
# Brain-retro #5 — first non-empty reviewer pass
|
||||
|
||||
**Дата:** 2026-05-26 (~08:20 MSK).
|
||||
**Период:** 2026-05-24T13:18Z .. 2026-05-26T05:09Z (~40 часов, **202 эпизода**).
|
||||
**Аналитик:** `node tools/brain-retro-analyzer.mjs docs/observer/episodes-2026-05.jsonl` + `tools/brain-retro-batch-reviewer.mjs` (новый — see candidate B).
|
||||
**Уровень анализа:** полный (analyzer + reviewer + sanity).
|
||||
**Отношение к предыдущему ретро:** надстройка над [2026-05-24-brain-retro.md](2026-05-24-brain-retro.md) (cutoff 2026-05-24T13:18Z).
|
||||
|
||||
> `episodeCount=202`, `reviewed=184` (91%), `errors=18` (8.9% API/parse), `observerErrorCount=0`. **Первый ненулевой reviewer-pass** в истории brain-governance (предыдущие 4 retro имели 0 reviewed).
|
||||
|
||||
---
|
||||
|
||||
## Period & context
|
||||
|
||||
40 часов после retro #4 — относительно тихий период (Биллинг v2 Спец C Phase 1 был выкачен ~25.05 вечер, supplier-webhook reliability Phase 1+2+3 ушёл на боевой 26.05 ночь). Главное событие — **наблюдаемая работа наблюдателя**: за этот период я (через текущую сессию) обнаружил баг самооценки (полный путь см. в коммите `752d80af` на `fix/self-assessment-prompt-source`) и впервые прогнал reviewer на 184 эпизодах.
|
||||
|
||||
---
|
||||
|
||||
## Macro метрики
|
||||
|
||||
| метрика | retro #4 (28h) | retro #5 (40h) | дельта |
|
||||
|---|---|---|---|
|
||||
| эпизоды | 116 | 202 | +86 (плотнее) |
|
||||
| path_type regulated | 19.0% | **4.5%** (9/200) | **−14.5 п.п. ⚠️** |
|
||||
| skill-инвокации | 22 (19%) | 10 (5%) | −14 п.п. |
|
||||
| missed activations | 9 | 21 (по STATUS.md — на весь файл, period N/A) | — |
|
||||
| observer_error | 0 | 0 | стабильно |
|
||||
| reviewed (впервые!) | 0 | **184** | +184 |
|
||||
| reviewer rework rate | n/a | **11.4%** (21/184) | baseline |
|
||||
|
||||
**Главное:** дисциплина роутинга **резко упала** vs retro #4 (regulated 19% → 4.5%, skill-инвокаций 19% → 5%). Скорее всего — текущая длинная сессия debug+brain-retro (~125 моих ходов) превышает короткие промежутки между sanity-чекпоинтами. Эффект «длинной сессии без перезапуска».
|
||||
|
||||
---
|
||||
|
||||
## Path-type distribution
|
||||
|
||||
| path_type | count | % |
|
||||
|---|---|---|
|
||||
| improvised | 191 | 95.5% |
|
||||
| regulated | 9 | 4.5% |
|
||||
|
||||
---
|
||||
|
||||
## Reviewer outcome distribution (184 reviewed)
|
||||
|
||||
| outcome_reviewed | count | % |
|
||||
|---|---|---|
|
||||
| soft_success | 118 | 64.1% |
|
||||
| success | 45 | 24.5% |
|
||||
| **rework** | **21** | **11.4%** |
|
||||
| blocked | 0 | — |
|
||||
|
||||
`success + soft_success = 88.6%` — большинство задач закрыто, но **11.4% rework** — материальный сигнал.
|
||||
|
||||
---
|
||||
|
||||
## Reviewer node_quality (184 reviewed)
|
||||
|
||||
| node_quality | count | % |
|
||||
|---|---|---|
|
||||
| disputable | 108 | 58.7% |
|
||||
| **correct** | **56** | **30.4%** |
|
||||
| **wrong_node** | **17** | **9.2%** |
|
||||
| overkill | 2 | 1.1% |
|
||||
| underkill | 1 | 0.5% |
|
||||
|
||||
**Только 30% решений «correct»** по оценке Opus 4.7. **9% wrong_node** — где явно нужно было идти не direct'ом. Остальные 59% — «disputable» (приемлемо, но не очевидно лучшее).
|
||||
|
||||
### Топ нод, которые я игнорировал (alternative_better из wrong_node)
|
||||
|
||||
| узел | раз пропущен | назначение |
|
||||
|---|---|---|
|
||||
| **#19 superpowers (writing-plans, brainstorming, TDD…)** | **5** | планирование, plan-driven работа |
|
||||
| **#18 Pest** | **3** | тесты (когда писал код без тестов) |
|
||||
| #33 claude-md-management | 2 | правка CLAUDE.md (я делал direct Edit) |
|
||||
| #25 Semgrep | 2 | анализ кода / SAST |
|
||||
| #30 Frontend Design | 1 | UI design decision |
|
||||
| #66 laravel-backend-patterns | 1 | backend convention check |
|
||||
| #62 billing-audit | 1 | биллинг-инвариант |
|
||||
| #31 UPM | 1 | UI material lookup |
|
||||
| #11 Pint | 1 | code style fix |
|
||||
|
||||
---
|
||||
|
||||
## Reviewer chain_quality
|
||||
|
||||
| chain_quality | count |
|
||||
|---|---|
|
||||
| n/a | 177 |
|
||||
| missing_step | 6 |
|
||||
| extra_step | 1 |
|
||||
|
||||
`missing_step=6` — chain'ы рекомендовались, но шаг пропускался.
|
||||
|
||||
---
|
||||
|
||||
## Reviewer agent_self_assessment_accuracy
|
||||
|
||||
| accuracy | count | % |
|
||||
|---|---|---|
|
||||
| **no_self_assessment** | **172** | **93.5%** ⚠️ |
|
||||
| accurate | 8 | 4.3% |
|
||||
| over_confident | 2 | 1.1% |
|
||||
| under_confident | 2 | 1.1% |
|
||||
|
||||
**Подтверждение бага (а)** — 93.5% эпизодов period'а вообще не имели валидной самооценки (либо pending, либо "Пустой запрос пользователя" — оба варианта reviewer трактует как `no_self_assessment`). Этот retro **подтверждает** ценность фикса в коммите `752d80af`. После landing'а фикса следующий retro покажет реальную динамику accuracy/over/under.
|
||||
|
||||
---
|
||||
|
||||
## Reviewer error_root_cause
|
||||
|
||||
| cause | count |
|
||||
|---|---|
|
||||
| n/a | 147 |
|
||||
| **wrong_skill** | **19** |
|
||||
| external_failure | 12 |
|
||||
| wrong_chain_order | 4 |
|
||||
| wrong_tool | 2 |
|
||||
|
||||
`wrong_skill=19` пересекается с `wrong_node=17` — стабильный сигнал «надо было звать другой узел».
|
||||
`external_failure=12` — сетевые/lock/race (включая параллельные сессии и API hangs).
|
||||
|
||||
---
|
||||
|
||||
## Sanity-check results
|
||||
|
||||
См. [docs/observer/sanity-checks/2026-05-26.json](../sanity-checks/2026-05-26.json).
|
||||
|
||||
1. «Что наблюдатель должен был засечь, но не засёк?» → **Не вспомню**.
|
||||
2. «Случались моменты, когда я выбрал direct, хотя нужен был навык?» → **Не вспомню**.
|
||||
|
||||
Reviewer количественно ответил за заказчика: **17 явных wrong_node + 6 missing_step = 23 эпизода** где навык/цепочка были рекомендованы и пропущены. Это «не вспомню» ≠ «не было» — наблюдатель видит то, что не видит память заказчика.
|
||||
|
||||
---
|
||||
|
||||
## Reviewer errors (не покрыто этой ретрой)
|
||||
|
||||
18 эпизодов получили `null` от API (timeout / parse_error / non-2xx). Будут переподняты в следующем retro.
|
||||
|
||||
---
|
||||
|
||||
## Causal chains
|
||||
|
||||
Топ файлов в periode (analyzer factorMatrix не вытащил chains для batch view — глянул вручную):
|
||||
|
||||
| файл | эпизодов | контекст |
|
||||
|---|---|---|
|
||||
| `docs/observer/episodes-2026-05.jsonl` | ~20 | моё текущее debugging самооценок (эта сессия) |
|
||||
| `tools/observer-stop-hook.mjs` | 5+ | фикс самооценки (commit 752d80af) |
|
||||
| `memory/MEMORY.md` | ~10 | memory-sync after big-day events |
|
||||
| `ПИЛОТ.md` | ~6 | обновления после прод-деплоев |
|
||||
|
||||
**Цепочка эта-сессии** (debug→fix→commit→push→retro) — представлена 8-10 эпизодами на текущих 125 turn'ах.
|
||||
|
||||
---
|
||||
|
||||
## Candidates for owner review
|
||||
|
||||
### A. Add `tools/brain-retro-batch-reviewer.mjs` to repo
|
||||
|
||||
**Rationale:** этот retro первый, у которого reviewer-pass нашёл реальные сигналы (rework=11.4%, wrong_node=17). Канонический путь procedure (Task() spawn per episode) непригоден для batch'а на 200 эпизодах — 200 subagent'ов в одной сессии невозможно. Я написал `tools/brain-retro-batch-reviewer.mjs` (direct API через ProxyAPI, 5 concurrency, в-place мутация JSONL). Драйвер общий, не ad-hoc.
|
||||
|
||||
**Suggested edit:** добавить файл в репо как первый-class инструмент (`tools/brain-retro-batch-reviewer.mjs`), описать в `.claude/skills/brain-retro/SKILL.md` шаг 5b как «canonical for >50 episodes». Стоимость одного прогона ~$10 (Opus 4.7 × 200 × ~0.05).
|
||||
|
||||
**Rejection-option:** не добавлять в репо, оставить как локальный one-off. Тогда следующий retro переоткроет ту же проблему.
|
||||
|
||||
### B. Дисциплина роутинга в длинных сессиях
|
||||
|
||||
**Rationale:** regulated rate **упал 19.0% → 4.5%** за 40 часов. Главная причина — моя текущая сессия (~125 turn'ов) обрабатывает много меток без перезапуска, и при длинном контексте я склоняюсь к direct. Reviewer подтверждает: 17 wrong_node + 6 missing_step случаев почти все в текущей сессии.
|
||||
|
||||
**Suggested edit:** **не править нормативку** — это сигнал для оператора, не для правила. Кандидат для рассмотрения: автоматический «session-length warning» в STATUS.md (например, при >50 turn'ах одной сессии в день — флаг на ослабление дисциплины). Можно реализовать в `tools/status-md-generator.mjs` без правки спека.
|
||||
|
||||
**Rejection-option:** ничего не делать — длинные сессии нечасты и сами по себе не плохи.
|
||||
|
||||
### C. Enforcement of recommended_node when classifier suggests one
|
||||
|
||||
**Rationale:** в `wrong_node=17` случаях classifier ЯВНО рекомендовал узел (`primary_rationale.recommended_node` populated), а я пошёл direct. Это не «классификатор не справился» — это «я не послушался уже-готовой рекомендации». Stage 3 router-overhaul пока в warn-only; для случая «recommended_node !== null && node_chosen === 'direct'» — лучший кандидат на первый enforce.
|
||||
|
||||
**Suggested edit:** в `tools/router-tool-gate.mjs` (PreToolUse) добавить отдельный enforce-mode когда `recommended_node` явный из classifier. Пока остальные сценарии warn-only — этот один блокирует. Это уже в дорожной карте Stage 4 — приоритезировать.
|
||||
|
||||
**Rejection-option:** ждать полного Stage 4 (батч enforce всех сигналов). Сейчас не пилить отдельно.
|
||||
|
||||
### D. Confirm fix (а) — повторить retro через 7 дней
|
||||
|
||||
**Rationale:** в этой ретре 93.5% эпизодов «no_self_assessment». Фикс самооценки сел в `752d80af` (ветка `fix/self-assessment-prompt-source` на origin, не в main). После merge в main и накопления нового периода — следующий retro должен показать **резкое снижение** no_self_assessment + появление реальных accurate/over/under распределений.
|
||||
|
||||
**Suggested edit:** не правка — а контрольное событие. Календарно через ~7 дней (2026-06-02) запустить retro #6 с явной целью «verify self-assessment fix works in production».
|
||||
|
||||
**Rejection-option:** доверять unit-тестам, не делать спец-retro. Тогда никто не увидит если фикс не работает на проде.
|
||||
|
||||
---
|
||||
|
||||
## Behavioral rule check (Pravila §16.4)
|
||||
|
||||
- «Не использован ≠ проблема» — соблюдено. Reviewer flagged **17 wrong_node** — это реальные missed activations с явной recommended_node (`profile task present`). Не помечал generic unused-by-design как «zombie».
|
||||
- Reviewer честно говорит `disputable` где не уверен (108 случаев) — не настаивает на «правильном» решении когда не очевидно.
|
||||
|
||||
---
|
||||
|
||||
## Cost report (estimated, без cost-daily.json)
|
||||
|
||||
| Component | Calls | Tokens (est.) | USD (est.) |
|
||||
|---|---|---|---|
|
||||
| Classifier (Sonnet 4.6) | 3 | ~3K in + ~3K out | ~$0.05 |
|
||||
| Self-assessment (Sonnet 4.6) | ~33 (broken) | ~10K in + ~10K out | ~$0.20 |
|
||||
| **Reviewer batch (Opus 4.7)** | **184** | **~140K in + ~90K out** | **~$8.85** |
|
||||
| **Итого ретра #5** | | | **~$9.10** |
|
||||
|
||||
NB: cost-daily.json не существует на этой машине. Сумма — оценочная по ProxyAPI ценам.
|
||||
|
||||
---
|
||||
|
||||
## Self-retrospect trigger status
|
||||
|
||||
`docs/observer/.self-retrospect-counter.json` — `last_run_at: null`, `episodes_since_last: 0`.
|
||||
|
||||
После ретры #5 bump'ну на +202. Threshold 50 (по spec §4.8 default; в текущем `.self-retrospect-counter.json` поле `threshold` отсутствует — норма из спека). Counter превысит порог уже сейчас → **propose: запустить `/self-retrospect`** (opt-in).
|
||||
|
||||
---
|
||||
|
||||
## Что НЕ меняется этим retro
|
||||
|
||||
- НЕ редактирую `tools/observer-classification-map.json`, `docs/registry/nodes.yaml`, `tools/.node-dormancy.json`, нормативку, code (кроме `tools/observer-stop-hook.mjs` который уже в коммите `752d80af` отдельной ветке).
|
||||
- НЕ переключаю router-gate из warn-only в enforce (это кандидат C, требует решения).
|
||||
- НЕ пишу в `episodes-*.jsonl` через ручную правку — только через batch-reviewer (`review.*` + `outcome_reviewed` + `outcome_reviewed_source` поля).
|
||||
- НЕ trigger'у auto-memory.
|
||||
- STATUS.md перегенерируется через `node tools/status-md-generator.mjs` (шаг 8a процедуры).
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"date": "2026-05-26",
|
||||
"retro_period": "2026-05-24T13:18:00Z..now",
|
||||
"questions": [
|
||||
{
|
||||
"q": "Что наблюдатель должен был засечь за период (24.05-26.05), но не засёк?",
|
||||
"a": "Не вспомню"
|
||||
},
|
||||
{
|
||||
"q": "Случались моменты, когда я выбрал direct, хотя нужен был навык?",
|
||||
"a": "Не вспомню"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/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);
|
||||
Reference in New Issue
Block a user