diff --git a/tools/enforce-judge-gate.mjs b/tools/enforce-judge-gate.mjs index 64f9416..75357d5 100644 --- a/tools/enforce-judge-gate.mjs +++ b/tools/enforce-judge-gate.mjs @@ -40,7 +40,7 @@ import { resolve as pathResolve } from 'node:path'; import { buildArbitrationCard } from './arbitration-card.mjs'; import { formatJudgeObjection } from './objection-format.mjs'; import { buildObjectionFeedback, buildDegradedFeedback } from './objection-delivery.mjs'; -import { parseNegotiationSection } from './negotiation-section.mjs'; +import { parseNegotiationSection, arbitrationRequested } from './negotiation-section.mjs'; // M7 наблюдаемость печати (ремонт «провал печати нигде не логируется»). import { buildSealEntry, logSealAttempt } from './seal-log.mjs'; // Способ B (Фаза 2): судья сам печатает план в Post при валидном mentor-GO (fail-safe). @@ -506,7 +506,8 @@ async function main() { } } catch { /* fail-quiet */ } } - if (isNoGo && n >= JUDGE_ESCALATE_AFTER) { + // SP2d: карточка на 3-м круге (потолок) ИЛИ при маркере `**Арбитраж:**` (любой круг, §7). + if (isNoGo && (n >= JUDGE_ESCALATE_AFTER || arbitrationRequested(String((event && event.tool_input && event.tool_input.content) ?? '')))) { const planContent = String((event && event.tool_input && event.tool_input.content) ?? ''); result = { ...result, message: buildJudgeArbitrationMessage(result.verdict, planContent, n) }; } diff --git a/tools/enforce-mentor-on-plan-write.mjs b/tools/enforce-mentor-on-plan-write.mjs index 1e5f950..c443b8e 100644 --- a/tools/enforce-mentor-on-plan-write.mjs +++ b/tools/enforce-mentor-on-plan-write.mjs @@ -28,7 +28,7 @@ import { resolveSessionId } from './enforce-supreme-gate.mjs'; import { buildArbitrationCard } from './arbitration-card.mjs'; import { formatMentorObjection } from './objection-format.mjs'; import { buildObjectionFeedback, buildDegradedFeedback } from './objection-delivery.mjs'; -import { parseNegotiationSection } from './negotiation-section.mjs'; +import { parseNegotiationSection, arbitrationRequested } from './negotiation-section.mjs'; import { bumpMentorNoGo, MENTOR_ESCALATE_AFTER } from './mentor-nogo-counter.mjs'; // Способ B (Фаза 2): наставник НЕ печатает — на GO лишь записывает подписанное одобрение // (mentor-GO, привязка к plan_hash). Печать делает судья (хук ПОСЛЕ наставника) при валидном mentor-GO. @@ -86,7 +86,9 @@ export function decideMentorObjection({ res, planContent, n } = {}) { const recordMentorGo = !!(res && res.wired === true && res.ok === true && decision === 'GO'); return { block: false, recordMentorGo }; } - const message = n >= MENTOR_ESCALATE_AFTER + // SP2d: карточка арбитража на 3-м круге (потолок) ИЛИ когда контроллер пишет маркер + // `**Арбитраж:**` в плане (выход на ЛЮБОМ круге, дизайн §7). + const message = (n >= MENTOR_ESCALATE_AFTER || arbitrationRequested(String(planContent ?? ''))) ? buildMentorArbitrationMessage(res, String(planContent ?? ''), n) : buildObjectionFeedback({ side: 'mentor', text: formatMentorObjection(res) }); return { block: true, recordMentorGo: false, message }; diff --git a/tools/enforce-mentor-on-plan-write.test.mjs b/tools/enforce-mentor-on-plan-write.test.mjs index 01a8b31..b526b72 100644 --- a/tools/enforce-mentor-on-plan-write.test.mjs +++ b/tools/enforce-mentor-on-plan-write.test.mjs @@ -170,6 +170,13 @@ describe('decideMentorObjection (Фаза 1 — канал замечаний н expect(d.message).toMatch(/не смог дозвониться|недоступен/i); expect(d.recordMentorGo).toBe(false); }); + // SP2d: маркер `**Арбитраж:**` в плане → карточка арбитража на ЛЮБОМ круге (не ждём 3-го). + it('SP2d: маркер арбитража в плане → карточка даже на круге 1', () => { + const planWithMarker = '# план\n## Переговоры\n**Арбитраж:** тупик по шагу 2\n### Круг 1\n**Наставнику:** не согласен'; + const d = decideMentorObjection({ res: noGo, planContent: planWithMarker, n: 1 }); + expect(d.block).toBe(true); + expect(d.message).toContain('Что меняет выбор'); // маркер карточки арбитража, не обычный фидбек + }); }); describe('decideMentorObjection — decision (мерж/Р7)', () => {