@@ -114,7 +114,11 @@ function assertionToNaturalLanguage(entry: RawAssertEntry): string | null {
114114
115115 switch ( type ) {
116116 case 'skill-trigger' :
117- // Handled separately — not an NL assertion
117+ throw new Error ( staleSkillTriggerMessage ( entry ) ) ;
118+
119+ case 'skill-used' :
120+ case 'not-skill-used' :
121+ // Handled separately as Agent Skills trigger labels.
118122 return null ;
119123
120124 case 'llm-rubric' :
@@ -249,12 +253,46 @@ function assertionToNaturalLanguageList(entry: RawAssertEntry): string[] {
249253 return nl !== null ? [ nl ] : [ ] ;
250254}
251255
252- /**
253- * Extract skill-trigger entries from an assertion list.
254- * Returns entries with type === 'skill-trigger'.
255- */
256- function extractTriggerAssertions ( assertions : RawAssertEntry [ ] ) : RawAssertEntry [ ] {
257- return assertions . filter ( ( a ) => a . type === 'skill-trigger' ) ;
256+ function staleSkillTriggerMessage ( entry : RawAssertEntry ) : string {
257+ const skill = typeof entry . skill === 'string' ? entry . skill . trim ( ) : '' ;
258+ const shouldTrigger = entry . should_trigger !== false ;
259+ if ( ! skill ) {
260+ return "Authored assertion type 'skill-trigger' has been removed. Use 'skill-used' with value: <skill> for expected skill use, or 'not-skill-used' with value: <skill> when the skill must not be used." ;
261+ }
262+ const replacementType = shouldTrigger ? 'skill-used' : 'not-skill-used' ;
263+ return `Authored assertion type 'skill-trigger' has been removed. Replace skill: ${ skill } with type: ${ replacementType } , value: ${ skill } .` ;
264+ }
265+
266+ interface SkillUseAssertion {
267+ readonly skill : string ;
268+ readonly shouldTrigger : boolean ;
269+ }
270+
271+ function skillNameFromValue ( value : unknown ) : string | undefined {
272+ if ( typeof value === 'string' && value . trim ( ) ) {
273+ return value . trim ( ) ;
274+ }
275+ if ( value && typeof value === 'object' && ! Array . isArray ( value ) ) {
276+ const name = ( value as Record < string , unknown > ) . name ;
277+ return typeof name === 'string' && name . trim ( ) ? name . trim ( ) : undefined ;
278+ }
279+ return undefined ;
280+ }
281+
282+ function extractSkillUseAssertions ( assertions : RawAssertEntry [ ] ) : SkillUseAssertion [ ] {
283+ return assertions . flatMap ( ( entry ) => {
284+ if ( entry . type === 'skill-trigger' ) {
285+ throw new Error ( staleSkillTriggerMessage ( entry ) ) ;
286+ }
287+ if ( entry . type !== 'skill-used' && entry . type !== 'not-skill-used' ) {
288+ return [ ] ;
289+ }
290+ const skill = skillNameFromValue ( entry . value ) ;
291+ if ( ! skill ) {
292+ return [ ] ;
293+ }
294+ return [ { skill, shouldTrigger : entry . type === 'skill-used' } ] ;
295+ } ) ;
258296}
259297
260298// ---------------------------------------------------------------------------
@@ -363,9 +401,7 @@ export function transpileEvalYaml(suite: unknown, source = 'EVAL.yaml'): Transpi
363401 const suiteAssertions = rawSuite . assert ?? [ ] ;
364402
365403 // Suite-level NL assertions (appended to every test)
366- const suiteNlAssertions : string [ ] = suiteAssertions
367- . filter ( ( a ) => a . type !== 'skill-trigger' )
368- . flatMap ( assertionToNaturalLanguageList ) ;
404+ const suiteNlAssertions : string [ ] = suiteAssertions . flatMap ( assertionToNaturalLanguageList ) ;
369405
370406 /**
371407 * Helper: get or create the EvalsJsonFile for a skill.
@@ -394,7 +430,7 @@ export function transpileEvalYaml(suite: unknown, source = 'EVAL.yaml'): Transpi
394430 ) ;
395431 }
396432
397- // Collect NL assertions (not skill-trigger )
433+ // Collect NL assertions (not skill-use assertions )
398434 const nlAssertions : string [ ] = [ ] ;
399435
400436 // Prepend test-level criteria as NL assertion
@@ -403,15 +439,15 @@ export function transpileEvalYaml(suite: unknown, source = 'EVAL.yaml'): Transpi
403439 }
404440
405441 for ( const entry of caseAssertions ) {
406- if ( entry . type !== 'skill-trigger ' ) {
442+ if ( entry . type !== 'skill-used' && entry . type !== 'not-skill-used ') {
407443 nlAssertions . push ( ...assertionToNaturalLanguageList ( entry ) ) ;
408444 }
409445 }
410446
411447 // Append suite-level NL assertions
412448 nlAssertions . push ( ...suiteNlAssertions ) ;
413449
414- const triggerJudges = extractTriggerAssertions ( caseAssertions ) ;
450+ const triggerJudges = extractSkillUseAssertions ( caseAssertions ) ;
415451 const { prompt, files : inputFiles } = extractInput ( rawCase ) ;
416452 const expectedOutput = extractExpectedOutput ( rawCase . expected_output ) ;
417453
@@ -428,7 +464,7 @@ export function transpileEvalYaml(suite: unknown, source = 'EVAL.yaml'): Transpi
428464 } ;
429465
430466 if ( triggerJudges . length === 0 ) {
431- // No skill-trigger : place in dominant skill (or _no-skill)
467+ // No skill-use assertion : place in dominant skill (or _no-skill)
432468 // Determine dominant skill by scanning all tests (first occurrence wins)
433469 // We defer this: record with a sentinel and resolve after all tests are processed.
434470 // For now, push to _no-skill; we'll re-assign at the end.
@@ -437,10 +473,8 @@ export function transpileEvalYaml(suite: unknown, source = 'EVAL.yaml'): Transpi
437473 } else {
438474 // Place in each skill with the correct should_trigger value
439475 for ( const tj of triggerJudges ) {
440- const skillName = typeof tj . skill === 'string' ? tj . skill : '_no-skill' ;
441- const shouldTrigger = tj . should_trigger !== false ; // default true
442- const skillFile = getSkillFile ( skillName ) ;
443- skillFile . evals . push ( { ...baseCase , should_trigger : shouldTrigger } ) ;
476+ const skillFile = getSkillFile ( tj . skill ) ;
477+ skillFile . evals . push ( { ...baseCase , should_trigger : tj . shouldTrigger } ) ;
444478 }
445479 }
446480 }
0 commit comments