@@ -81,6 +81,8 @@ const OUT_DATA_DIR = path.join(__dirname, 'assets', 'data');
8181const OUT_GUIDES_DIR = path . join ( OUT_DATA_DIR , 'challenges' ) ;
8282const OUT_RESOURCES_DIR = path . join ( __dirname , 'resources' ) ;
8383const OUTCOMES_PATH = path . join ( ROOT , 'outcomes.json' ) ;
84+ const CURRENT_SOURCE_REPO = 'microsoft/frontier-agentic-devops-rvas' ;
85+ const CURRENT_SOURCE_REF = process . env . SOURCE_REF || 'main' ;
8486
8587/* ─── Minimal YAML parser ────────────────────────────────────────────────────
8688 * Handles only the locked meta.yml contract: scalar key-value pairs, block
@@ -231,6 +233,21 @@ function readDirSafe(p) {
231233 catch { return [ ] ; }
232234}
233235
236+ function relativeSourcePath ( file ) {
237+ return path . relative ( ROOT , file ) . replace ( / \\ / g, '/' ) ;
238+ }
239+
240+ function validateLocalSourceAttribution ( meta , metaPath ) {
241+ if ( meta . source_repo !== CURRENT_SOURCE_REPO ) return null ;
242+
243+ const sourcePath = String ( meta . source_path || '' ) ;
244+ const resolved = path . resolve ( ROOT , sourcePath ) ;
245+ if ( ! sourcePath || ! resolved . startsWith ( `${ ROOT } ${ path . sep } ` ) || ! fs . existsSync ( resolved ) ) {
246+ return `${ relativeSourcePath ( metaPath ) } : source_path "${ sourcePath } " does not resolve in ${ CURRENT_SOURCE_REPO } ` ;
247+ }
248+ return null ;
249+ }
250+
234251function rewriteResourceLinksForPages ( text , moduleId ) {
235252 const moduleResources = `resources/${ moduleId } /` ;
236253 return text . replace (
@@ -407,6 +424,11 @@ function main() {
407424
408425 const raw = parseMeta ( fs . readFileSync ( metaPath , 'utf8' ) ) ;
409426 const meta = normaliseMeta ( raw , moduleId , slug ) ;
427+ const sourceAttributionError = validateLocalSourceAttribution ( meta , metaPath ) ;
428+ if ( sourceAttributionError ) {
429+ console . error ( ` ✗ ${ sourceAttributionError } ` ) ;
430+ errors ++ ;
431+ }
410432
411433 // Warn on missing recommended fields
412434 const warnFields = [ 'description' , 'title' , 'track' , 'difficulty' , 'duration_minutes' ] ;
@@ -429,8 +451,8 @@ function main() {
429451 const hasReadme = copyGuideForPages ( path . join ( dir , 'README.md' ) , path . join ( guideDir , 'README.md' ) , moduleId , meta . tier !== 'setup' ) ;
430452 const hasCoach = copyGuideForPages ( path . join ( dir , 'COACH.md' ) , path . join ( guideDir , 'COACH.md' ) , moduleId ) ;
431453
432- if ( ! hasReadme ) { console . warn ( ` ! ${ meta . id } : no README.md (delivery guide)` ) ; warnings ++ ; }
433- if ( ! hasCoach ) { console . warn ( ` ! ${ meta . id } : no COACH.md (coach guide)` ) ; warnings ++ ; }
454+ if ( ! hasReadme ) { console . error ( ` ✗ ${ meta . id } : no README.md (delivery guide)` ) ; errors ++ ; }
455+ if ( ! hasCoach ) { console . error ( ` ✗ ${ meta . id } : no COACH.md (coach guide)` ) ; errors ++ ; }
434456
435457 const trackCfg = ( moduleCfg . tracks && moduleCfg . tracks [ meta . track ] ) || { } ;
436458
@@ -456,6 +478,11 @@ function main() {
456478 references : meta . references . filter ( r => r && r !== 'TODO' ) ,
457479 source_repo : meta . source_repo || '' ,
458480 source_path : meta . source_path || '' ,
481+ source_ref : CURRENT_SOURCE_REF ,
482+ student_source_repo : CURRENT_SOURCE_REPO ,
483+ student_source_path : relativeSourcePath ( path . join ( dir , 'README.md' ) ) ,
484+ coach_source_repo : CURRENT_SOURCE_REPO ,
485+ coach_source_path : relativeSourcePath ( path . join ( dir , 'COACH.md' ) ) ,
459486 license : meta . license ,
460487 student_path : `assets/data/challenges/${ meta . id } /README.md` ,
461488 coach_path : `assets/data/challenges/${ meta . id } /COACH.md` ,
0 commit comments