@@ -32,15 +32,15 @@ internal sealed partial class AgentFileSkillsSource : AgentSkillsSource
3232 private const string SkillFileName = "SKILL.md" ;
3333 private const int MaxSearchDepth = 2 ;
3434
35- // "." means the skill directory root itself (no sub-folder descent constraint)
36- private const string RootFolderIndicator = "." ;
35+ // "." means the skill directory root itself (no subdirectory descent constraint)
36+ private const string RootDirectoryIndicator = "." ;
3737
3838 private static readonly string [ ] s_defaultScriptExtensions = [ ".py" , ".js" , ".sh" , ".ps1" , ".cs" , ".csx" ] ;
3939 private static readonly string [ ] s_defaultResourceExtensions = [ ".md" , ".json" , ".yaml" , ".yml" , ".csv" , ".xml" , ".txt" ] ;
4040
41- // Standard sub-folder names per https://agentskills.io/specification#directory-structure
42- private static readonly string [ ] s_defaultScriptFolders = [ "scripts" ] ;
43- private static readonly string [ ] s_defaultResourceFolders = [ "references" , "assets" ] ;
41+ // Standard subdirectory names per https://agentskills.io/specification#directory-structure
42+ private static readonly string [ ] s_defaultScriptDirectories = [ "scripts" ] ;
43+ private static readonly string [ ] s_defaultResourceDirectories = [ "references" , "assets" ] ;
4444
4545 // Matches YAML frontmatter delimited by "---" lines. Group 1 = content between delimiters.
4646 // Multiline makes ^/$ match line boundaries; Singleline makes . match newlines across the block.
@@ -63,8 +63,8 @@ internal sealed partial class AgentFileSkillsSource : AgentSkillsSource
6363 private readonly IEnumerable < string > _skillPaths ;
6464 private readonly HashSet < string > _allowedResourceExtensions ;
6565 private readonly HashSet < string > _allowedScriptExtensions ;
66- private readonly IReadOnlyList < string > _scriptFolders ;
67- private readonly IReadOnlyList < string > _resourceFolders ;
66+ private readonly IReadOnlyList < string > _scriptDirectories ;
67+ private readonly IReadOnlyList < string > _resourceDirectories ;
6868 private readonly AgentFileSkillScriptRunner ? _scriptRunner ;
6969 private readonly ILogger _logger ;
7070
@@ -111,13 +111,13 @@ public AgentFileSkillsSource(
111111 options ? . AllowedScriptExtensions ?? s_defaultScriptExtensions ,
112112 StringComparer . OrdinalIgnoreCase ) ;
113113
114- this . _scriptFolders = options ? . ScriptFolders is not null
115- ? [ .. ValidateAndNormalizeFolderNames ( options . ScriptFolders , this . _logger ) ]
116- : s_defaultScriptFolders ;
114+ this . _scriptDirectories = options ? . ScriptDirectories is not null
115+ ? [ .. ValidateAndNormalizeDirectoryNames ( options . ScriptDirectories , this . _logger ) ]
116+ : s_defaultScriptDirectories ;
117117
118- this . _resourceFolders = options ? . ResourceFolders is not null
119- ? [ .. ValidateAndNormalizeFolderNames ( options . ResourceFolders , this . _logger ) ]
120- : s_defaultResourceFolders ;
118+ this . _resourceDirectories = options ? . ResourceDirectories is not null
119+ ? [ .. ValidateAndNormalizeDirectoryNames ( options . ResourceDirectories , this . _logger ) ]
120+ : s_defaultResourceDirectories ;
121121
122122 this . _scriptRunner = scriptRunner ;
123123 }
@@ -303,41 +303,41 @@ private bool TryParseFrontmatter(string content, string skillFilePath, [NotNullW
303303 }
304304
305305 /// <summary>
306- /// Scans configured resource folders within a skill directory for resource files matching the configured extensions.
306+ /// Scans configured resource directories within a skill directory for resource files matching the configured extensions.
307307 /// </summary>
308308 /// <remarks>
309- /// By default, scans <c>references/</c> and <c>assets/</c> sub-folders as specified by the
309+ /// By default, scans <c>references/</c> and <c>assets/</c> subdirectories as specified by the
310310 /// <see href="https://agentskills.io/specification">Agent Skills specification</see>.
311- /// Configure <see cref="AgentFileSkillsSourceOptions.ResourceFolders "/> to scan different or
311+ /// Configure <see cref="AgentFileSkillsSourceOptions.ResourceDirectories "/> to scan different or
312312 /// additional directories, including <c>"."</c> for the skill root itself.
313313 /// Each file is validated against path-traversal and symlink-escape checks; unsafe files are skipped.
314314 /// </remarks>
315315 private List < AgentFileSkillResource > DiscoverResourceFiles ( string skillDirectoryFullPath , string skillName )
316316 {
317317 var resources = new List < AgentFileSkillResource > ( ) ;
318318
319- foreach ( string folder in this . _resourceFolders . Distinct ( StringComparer . OrdinalIgnoreCase ) )
319+ foreach ( string directory in this . _resourceDirectories . Distinct ( StringComparer . OrdinalIgnoreCase ) )
320320 {
321- bool isRootFolder = string . Equals ( folder , RootFolderIndicator , StringComparison . Ordinal ) ;
321+ bool isRootDirectory = string . Equals ( directory , RootDirectoryIndicator , StringComparison . Ordinal ) ;
322322
323323 // GetFullPath normalizes mixed separators (e.g. "C:\skill\scripts/f1" → "C:\skill\scripts\f1")
324- string targetDirectory = isRootFolder
324+ string targetDirectory = isRootDirectory
325325 ? skillDirectoryFullPath
326- : Path . GetFullPath ( Path . Combine ( skillDirectoryFullPath , folder ) ) + Path . DirectorySeparatorChar ;
326+ : Path . GetFullPath ( Path . Combine ( skillDirectoryFullPath , directory ) ) + Path . DirectorySeparatorChar ;
327327
328328 if ( ! Directory . Exists ( targetDirectory ) )
329329 {
330330 continue ;
331331 }
332332
333333 // Directory-level symlink check: skip if targetDirectory (or any intermediate
334- // segment) is a reparse point. The root folder is excluded — it's a caller-supplied
334+ // segment) is a reparse point. The root directory is excluded — it's a caller-supplied
335335 // trusted path, and the security boundary guards files within it, not the path itself.
336- if ( ! isRootFolder && HasSymlinkInPath ( targetDirectory , skillDirectoryFullPath ) )
336+ if ( ! isRootDirectory && HasSymlinkInPath ( targetDirectory , skillDirectoryFullPath ) )
337337 {
338338 if ( this . _logger . IsEnabled ( LogLevel . Warning ) )
339339 {
340- LogResourceSymlinkFolder ( this . _logger , skillName , SanitizePathForLog ( folder ) ) ;
340+ LogResourceSymlinkDirectory ( this . _logger , skillName , SanitizePathForLog ( directory ) ) ;
341341 }
342342
343343 continue ;
@@ -380,7 +380,7 @@ private List<AgentFileSkillResource> DiscoverResourceFiles(string skillDirectory
380380 // e.g. "references/../../../etc/shadow" → "/etc/shadow"
381381 string resolvedFilePath = Path . GetFullPath ( filePath ) ;
382382
383- // Path containment: reject if the resolved path escapes the target folder .
383+ // Path containment: reject if the resolved path escapes the target directory .
384384 // e.g. "/etc/shadow".StartsWith("/skills/myskill/references/") → false → skip
385385 if ( ! resolvedFilePath . StartsWith ( targetDirectory , StringComparison . OrdinalIgnoreCase ) )
386386 {
@@ -416,41 +416,41 @@ private List<AgentFileSkillResource> DiscoverResourceFiles(string skillDirectory
416416 }
417417
418418 /// <summary>
419- /// Scans configured script folders within a skill directory for script files matching the configured extensions.
419+ /// Scans configured script directories within a skill directory for script files matching the configured extensions.
420420 /// </summary>
421421 /// <remarks>
422- /// By default, scans the <c>scripts/</c> sub-folder as specified by the
422+ /// By default, scans the <c>scripts/</c> subdirectory as specified by the
423423 /// <see href="https://agentskills.io/specification">Agent Skills specification</see>.
424- /// Configure <see cref="AgentFileSkillsSourceOptions.ScriptFolders "/> to scan different or
424+ /// Configure <see cref="AgentFileSkillsSourceOptions.ScriptDirectories "/> to scan different or
425425 /// additional directories, including <c>"."</c> for the skill root itself.
426426 /// Each file is validated against path-traversal and symlink-escape checks; unsafe files are skipped.
427427 /// </remarks>
428428 private List < AgentFileSkillScript > DiscoverScriptFiles ( string skillDirectoryFullPath , string skillName )
429429 {
430430 var scripts = new List < AgentFileSkillScript > ( ) ;
431431
432- foreach ( string folder in this . _scriptFolders . Distinct ( StringComparer . OrdinalIgnoreCase ) )
432+ foreach ( string directory in this . _scriptDirectories . Distinct ( StringComparer . OrdinalIgnoreCase ) )
433433 {
434- bool isRootFolder = string . Equals ( folder , RootFolderIndicator , StringComparison . Ordinal ) ;
434+ bool isRootDirectory = string . Equals ( directory , RootDirectoryIndicator , StringComparison . Ordinal ) ;
435435
436436 // GetFullPath normalizes mixed separators (e.g. "C:\skill\scripts/f1" → "C:\skill\scripts\f1")
437- string targetDirectory = isRootFolder
437+ string targetDirectory = isRootDirectory
438438 ? skillDirectoryFullPath
439- : Path . GetFullPath ( Path . Combine ( skillDirectoryFullPath , folder ) ) + Path . DirectorySeparatorChar ;
439+ : Path . GetFullPath ( Path . Combine ( skillDirectoryFullPath , directory ) ) + Path . DirectorySeparatorChar ;
440440
441441 if ( ! Directory . Exists ( targetDirectory ) )
442442 {
443443 continue ;
444444 }
445445
446446 // Directory-level symlink check: skip if targetDirectory (or any intermediate
447- // segment) is a reparse point. The root folder is excluded — it's a caller-supplied
447+ // segment) is a reparse point. The root directory is excluded — it's a caller-supplied
448448 // trusted path, and the security boundary guards files within it, not the path itself.
449- if ( ! isRootFolder && HasSymlinkInPath ( targetDirectory , skillDirectoryFullPath ) )
449+ if ( ! isRootDirectory && HasSymlinkInPath ( targetDirectory , skillDirectoryFullPath ) )
450450 {
451451 if ( this . _logger . IsEnabled ( LogLevel . Warning ) )
452452 {
453- LogScriptSymlinkFolder ( this . _logger , skillName , SanitizePathForLog ( folder ) ) ;
453+ LogScriptSymlinkDirectory ( this . _logger , skillName , SanitizePathForLog ( directory ) ) ;
454454 }
455455
456456 continue ;
@@ -480,7 +480,7 @@ private List<AgentFileSkillScript> DiscoverScriptFiles(string skillDirectoryFull
480480 // e.g. "scripts/../../../etc/shadow" → "/etc/shadow"
481481 string resolvedFilePath = Path . GetFullPath ( filePath ) ;
482482
483- // Path containment: reject if the resolved path escapes the target folder .
483+ // Path containment: reject if the resolved path escapes the target directory .
484484 // e.g. "/etc/shadow".StartsWith("/skills/myskill/scripts/") → false → skip
485485 if ( ! resolvedFilePath . StartsWith ( targetDirectory , StringComparison . OrdinalIgnoreCase ) )
486486 {
@@ -541,8 +541,8 @@ private static bool HasSymlinkInPath(string pathToCheck, string trustedBasePath)
541541 }
542542
543543 /// <summary>
544- /// Normalizes a relative path or folder name by stripping a leading "./"/".\",
545- /// trimming trailing directory separators, and replacing backslashes with forward
544+ /// Normalizes a relative path or directory name by stripping a leading "./"/".\",
545+ /// trimming trailing separators, and replacing backslashes with forward
546546 /// slashes.
547547 /// </summary>
548548 private static string NormalizePath ( string path )
@@ -602,36 +602,36 @@ private static void ValidateExtensions(IEnumerable<string>? extensions)
602602 }
603603 }
604604
605- private static IEnumerable < string > ValidateAndNormalizeFolderNames ( IEnumerable < string > folders , ILogger logger )
605+ private static IEnumerable < string > ValidateAndNormalizeDirectoryNames ( IEnumerable < string > directories , ILogger logger )
606606 {
607- foreach ( string folder in folders )
607+ foreach ( string directory in directories )
608608 {
609- if ( string . IsNullOrWhiteSpace ( folder ) )
609+ if ( string . IsNullOrWhiteSpace ( directory ) )
610610 {
611- throw new ArgumentException ( "Folder names must not be null or whitespace." , nameof ( folders ) ) ;
611+ throw new ArgumentException ( "Directory names must not be null or whitespace." , nameof ( directories ) ) ;
612612 }
613613
614614 // "." is valid — it means the skill root directory.
615- if ( string . Equals ( folder , RootFolderIndicator , StringComparison . Ordinal ) )
615+ if ( string . Equals ( directory , RootDirectoryIndicator , StringComparison . Ordinal ) )
616616 {
617- yield return folder ;
617+ yield return directory ;
618618 continue ;
619619 }
620620
621621 // Reject absolute paths and any path segments that escape upward.
622- if ( Path . IsPathRooted ( folder ) || ContainsParentTraversalSegment ( folder ) )
622+ if ( Path . IsPathRooted ( directory ) || ContainsParentTraversalSegment ( directory ) )
623623 {
624- LogFolderNameSkippedInvalid ( logger , folder ) ;
624+ LogDirectoryNameSkippedInvalid ( logger , directory ) ;
625625 continue ;
626626 }
627627
628- yield return NormalizePath ( folder ) ;
628+ yield return NormalizePath ( directory ) ;
629629 }
630630 }
631631
632- private static bool ContainsParentTraversalSegment ( string folder )
632+ private static bool ContainsParentTraversalSegment ( string directory )
633633 {
634- foreach ( string segment in folder . Split ( '/' , '\\ ' ) )
634+ foreach ( string segment in directory . Split ( '/' , '\\ ' ) )
635635 {
636636 if ( segment == ".." )
637637 {
@@ -666,8 +666,8 @@ private static bool ContainsParentTraversalSegment(string folder)
666666 [ LoggerMessage ( LogLevel . Warning , "Skipping resource in skill '{SkillName}': '{ResourcePath}' is a symlink that resolves outside the skill directory" ) ]
667667 private static partial void LogResourceSymlinkEscape ( ILogger logger , string skillName , string resourcePath ) ;
668668
669- [ LoggerMessage ( LogLevel . Warning , "Skipping resource folder '{FolderName }' in skill '{SkillName}': folder path contains a symlink" ) ]
670- private static partial void LogResourceSymlinkFolder ( ILogger logger , string skillName , string folderName ) ;
669+ [ LoggerMessage ( LogLevel . Warning , "Skipping resource directory '{DirectoryName }' in skill '{SkillName}': directory path contains a symlink" ) ]
670+ private static partial void LogResourceSymlinkDirectory ( ILogger logger , string skillName , string directoryName ) ;
671671
672672 [ LoggerMessage ( LogLevel . Debug , "Skipping file '{FilePath}' in skill '{SkillName}': extension '{Extension}' is not in the allowed list" ) ]
673673 private static partial void LogResourceSkippedExtension ( ILogger logger , string skillName , string filePath , string extension ) ;
@@ -678,9 +678,9 @@ private static bool ContainsParentTraversalSegment(string folder)
678678 [ LoggerMessage ( LogLevel . Warning , "Skipping script in skill '{SkillName}': '{ScriptPath}' is a symlink that resolves outside the skill directory" ) ]
679679 private static partial void LogScriptSymlinkEscape ( ILogger logger , string skillName , string scriptPath ) ;
680680
681- [ LoggerMessage ( LogLevel . Warning , "Skipping script folder '{FolderName }' in skill '{SkillName}': folder path contains a symlink" ) ]
682- private static partial void LogScriptSymlinkFolder ( ILogger logger , string skillName , string folderName ) ;
681+ [ LoggerMessage ( LogLevel . Warning , "Skipping script directory '{DirectoryName }' in skill '{SkillName}': directory path contains a symlink" ) ]
682+ private static partial void LogScriptSymlinkDirectory ( ILogger logger , string skillName , string directoryName ) ;
683683
684- [ LoggerMessage ( LogLevel . Warning , "Skipping invalid folder name '{FolderName }': must be a relative path with no '..' segments" ) ]
685- private static partial void LogFolderNameSkippedInvalid ( ILogger logger , string folderName ) ;
684+ [ LoggerMessage ( LogLevel . Warning , "Skipping invalid directory name '{DirectoryName }': must be a relative path with no '..' segments" ) ]
685+ private static partial void LogDirectoryNameSkippedInvalid ( ILogger logger , string directoryName ) ;
686686}
0 commit comments