@@ -494,7 +494,7 @@ const detectors = [
494494 category : "testing" ,
495495 name : "e2e/ contains .tftest.hcl files" ,
496496 emoji : "✅" ,
497- fixRef : E2E ( "e2etests-tftesthcl -conventions" ) ,
497+ fixRef : E2E ( "e2eteststftesthcl -conventions" ) ,
498498 fn : ( mod ) => {
499499 const e2eDir = join ( mod . path , "e2e" , "tests" ) ;
500500 if ( ! existsSync ( e2eDir ) ) return { pass : false } ;
@@ -577,6 +577,67 @@ function discoverModules() {
577577 return modules . sort ( ( a , b ) => a . id . localeCompare ( b . id ) ) ;
578578}
579579
580+ // ─── fixRef validator ────────────────────────────────────────────────────────
581+
582+ function headingToAnchor ( heading ) {
583+ return heading
584+ . replace ( / ^ # + \s * / , "" )
585+ . replace ( / ` ( [ ^ ` ] * ) ` / g, "$1" )
586+ . toLowerCase ( )
587+ . replace ( / [ ^ \w \s - ] / g, "" )
588+ . replace ( / \s + / g, "-" ) ;
589+ }
590+
591+ function validateFixRefs ( ) {
592+ const filesToParse = [ ...new Set ( detectors . filter ( ( d ) => d . fixRef ) . map ( ( d ) => d . fixRef . file ) ) ] ;
593+ const markerMap = new Map ( ) ; // "file#section" → Set<checkId>
594+ const allMarkerCheckIds = new Set ( ) ;
595+
596+ for ( const relFile of filesToParse ) {
597+ const filePath = join ( ROOT , relFile ) ;
598+ if ( ! existsSync ( filePath ) ) continue ;
599+
600+ const lines = readFileSync ( filePath , "utf-8" ) . split ( "\n" ) ;
601+ let pendingIds = null ;
602+
603+ for ( const line of lines ) {
604+ const m = line . match ( / < ! - - \s * s c o r e c a r d - c h e c k s : \s * ( [ ^ - ] + ?) \s * - - > / ) ;
605+ if ( m ) {
606+ pendingIds = m [ 1 ] . split ( "," ) . map ( ( s ) => s . trim ( ) ) . filter ( Boolean ) ;
607+ continue ;
608+ }
609+ if ( pendingIds && / ^ # { 1 , 6 } \s / . test ( line ) ) {
610+ const section = headingToAnchor ( line ) ;
611+ markerMap . set ( `${ relFile } #${ section } ` , new Set ( pendingIds ) ) ;
612+ for ( const id of pendingIds ) allMarkerCheckIds . add ( id ) ;
613+ pendingIds = null ;
614+ }
615+ }
616+ }
617+
618+ const errors = [ ] ;
619+ const detectorIds = new Set ( detectors . map ( ( d ) => d . id ) ) ;
620+
621+ for ( const d of detectors ) {
622+ if ( ! d . fixRef ) continue ;
623+ const key = `${ d . fixRef . file } #${ d . fixRef . section } ` ;
624+ const checksInSection = markerMap . get ( key ) ;
625+ if ( ! checksInSection ) {
626+ errors . push ( `fixRef "${ key } " not found — no <!-- scorecard-checks: ... --> marker before that heading` ) ;
627+ } else if ( ! checksInSection . has ( d . id ) ) {
628+ errors . push ( `check "${ d . id } " missing from marker at "${ key } " (has: ${ [ ...checksInSection ] . join ( ", " ) } )` ) ;
629+ }
630+ }
631+
632+ for ( const id of allMarkerCheckIds ) {
633+ if ( ! detectorIds . has ( id ) ) {
634+ errors . push ( `marker references unknown check "${ id } " — no detector with that id` ) ;
635+ }
636+ }
637+
638+ return errors ;
639+ }
640+
580641// ─── Fix prompt generator ────────────────────────────────────────────────────
581642
582643function generateFixPrompt ( mod , failingChecks ) {
@@ -620,6 +681,13 @@ function main() {
620681 const filterModules = args . filter ( ( a ) => a . startsWith ( "--module=" ) ) . map ( ( a ) => a . split ( "=" ) [ 1 ] ) ;
621682 const fixMode = args . includes ( "--fix" ) ;
622683
684+ const fixRefErrors = validateFixRefs ( ) ;
685+ if ( fixRefErrors . length > 0 ) {
686+ process . stderr . write ( "❌ fixRef/marker validation failed:\n" ) ;
687+ for ( const e of fixRefErrors ) process . stderr . write ( ` • ${ e } \n` ) ;
688+ process . exit ( 1 ) ;
689+ }
690+
623691 let modules = discoverModules ( ) ;
624692 if ( filterProvider ) {
625693 modules = modules . filter ( ( m ) => m . provider === filterProvider ) ;
0 commit comments