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.
This commit is contained in:
root
2026-05-23 23:06:22 +00:00
parent 677142a680
commit b142bbdb0d
+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({