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.

(cherry picked from commit b142bbdb0d)
This commit is contained in:
root
2026-05-24 01:09:38 -07:00
committed by Garry Tan
parent 4a717250cb
commit d2ad6aa0f9
+5
View File
@@ -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({