feat(verdict-surface): врезки judge/mentor + регистрация хуков (SP1 Task 5)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-16 14:37:47 +03:00
parent 921bb1146c
commit e6347b2a70
3 changed files with 146 additions and 20 deletions
+120 -20
View File
@@ -50,67 +50,111 @@
{
"matcher": "*",
"hooks": [
{ "type": "command", "command": "node tools/enforce-floor.mjs", "timeout": 10 }
{
"type": "command",
"command": "node tools/enforce-floor.mjs",
"timeout": 10
}
]
},
{
"matcher": "Edit|Write|MultiEdit|NotebookEdit|Bash",
"hooks": [
{ "type": "command", "command": "node tools/enforce-domain-skill-discipline.mjs", "timeout": 10 }
{
"type": "command",
"command": "node tools/enforce-domain-skill-discipline.mjs",
"timeout": 10
}
]
},
{
"matcher": "*",
"hooks": [
{ "type": "command", "command": "node tools/enforce-supreme-gate.mjs", "timeout": 10 }
{
"type": "command",
"command": "node tools/enforce-supreme-gate.mjs",
"timeout": 10
}
]
},
{
"matcher": "*",
"hooks": [
{ "type": "command", "command": "node tools/enforce-snapshot.mjs", "timeout": 10 }
{
"type": "command",
"command": "node tools/enforce-snapshot.mjs",
"timeout": 10
}
]
},
{
"matcher": "Edit|Write|MultiEdit|NotebookEdit",
"hooks": [
{ "type": "command", "command": "node tools/enforce-normative-content-rules.mjs", "timeout": 10 }
{
"type": "command",
"command": "node tools/enforce-normative-content-rules.mjs",
"timeout": 10
}
]
},
{
"matcher": "Read",
"hooks": [
{ "type": "command", "command": "node tools/enforce-read-path-deny.mjs", "timeout": 5 }
{
"type": "command",
"command": "node tools/enforce-read-path-deny.mjs",
"timeout": 5
}
]
},
{
"matcher": "mcp__.*",
"hooks": [
{ "type": "command", "command": "node tools/enforce-mcp-classification.mjs", "timeout": 5 }
{
"type": "command",
"command": "node tools/enforce-mcp-classification.mjs",
"timeout": 5
}
]
},
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "node tools/enforce-verify-gate.mjs", "timeout": 10 }
{
"type": "command",
"command": "node tools/enforce-verify-gate.mjs",
"timeout": 10
}
]
},
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "node tools/enforce-criterion-gate.mjs", "timeout": 10 }
{
"type": "command",
"command": "node tools/enforce-criterion-gate.mjs",
"timeout": 10
}
]
},
{
"matcher": "TodoWrite",
"hooks": [
{ "type": "command", "command": "node tools/enforce-todowrite-skill-verifier.mjs", "timeout": 5 }
{
"type": "command",
"command": "node tools/enforce-todowrite-skill-verifier.mjs",
"timeout": 5
}
]
},
{
"matcher": "AskUserQuestion",
"hooks": [
{ "type": "command", "command": "node tools/askuser-cosmetic-detector.mjs", "timeout": 5 }
{
"type": "command",
"command": "node tools/askuser-cosmetic-detector.mjs",
"timeout": 5
}
]
}
],
@@ -136,54 +180,110 @@
{
"matcher": "Write",
"hooks": [
{ "type": "command", "command": "node tools/enforce-mentor-then-judge.mjs", "timeout": 300 }
{
"type": "command",
"command": "node tools/enforce-mentor-then-judge.mjs",
"timeout": 300
}
]
},
{
"matcher": "Skill",
"hooks": [
{ "type": "command", "command": "node tools/enforce-skill-journaler.mjs", "timeout": 5 }
{
"type": "command",
"command": "node tools/enforce-skill-journaler.mjs",
"timeout": 5
}
]
},
{
"matcher": "AskUserQuestion",
"hooks": [
{ "type": "command", "command": "node tools/enforce-askuser-answer-parser.mjs", "timeout": 5 }
{
"type": "command",
"command": "node tools/enforce-askuser-answer-parser.mjs",
"timeout": 5
}
]
},
{
"matcher": "Edit|Write|MultiEdit|Bash|PowerShell",
"hooks": [
{ "type": "command", "command": "node tools/enforce-floor-escape-consume.mjs", "timeout": 5 }
{
"type": "command",
"command": "node tools/enforce-floor-escape-consume.mjs",
"timeout": 5
}
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "node tools/observer-stop-hook.mjs", "timeout": 60 }
{
"type": "command",
"command": "node tools/observer-stop-hook.mjs",
"timeout": 60
}
]
},
{
"hooks": [
{ "type": "command", "command": "node tools/cost-stop-hook.mjs", "timeout": 10 }
{
"type": "command",
"command": "node tools/cost-stop-hook.mjs",
"timeout": 10
}
]
},
{
"hooks": [
{ "type": "command", "command": "node tools/enforce-coverage-verify.mjs", "timeout": 10 }
{
"type": "command",
"command": "node tools/enforce-coverage-verify.mjs",
"timeout": 10
}
]
},
{
"hooks": [
{
"type": "command",
"command": "node tools/enforce-verdict-ack.mjs",
"timeout": 10
}
]
}
],
"SessionStart": [
{
"hooks": [
{ "type": "command", "command": "node tools/floor-manifest-check.mjs", "timeout": 10 }
{
"type": "command",
"command": "node tools/floor-manifest-check.mjs",
"timeout": 10
}
]
},
{
"hooks": [
{ "type": "command", "command": "node tools/router-embedding-warmup.mjs", "timeout": 30 }
{
"type": "command",
"command": "node tools/router-embedding-warmup.mjs",
"timeout": 30
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "node tools/enforce-verdict-surface.mjs",
"timeout": 10
}
]
}
]
+14
View File
@@ -14,6 +14,8 @@
* без транспорта НЕ кирпичит сессию). Рекомендация §11: обкатка в shadow перед block.
*/
import { readStdin, parseEventJson, exitDecision, runtimeDir } from './enforce-hook-helpers.mjs';
import { classifyJudgeOutcome } from './verdict-outcome-line.mjs';
import { pushVerdict } from './verdict-surface-store.mjs';
import { judgeGateMode, judgeActive, resolveJudgeLlmKey } from './judge-gate-config.mjs';
import { finalGate, logVerdict } from './judge-orchestrator.mjs';
import { runJudge, requiredLensesFor, buildJudgePrompt } from './judge-engine.mjs';
@@ -305,6 +307,18 @@ export async function runJudgeTurn(event, { mode, logImpl = logVerdictLine, warn
errorType: verdict && verdict.errorType,
sealResult,
});
// SP1: громкая видимость вердикта судьи (best-effort, fail-quiet).
if (judged) {
try {
pushVerdict((event && event.session_id) || 'unknown', {
outcome: classifyJudgeOutcome(verdict),
gate: 'judge',
round: (verdict && verdict.round) ?? null,
version: (verdict && verdict.version) ?? null,
reason: (verdict && (verdict.reason || (verdict.verdict && verdict.verdict.recommendation))) || '',
});
} catch { /* fail-quiet */ }
}
if (mode === 'shadow') return { block: false }; // D28: логирует, не блокирует
// Способ B + §9 (degraded): live-block, судья не дозвонился (нет ключа/таймаут) → контроллер
// ИНФОРМИРУЕТСЯ «судья не дозвонился» (не тихо), печати нет. Это НЕ NO-GO (escalation не растёт).
+12
View File
@@ -33,6 +33,8 @@ import { bumpMentorNoGo, MENTOR_ESCALATE_AFTER } from './mentor-nogo-counter.mjs
// Способ B (Фаза 2): наставник НЕ печатает — на GO лишь записывает подписанное одобрение
// (mentor-GO, привязка к plan_hash). Печать делает судья (хук ПОСЛЕ наставника) при валидном mentor-GO.
import { buildMentorGo, persistMentorGo } from './mentor-go-store.mjs';
import { classifyJudgeOutcome } from './verdict-outcome-line.mjs';
import { pushVerdict } from './verdict-surface-store.mjs';
/**
* Волна 7 (§6): сообщение арбитража при 3 NO-GO наставника — дословное замечание +
@@ -211,6 +213,16 @@ async function main() {
registryImpl: () => { try { return loadRegistry({ useCache: false }); } catch { return null; } },
});
if (res && res.ran) {
// SP1: громкая видимость вердикта наставника (best-effort, fail-quiet).
try {
pushVerdict(sess, {
outcome: classifyJudgeOutcome({ wired: res.wired, decision: res.verdict && res.verdict.decision }),
gate: 'mentor',
round: null,
version: null,
reason: (res.verdict && res.verdict.recommendation) || res.reason || '',
});
} catch { /* fail-quiet */ }
// Р7/§3.4: счётчик L1 растёт на NO-GO = содержательный decision='NO-GO' ИЛИ сломанный
// вердикт (ok!==true). degraded (wired:false) не считается (escalation L1 не растёт).
const verdictDecision = res.verdict && res.verdict.decision;