Discovers skills from project-level and user-level skill directories.
Implements the agentskills.io discovery spec:
- Project-level:
.agents/skills/directory - User-level:
~/.agents/skills/directory
Precedence Rules
Project-level skills override user-level skills when both have the same name. This follows deterministic precedence: project > user
Metadata Tracking
Each discovered skill includes:
name- Skill identifierdescription- Brief description from SKILL.md frontmatterskill_md_path- Absolute path to the SKILL.md fileroot_dir- Skill root directory (parent of SKILL.md location)scope-:projector:userindicating sourcesource_metadata- Additional discovery metadata
Usage
# Discover all skills
{:ok, skills} = Jido.AI.Skill.Discovery.discover()
# Discover from specific paths only
{:ok, skills} = Jido.AI.Skill.Discovery.discover_from([".agents/skills/"])
# Get a single skill by name
{:ok, spec} = Jido.AI.Skill.Discovery.find("code-review")
Summary
Functions
Discovers skills from both project and user directories.
Discovers skills from a list of specific paths.
Discovers skills from the project-level .agents/skills/ directory.
Discovers skills from the user-level ~/.agents/skills/ directory.
Finds a specific skill by name across all discovery sources.
Converts discovery metadata into a full Spec by loading the SKILL.md.
Types
Functions
@spec discover() :: {:ok, [discovery_metadata()]} | {:error, term()}
Discovers skills from both project and user directories.
Returns skills with project-level taking precedence over user-level when names collide.
Returns
{:ok, [discovery_metadata]}- List of discovered skill metadata{:error, reason}- Discovery failed
Examples
{:ok, skills} = Jido.AI.Skill.Discovery.discover()
# skills will have project-level skills overriding user-level
@spec discover_from( [String.t()], keyword() ) :: {:ok, [discovery_metadata()]} | {:error, term()}
Discovers skills from a list of specific paths.
Useful for scanning custom directories or testing.
Options
:scope- Assign scope metadata (:projector:user), defaults to:custom
Examples
{:ok, skills} = Jido.AI.Skill.Discovery.discover_from(["priv/skills/"])
{:ok, skills} = Jido.AI.Skill.Discovery.discover_from(["priv/skills/"], scope: :project)
@spec discover_from_project(String.t()) :: {:ok, [discovery_metadata()]} | {:error, term()}
Discovers skills from the project-level .agents/skills/ directory.
Returns
{:ok, [discovery_metadata]}- List of project-level skills{:ok, []}- Directory doesn't exist or is empty{:error, reason}- Discovery failed
@spec discover_from_user(String.t()) :: {:ok, [discovery_metadata()]} | {:error, term()}
Discovers skills from the user-level ~/.agents/skills/ directory.
Returns
{:ok, [discovery_metadata]}- List of user-level skills{:ok, []}- Directory doesn't exist or is empty{:error, reason}- Discovery failed
@spec find(String.t(), [String.t()] | nil) :: {:ok, discovery_metadata()} | {:error, :not_found}
Finds a specific skill by name across all discovery sources.
Returns the first matching skill with project-level taking precedence.
When paths is given, searches those directories instead of the default
project/user discovery sources — useful for scoped lookups and testing.
Examples
{:ok, metadata} = Jido.AI.Skill.Discovery.find("code-review")
{:error, :not_found} = Jido.AI.Skill.Discovery.find("unknown-skill")
{:ok, metadata} = Jido.AI.Skill.Discovery.find("local-skill", ["priv/skills/"])
@spec to_spec(discovery_metadata() | map()) :: {:ok, Jido.AI.Skill.Spec.t()} | {:error, term()}
Converts discovery metadata into a full Spec by loading the SKILL.md.
Examples
{:ok, metadata} = Jido.AI.Skill.Discovery.find("code-review")
{:ok, spec} = Jido.AI.Skill.Discovery.to_spec(metadata)