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." } ] }