From eca9be46c890beeb541f02a34932659000e6edbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Fri, 12 Jun 2026 18:43:43 +0300 Subject: [PATCH] feat(mentor): objection-format judge+mentor formatters (waves 4-5) - 9 tests green --- docs/observer/STATUS.md | 16 ++++++------ tools/objection-format.mjs | 24 ++++++++++++++++++ tools/objection-format.test.mjs | 44 +++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 tools/objection-format.mjs create mode 100644 tools/objection-format.test.mjs diff --git a/docs/observer/STATUS.md b/docs/observer/STATUS.md index 87e14c19..d477b769 100644 --- a/docs/observer/STATUS.md +++ b/docs/observer/STATUS.md @@ -1,6 +1,6 @@ # Brain Status (auto-generated) -Last updated: 2026-06-12T15:26:26.340Z +Last updated: 2026-06-12T15:30:14.970Z | Контролёр | Состояние | Детали | |---|---|---| @@ -8,7 +8,7 @@ Last updated: 2026-06-12T15:26:26.340Z | C2 Cross-ref consistency | ✅ | [cross-ref-checker] OK — 0 drift in 4 files | | C3 Observer-of-observer | ✅ | [observer-of-observer] OK — last read 2 week(s) ago | | C4 Сигнальный статус | ✅ | This file (self-reference) | -| C5 Observer-coverage | ✅ | 1120 episode(s) this month · Stop-hook + post-commit OK | +| C5 Observer-coverage | ✅ | 1121 episode(s) this month · Stop-hook + post-commit OK | | C6 Chain map sync | ✅ | [chain-map-checker] OK — 16 chains in sync | ## Кто на посту (оборона М1–М6) @@ -54,6 +54,7 @@ Last updated: 2026-06-12T15:26:26.340Z | Время | Действие | Причина | |---|---|---| +| 2026-06-12T15:30:00.302Z | write:c:/users/administrator/.claude/projects/c---------------------crm-------------/196f7d18-2a2c-4f25-966f-ebc43f93e38 | path «C:/Users/Administrator/.claude/projects/c---------------------crm-------------/196f7d18-2a2c-4f25-966f-ebc43f93e38 | | 2026-06-12T15:25:57.082Z | write:c:/users/administrator/.claude/projects/c---------------------crm-------------/196f7d18-2a2c-4f25-966f-ebc43f93e38 | path «C:/Users/Administrator/.claude/projects/c---------------------crm-------------/196f7d18-2a2c-4f25-966f-ebc43f93e38 | | 2026-06-12T15:22:21.601Z | write:c:/users/administrator/.claude/projects/c---------------------crm-------------/196f7d18-2a2c-4f25-966f-ebc43f93e38 | path «C:/Users/Administrator/.claude/projects/c---------------------crm-------------/196f7d18-2a2c-4f25-966f-ebc43f93e38 | | 2026-06-12T15:17:57.060Z | write:c:/users/administrator/.claude/projects/c---------------------crm-------------/196f7d18-2a2c-4f25-966f-ebc43f93e38 | path «C:/Users/Administrator/.claude/projects/c---------------------crm-------------/196f7d18-2a2c-4f25-966f-ebc43f93e38 | @@ -63,12 +64,11 @@ Last updated: 2026-06-12T15:26:26.340Z | 2026-06-12T15:02:09.672Z | bash:ls $TEMP/claude-economy-196f7d18-2a2c-4f25-966f-ebc43f93e381.json 2>/dev/null && cat $TEMP/claude-economy-196f7d18- | floor: опасная по содержанию команда без аварийного выхода — блок (правило 8); FLOOR-ESCAPE: bash:ls $TEMP/claude-econom | | 2026-06-12T15:01:31.586Z | write:c:/users/administrator/.claude/projects/c---------------------crm-------------/196f7d18-2a2c-4f25-966f-ebc43f93e38 | path «C:/Users/Administrator/.claude/projects/c---------------------crm-------------/196f7d18-2a2c-4f25-966f-ebc43f93e38 | | 2026-06-12T15:01:26.432Z | bash:cat "$TEMP/claude-economy-196f7d18-2a2c-4f25-966f-ebc43f93e381.json" 2>/dev/null \|\| echo "FILE_NOT_FOUND" | floor: опасная по содержанию команда без аварийного выхода — блок (правило 8); FLOOR-ESCAPE: bash:cat "$TEMP/claude-econ | -| 2026-06-12T14:55:22.377Z | bash:ls "$TEMP/claude-economy-196f7d18-2a2c-4f25-966f-ebc43f93e381.json" 2>/dev/null && cat "$TEMP/claude-economy-196f7d | floor: опасная по содержанию команда без аварийного выхода — блок (правило 8); FLOOR-ESCAPE: bash:ls "$TEMP/claude-econo | ## Метрики (информационные, не алерты) -- Observer evidence: 1120 episodes this month, 0 observer_error markers, 5 PII matches before filter -- Legacy v1 episodes (not in factor analysis): 1120 +- Observer evidence: 1121 episodes this month, 0 observer_error markers, 5 PII matches before filter +- Legacy v1 episodes (not in factor analysis): 1121 - Last /brain-retro: 16 day(s) ago - Использование узлов: см. `/brain-retro` (раз в спринт). missed_activations: 0. **Неиспользованные узлы — не алерт, если профильной задачи не было** (Pravila §16.4 v1.36; capability-readiness; см. memory `feedback_brain_unused_tools_not_problem` — outside-repo memory store). @@ -84,9 +84,9 @@ Baseline дисциплины роутера (этап 2 router discipline overh | bugfix | 33 | 12.1% | 18.2% | | cleanup | 1 | 0.0% | 0.0% | -Router step distribution: 1: 566, 2: 395, 3: 18, 5: 120 +Router step distribution: 1: 567, 2: 395, 3: 18, 5: 120 -Boundaries applied (ADR / границы): 8 of 1099 эпизодов (0.7%). +Boundaries applied (ADR / границы): 8 of 1100 эпизодов (0.7%). ## Активные многоэтапные проекты @@ -126,7 +126,7 @@ Episodes since last run: 542 / threshold: 10 ## Reviewer: субагент vs fallback -0 эпизодов проверено из 1120. +0 эпизодов проверено из 1121. ## Reviewer findings diff --git a/tools/objection-format.mjs b/tools/objection-format.mjs new file mode 100644 index 00000000..d50fb37b --- /dev/null +++ b/tools/objection-format.mjs @@ -0,0 +1,24 @@ +#!/usr/bin/env node +/** + * objection-format — дословные сводки возражений для блок-сообщений (П1). + * Чистые форматтеры (презентация, не логика гейта): судья и наставник. + * Fail-safe: мусор/нет возражений → пустая строка. + */ + +/** Дословная сводка возражений судьи (форма parseJudgeResponse: objections[{anchor:{ref},severity}]). */ +export function formatJudgeObjection(verdict) { + const objs = verdict && Array.isArray(verdict.objections) ? verdict.objections : []; + const lines = objs + .filter((o) => o && o.anchor && o.anchor.ref) + .map((o) => `— [${o.severity || 'light'}] ${o.anchor.ref}`); + return lines.join('\n'); +} + +/** Дословная сводка замечания наставника (результат onPlanWrite {ok,wired,reason,verdict}). */ +export function formatMentorObjection(r) { + if (!r || r.wired !== true || r.ok === true) return ''; + const head = typeof r.reason === 'string' && r.reason ? `Замечание наставника: ${r.reason}` : 'Замечание наставника.'; + const objs = r.verdict && Array.isArray(r.verdict.objections) ? r.verdict.objections : []; + const lines = objs.filter((o) => o && o.anchor && o.anchor.ref).map((o) => `— ${o.anchor.ref}`); + return [head, ...lines].join('\n'); +} diff --git a/tools/objection-format.test.mjs b/tools/objection-format.test.mjs new file mode 100644 index 00000000..0305c96f --- /dev/null +++ b/tools/objection-format.test.mjs @@ -0,0 +1,44 @@ +import { describe, it, expect } from 'vitest'; +import { formatJudgeObjection, formatMentorObjection } from './objection-format.mjs'; + +describe('formatJudgeObjection', () => { + it('склеивает дословные возражения (anchor.ref + severity)', () => { + const v = { objections: [ + { verdict: 'NO', anchor: { kind: 'failed_criterion', ref: 'шаг 3 не проверяемый исход' }, severity: 'heavy' }, + ], slots: {} }; + const s = formatJudgeObjection(v); + expect(s).toContain('шаг 3 не проверяемый исход'); + expect(s).toContain('heavy'); + }); + it('несколько возражений — все в выводе', () => { + const v = { objections: [{ anchor: { ref: 'A' }, severity: 'fatal' }, { anchor: { ref: 'B' }, severity: 'light' }] }; + const s = formatJudgeObjection(v); + expect(s).toContain('A'); + expect(s).toContain('B'); + }); + it('нет возражений → пустая строка', () => { + expect(formatJudgeObjection({ objections: [], slots: {} })).toBe(''); + }); + it('мусорный вердикт не роняет', () => { + expect(formatJudgeObjection(null)).toBe(''); + expect(formatJudgeObjection({ objections: 'x' })).toBe(''); + }); + it('возражение без anchor.ref пропускается', () => { + expect(formatJudgeObjection({ objections: [{ severity: 'heavy' }] })).toBe(''); + }); +}); + +describe('formatMentorObjection', () => { + it('собирает дословный reason + замечания вердикта', () => { + const s = formatMentorObjection({ ok: false, wired: true, reason: 'шаг 2 склеен', verdict: { objections: [{ anchor: { ref: 'разбей шаг 2' } }] } }); + expect(s).toContain('шаг 2 склеен'); + expect(s).toContain('разбей шаг 2'); + }); + it('ok=true → пустая строка (нет замечания)', () => { + expect(formatMentorObjection({ ok: true, wired: true })).toBe(''); + }); + it('не wired → пустая строка ($0, наставник не судил)', () => { + expect(formatMentorObjection({ ok: false, wired: false })).toBe(''); + }); + it('мусор не роняет', () => { expect(formatMentorObjection(null)).toBe(''); }); +});