diff --git a/tools/enforce-classifier-match.mjs b/tools/enforce-classifier-match.mjs index d7aaa4e1..f782e6ad 100644 --- a/tools/enforce-classifier-match.mjs +++ b/tools/enforce-classifier-match.mjs @@ -40,13 +40,22 @@ function nodeMatches(recommendation, toolUse) { if (!recommendation || !toolUse) return false; const rec = normalizeNode(recommendation); if (!rec) return false; + // Hole 5 fix: exact match OR matching last segment after ':' / '#'. + // No generic substring (would match meta-planning to planning). + const matches = (candidate) => { + if (!candidate) return false; + if (candidate === rec) return true; + const recSegs = rec.split(/[:#]/); + const canSegs = candidate.split(/[:#]/); + const recLast = recSegs[recSegs.length - 1]; + const canLast = canSegs[canSegs.length - 1]; + return recLast === canLast; + }; if (toolUse.name === 'Skill') { - const s = normalizeNode(String(toolUse.input && toolUse.input.skill || '')); - if (s && (s === rec || s.includes(rec) || rec.includes(s))) return true; + return matches(normalizeNode(String(toolUse.input && toolUse.input.skill || ''))); } - if (toolUse.name === 'Task') { - const sub = String(toolUse.input && toolUse.input.subagent_type || '').toLowerCase(); - if (sub && rec.includes(sub)) return true; + if (toolUse.name === 'Task' || toolUse.name === 'Agent') { + return matches(String(toolUse.input && toolUse.input.subagent_type || '').toLowerCase()); } return false; } diff --git a/tools/enforce-classifier-match.test.mjs b/tools/enforce-classifier-match.test.mjs index 1a6e90af..fb076224 100644 --- a/tools/enforce-classifier-match.test.mjs +++ b/tools/enforce-classifier-match.test.mjs @@ -125,4 +125,35 @@ describe('enforce-classifier-match / decide', () => { }); expect(r.block).toBe(false); }); + + it('does not match meta-planning to planning recommendation (hole 5)', () => { + const r = decide({ + toolUses: [{ name: 'Skill', input: { skill: 'meta-planning' } }, { name: 'Edit', input: {} }], + recommendation: 'planning', + confidence: 0.9, + assistantText: '', + override: null, + }); + expect(r.block).toBe(true); + }); + + it('matches superpowers:writing-plans to writing-plans recommendation (regression — keep working)', () => { + expect(decide({ + toolUses: [{ name: 'Skill', input: { skill: 'superpowers:writing-plans' } }, { name: 'Edit', input: {} }], + recommendation: 'writing-plans', + confidence: 0.9, + assistantText: '', + override: null, + }).block).toBe(false); + }); + + it('matches exact-name skill regression — keep working', () => { + expect(decide({ + toolUses: [{ name: 'Skill', input: { skill: 'brainstorming' } }, { name: 'Edit', input: {} }], + recommendation: 'brainstorming', + confidence: 0.9, + assistantText: '', + override: null, + }).block).toBe(false); + }); });