From b142bbdb0d089224ac723e637d3ea98a448570ca Mon Sep 17 00:00:00 2001 From: root Date: Sat, 23 May 2026 23:06:22 +0000 Subject: [PATCH] fix: guard against missing 'intent' field in routing-eval fixtures Two defensive fixes: 1. normalizeText(): return empty string on null/undefined input instead of crashing with 'undefined is not an object (evaluating s.toLowerCase)' 2. loadRoutingFixtures(): validate that parsed fixture has 'intent' as a string before adding to fixtures array. Fixtures with wrong field names (e.g. 'input' instead of 'intent') are now reported as malformed with a helpful error message listing the actual keys found. Root cause: a skill's routing-eval.jsonl used {"input": ...} instead of {"intent": ...}. The JSON parsed fine but the cast to RoutingFixture was unchecked, so fixture.intent was undefined. normalizeText(undefined) then crashed. This made 'gbrain doctor' completely unusable. --- src/core/routing-eval.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/routing-eval.ts b/src/core/routing-eval.ts index 240cc4134..9b567a14e 100644 --- a/src/core/routing-eval.ts +++ b/src/core/routing-eval.ts @@ -96,6 +96,7 @@ export interface RoutingCaseResult { * variants that agents emit in practice. */ export function normalizeText(s: string): string { + if (!s) return ''; return s.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim(); } @@ -298,6 +299,10 @@ export function loadRoutingFixtures(skillsDir: string): LoadResult { if (raw.startsWith('//') || raw.startsWith('#')) continue; try { const obj = JSON.parse(raw) as RoutingFixture; + if (typeof obj.intent !== 'string') { + malformed.push({ file: fixturePath, line: i + 1, raw, error: `missing required field 'intent' (found keys: ${Object.keys(obj).join(', ')})` }); + continue; + } fixtures.push({ ...obj, source: fixturePath }); } catch (err) { malformed.push({