fix(enforce): hole 5 — tighten nodeMatches to exact/segment match

Brain-retro #5 candidate C, hole 5: nodeMatches() used free-form substring
matching (s.includes(rec) || rec.includes(s)), which matched 'meta-planning'
to a 'planning' recommendation. Tightened to exact match OR matching last
segment after ':' / '#' (skill ns / registry id).

Regression tests preserve: superpowers:writing-plans matches writing-plans,
exact-name matches keep working.
This commit is contained in:
Дмитрий
2026-05-26 11:11:29 +03:00
parent 7e5c297394
commit a846eed9dc
2 changed files with 45 additions and 5 deletions
+14 -5
View File
@@ -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;
}
+31
View File
@@ -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);
});
});