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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user