From 57a7f55bf15467249a2ebed46355ba9708a0b155 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: Tue, 26 May 2026 11:23:19 +0300 Subject: [PATCH] =?UTF-8?q?fix(enforce):=20hole=207=20=E2=80=94=20=D1=80?= =?UTF-8?q?=D0=B5=D0=BC=D0=BE=D0=BD=D1=82=20=D0=B8=D0=BD=D1=84=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D1=8B=20requir?= =?UTF-8?q?es=20justification=20line?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brain-retro #5 candidate C, hole 7: the 'ремонт инфраструктуры' phrase suppressed ALL rule keys with no constraint. Now requires a 'ремонт: ' line in the same prompt documenting the target. enforce-override-vocab.json: added 'requires_justification: "ремонт:"' to the entry. enforce-hook-helpers.mjs findOverride(): honors requires_justification — when set, the user prompt must contain ' ' or the override is rejected. --- tools/enforce-hook-helpers.mjs | 11 ++++++++++- tools/enforce-hook-helpers.test.mjs | 29 +++++++++++++++++++++++++++++ tools/enforce-override-vocab.json | 3 ++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/tools/enforce-hook-helpers.mjs b/tools/enforce-hook-helpers.mjs index 96f4d6ae..8c0c4330 100644 --- a/tools/enforce-hook-helpers.mjs +++ b/tools/enforce-hook-helpers.mjs @@ -200,7 +200,16 @@ export function findOverride(userPrompt, ruleKey, vocab) { for (const p of v.phrases || []) { if (!p.phrase || !Array.isArray(p.suppresses)) continue; if (!lo.includes(p.phrase.toLowerCase())) continue; - if (p.suppresses.includes(ruleKey)) return p; + if (!p.suppresses.includes(ruleKey)) continue; + if (p.requires_justification) { + // Hole 7 fix: master overrides require a line " " + // in the same prompt documenting what is being repaired. + const prefix = p.requires_justification.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const re = new RegExp(prefix + '\\s+(\\S[^\\n]*)', 'i'); + const m = userPrompt.match(re); + if (!m || !m[1] || !m[1].trim()) continue; + } + return p; } return null; } diff --git a/tools/enforce-hook-helpers.test.mjs b/tools/enforce-hook-helpers.test.mjs index 83d0273f..93223bf7 100644 --- a/tools/enforce-hook-helpers.test.mjs +++ b/tools/enforce-hook-helpers.test.mjs @@ -151,6 +151,35 @@ describe('loadOverrideVocab / findOverride', () => { }); }); +describe('findOverride — requires_justification (hole 7)', () => { + const testVocab = { + phrases: [ + { + phrase: 'ремонт инфраструктуры', + suppresses: ['classifier-mismatch'], + requires_justification: 'ремонт:', + description: 'master kill — requires justification', + }, + ], + }; + + it('rejects when phrase present but justification line missing (hole 7)', () => { + const r = findOverride('ремонт инфраструктуры', 'classifier-mismatch', testVocab); + expect(r).toBeNull(); + }); + + it('accepts when justification line provides target', () => { + const r = findOverride('ремонт инфраструктуры\nремонт: enforce-hook-helpers.mjs', 'classifier-mismatch', testVocab); + expect(r).not.toBeNull(); + expect(r.phrase).toBe('ремонт инфраструктуры'); + }); + + it('rejects when justification line empty after the prefix', () => { + const r = findOverride('ремонт инфраструктуры\nремонт: ', 'classifier-mismatch', testVocab); + expect(r).toBeNull(); + }); +}); + describe('isProductionCodePath', () => { it('classifies tools/*.mjs as production', () => { expect(isProductionCodePath('tools/router-classifier.mjs')).toBe(true); diff --git a/tools/enforce-override-vocab.json b/tools/enforce-override-vocab.json index 4f814c12..44698c72 100644 --- a/tools/enforce-override-vocab.json +++ b/tools/enforce-override-vocab.json @@ -35,7 +35,8 @@ { "phrase": "ремонт инфраструктуры", "suppresses": ["tdd-gate", "verify-before-commit", "verify-before-push", "writing-plans-required", "skill-required", "memory-sync-coverage", "classifier-mismatch", "coverage-skill-match"], - "description": "Bypass all rules (full opt-out). Use only when literally fixing the enforce-infrastructure itself." + "requires_justification": "ремонт:", + "description": "Bypass all rules (full opt-out). Requires 'ремонт: ' line in same prompt." } ] }