diff --git a/docs/observer/STATUS.md b/docs/observer/STATUS.md index d477b769..781a4941 100644 --- a/docs/observer/STATUS.md +++ b/docs/observer/STATUS.md @@ -1,6 +1,6 @@ # Brain Status (auto-generated) -Last updated: 2026-06-12T15:30:14.970Z +Last updated: 2026-06-12T15:43:51.956Z | Контролёр | Состояние | Детали | |---|---|---| @@ -8,7 +8,7 @@ Last updated: 2026-06-12T15:30:14.970Z | 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 | ✅ | 1121 episode(s) this month · Stop-hook + post-commit OK | +| C5 Observer-coverage | ✅ | 1123 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,8 @@ Last updated: 2026-06-12T15:30:14.970Z | Время | Действие | Причина | |---|---|---| +| 2026-06-12T15:43:16.857Z | 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:31:58.675Z | write:c:/моя/проекты/портал crm/документация/tools/judge-objection-format.test.mjs | §6: правка ЗАКОНА (discipline source edited ad-hoc (outside sealed plan)) требует escape владельца — скил недостаточен | | 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 | @@ -62,13 +64,11 @@ Last updated: 2026-06-12T15:30:14.970Z | 2026-06-12T15:08:13.085Z | 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:02:16.254Z | 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: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 | ## Метрики (информационные, не алерты) -- Observer evidence: 1121 episodes this month, 0 observer_error markers, 5 PII matches before filter -- Legacy v1 episodes (not in factor analysis): 1121 +- Observer evidence: 1123 episodes this month, 0 observer_error markers, 5 PII matches before filter +- Legacy v1 episodes (not in factor analysis): 1123 - 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: 567, 2: 395, 3: 18, 5: 120 +Router step distribution: 1: 569, 2: 395, 3: 18, 5: 120 -Boundaries applied (ADR / границы): 8 of 1100 эпизодов (0.7%). +Boundaries applied (ADR / границы): 8 of 1102 эпизодов (0.7%). ## Активные многоэтапные проекты @@ -110,7 +110,7 @@ Long sessions correlate with discipline drift. Если % regulated просел | Компонент | Токены (in/out) | USD | |---|---|---| -| Classifier (Sonnet 4.6) | 63150/224377 | $3.56 | +| Classifier (Sonnet 4.6) | 63166/224825 | $3.56 | | Self-assessment (Sonnet 4.6) | 0/0 | $0.00 | | Reviewer (Opus 4.7 + fallback) | 0/0 | $0.00 | | **Итого** | | **$3.56** | @@ -126,7 +126,7 @@ Episodes since last run: 542 / threshold: 10 ## Reviewer: субагент vs fallback -0 эпизодов проверено из 1121. +0 эпизодов проверено из 1123. ## Reviewer findings diff --git a/tools/enforce-judge-gate.mjs b/tools/enforce-judge-gate.mjs index 85ddf9dd..7ec39d9d 100644 --- a/tools/enforce-judge-gate.mjs +++ b/tools/enforce-judge-gate.mjs @@ -34,6 +34,33 @@ import { parseVerifiedContext } from './plan-verified-context.mjs'; import { freezeGate } from './freeze-gate.mjs'; import { artifactHasUnresolvedExtracted } from './context-verity.mjs'; import { resolve as pathResolve } from 'node:path'; +// Волна 6 (двухуровневые переговоры §6): эскалация судьи → карточка арбитража. +import { buildArbitrationCard } from './arbitration-card.mjs'; +import { formatJudgeObjection } from './objection-format.mjs'; +import { parseNegotiationSection } from './negotiation-section.mjs'; + +/** + * Волна 6 (§6): сообщение арбитража при 3 NO-GO судьи — дословное замечание судьи + + * позиция контроллера (раздел «Переговоры» плана) + 3 выбора + аффорданс. Чистая. + */ +export function buildJudgeArbitrationMessage(verdict, planContent, n) { + const neg = parseNegotiationSection(planContent); + const position = neg.length ? neg[neg.length - 1].position : '(позиция не указана в разделе «Переговоры» плана)'; + const card = buildArbitrationCard({ + side: 'judge', level: 'L2', round: n, + objectionVerbatim: formatJudgeObjection(verdict) || '(судья не дал текста возражения)', + controllerPositionVerbatim: position, + }); + const opts = card.options.map((o) => `• ${o.label}: ${o.whatChanges}`).join('\n'); + return [ + `[judge-gate] ${card.title}`, + `Замечание судьи:\n${card.objection}`, + `Позиция контроллера:\n${card.position}`, + `Что меняет выбор:\n${opts}`, + 'Можно сказать «объясни подробнее». Решение — через escape/вейвер владельца (не со слов Claude).', + ].join('\n\n'); +} + /** * Чистое решение обёртки. inert/shadow → allow. live-block → finalGate(вердикт, пол): @@ -242,7 +269,7 @@ export async function runJudgeTurn(event, { mode, logImpl = logVerdictLine, warn } } catch { /* escape недоступен → обычное решение судьи */ } const d = decide({ mode, verdict, floorBlocked: false }); - return { block: d.block, message: d.message }; + return { block: d.block, message: d.message, verdict }; } /** @@ -344,7 +371,8 @@ async function main() { if (judgedPlan) { const n = bumpJudgeNoGo({ sessionId: (event && event.session_id) || 'unknown', blocked: !!result.block }); if (result.block && n >= JUDGE_ESCALATE_AFTER) { - result = { ...result, message: `[judge-gate] ЭСКАЛАЦИЯ ВЛАДЕЛЬЦУ: судья отказал ${n} раз(а) подряд по плану. Реши — дай escape на это действие, чтобы продавить, либо подтверди отказ. (round-control C-12)` }; + const planContent = String((event && event.tool_input && event.tool_input.content) ?? ''); + result = { ...result, message: buildJudgeArbitrationMessage(result.verdict, planContent, n) }; } } } catch { /* счётчик best-effort */ } diff --git a/tools/gate-arbitration-wiring.test.mjs b/tools/gate-arbitration-wiring.test.mjs new file mode 100644 index 00000000..f3807aa7 --- /dev/null +++ b/tools/gate-arbitration-wiring.test.mjs @@ -0,0 +1,28 @@ +import { describe, it, expect } from 'vitest'; +import { buildJudgeArbitrationMessage } from './enforce-judge-gate.mjs'; + +describe('buildJudgeArbitrationMessage (wave 6 — judge escalation → card)', () => { + it('включает дословное замечание судьи + позицию контроллера + 3 выбора + L2', () => { + const msg = buildJudgeArbitrationMessage( + { objections: [{ anchor: { ref: 'шаг 3 не проверяем' }, severity: 'heavy' }] }, + '## Переговоры\n### Круг 3\nНе согласен: критерий проверяемый — pest зелёный.', + 3, + ); + expect(msg).toContain('шаг 3 не проверяем'); + expect(msg).toContain('критерий проверяемый — pest зелёный'); + expect(msg).toMatch(/держусь/i); + expect(msg).toMatch(/согласиться/i); + expect(msg).toMatch(/своё/i); + expect(msg).toMatch(/L2/); + }); + it('нет позиции в плане → плейсхолдер, не падает', () => { + const msg = buildJudgeArbitrationMessage({ objections: [{ anchor: { ref: 'X' } }] }, '# План\n', 3); + expect(msg).toContain('X'); + expect(typeof msg).toBe('string'); + }); + it('судья без текста возражения → плейсхолдер', () => { + const msg = buildJudgeArbitrationMessage({ objections: [] }, '## Переговоры\n### Круг 3\nмоя позиция', 3); + expect(msg).toContain('моя позиция'); + expect(typeof msg).toBe('string'); + }); +});