discoverSkills function

Future<List<Skill>> discoverSkills(
  1. ExecutionEnv env, {
  2. List<SkillRoot> projectRoots = const [],
  3. List<SkillRoot> userRoots = const [],
  4. Set<SkillSource>? allowedSources,
  5. List<Skill> builtins = const [],
})

Discovers skills under projectRoots then userRoots (project wins on a name clash, first-name-wins case-insensitively). Roots whose source is not in allowedSources (defaults: everything) are skipped — hosts pass the first-party set when the user has not granted third-party skills access. Missing roots are silently skipped.

builtins are the package's compiled-in skills (issue #1151), merged LAST so every project/user/third-party skill of the same name shadows them (precedence ladder: project > user > third-party-granted > builtin). Built-ins are first-party and never gated by allowedSources.

Implementation

Future<List<Skill>> discoverSkills(
  ExecutionEnv env, {
  List<SkillRoot> projectRoots = const [],
  List<SkillRoot> userRoots = const [],
  Set<SkillSource>? allowedSources,
  List<Skill> builtins = const [],
}) {
  bool allowed(SkillRoot root) =>
      allowedSources == null || allowedSources.contains(root.source);
  final skills = <Skill>[];
  final seen = <String>{};
  Future<void> scan(SkillScope scope, List<SkillRoot> roots) async {
    for (final root in roots.where(allowed)) {
      for (final skill in await _scanRoot(env, root, scope)) {
        if (seen.add(skill.name.toLowerCase())) skills.add(skill);
      }
    }
  }

  return () async {
    await scan(SkillScope.project, projectRoots);
    await scan(SkillScope.user, userRoots);
    for (final skill in builtins) {
      if (seen.add(skill.name.toLowerCase())) skills.add(skill);
    }
    return skills;
  }();
}