# How Plugins Are Managed in the dotnet/skills Repository: A Complete Guide

> Discover how plugins are managed in the dotnet/skills repository. Learn about plugin manifests, discovery, and validation with this comprehensive guide.

- Repository: [.NET Platform/skills](https://github.com/dotnet/skills)
- Tags: how-to-guide
- Published: 2026-05-22

---

**Plugins in the dotnet/skills repository are self-contained units defined by a [`plugin.json`](https://github.com/dotnet/skills/blob/main/plugin.json) manifest file, discovered via upward directory traversal, and validated through shared tooling centralized in the `eng/skill-validator/` path.** 

The repository organizes tutorials and automation scripts into modular plugins, each residing in its own subdirectory under `plugins/`. Every plugin declares its metadata and contents through a JSON manifest, enabling consistent discovery and validation across the entire codebase.

## Directory Structure and the Plugin Manifest

Each plugin lives in its own directory under `plugins/<plugin-name>/` and must contain a [`plugin.json`](https://github.com/dotnet/skills/blob/main/plugin.json) file at its root. This manifest declares the plugin's identity and maps its contents.

The [`plugin.json`](https://github.com/dotnet/skills/blob/main/plugin.json) schema requires:
- **`name`**: The plugin identifier
- **`version`**: Semantic version string
- **`description`**: Human-readable summary  
- **`skills`**: Array of relative paths to skill directories (typically `./skills/`)
- **`agents`**: Optional array of relative paths to agent directories

All paths are relative to the plugin root. The live [`plugins/dotnet/plugin.json`](https://github.com/dotnet/skills/blob/main/plugins/dotnet/plugin.json) demonstrates this structure:

```json
{
  "name": "dotnet",
  "version": "1.0.0",
  "description": ".NET maintenance skills",
  "skills": ["./skills/"],
  "agents": ["./agents/"]
}

```

Inside the plugin directory, the `skills/` folder contains [`SKILL.md`](https://github.com/dotnet/skills/blob/main/SKILL.md) files, while the optional `agents/` folder houses [`.agent.md`](https://github.com/dotnet/skills/blob/main/.agent.md) definitions.

## Plugin Discovery Mechanism

The discovery logic resides in **[`eng/skill-validator/src/Shared/PluginDiscovery.cs`](https://github.com/dotnet/skills/blob/main/eng/skill-validator/src/Shared/PluginDiscovery.cs)**. When the skill-validator processes a file, it invokes `FindPluginRoot` to locate the containing plugin by walking upward from the file path (maximum 4 levels) until it finds a [`plugin.json`](https://github.com/dotnet/skills/blob/main/plugin.json).

Once discovered, `ParsePluginJson` deserializes the manifest into a `PluginInfo` record defined in **[`eng/skill-validator/src/Shared/Models.cs`](https://github.com/dotnet/skills/blob/main/eng/skill-validator/src/Shared/Models.cs)**. The validator then uses the `SkillPaths` and `AgentPaths` properties to enumerate concrete markdown files.

The discovery workflow guarantees that skill resources resolve relative to the correct plugin directory through `FindPluginContext`, ensuring isolation between plugins.

## Validation and Enforcement

After discovery, **[`eng/skill-validator/src/Check/PluginProfiler.cs`](https://github.com/dotnet/skills/blob/main/eng/skill-validator/src/Check/PluginProfiler.cs)** enforces manifest completeness. The validator checks for required fields (`name`, `version`, `skills`) and generates user-visible errors for malformed manifests.

Validation ensures that:
- All declared skill directories exist and are accessible
- Path traversals remain within the plugin root (security check)
- Agent paths are valid when present

## Skill and Agent Enumeration

With a validated `PluginInfo` instance, the system locates content files:
- **Skills**: `.md` files within paths listed in the `skills` array
- **Agents**: [`.agent.md`](https://github.com/dotnet/skills/blob/main/.agent.md) files within paths listed in the optional `agents` array

The `AgentDiscovery` class and evaluation engine rely on these resolved paths to load tutorial content and automation scripts without hardcoding directory structures.

## Practical Implementation Examples

### Creating a plugin.json Manifest

For a new plugin named `my-plugin`, create [`plugins/my-plugin/plugin.json`](https://github.com/dotnet/skills/blob/main/plugins/my-plugin/plugin.json):

```json
{
  "name": "my-plugin",
  "version": "0.1.0",
  "description": "Custom .NET skills for my team.",
  "skills": ["./skills/"],
  "agents": ["./agents/"]
}

```

### Locating a Plugin Programmatically

The `PluginDiscovery` class exposes methods to resolve plugin context from any skill file path:

```csharp
using SkillValidator.Shared;

// Path to a specific skill file
string skillPath = "/repo/plugins/dotnet/skills/nuget-trusted-publishing/SKILL.md";

var pluginContext = PluginDiscovery.FindPluginContext(
    new SkillInfo(
        Name: "nuget-trusted-publishing",
        Description: "...",
        Path: skillPath,
        SkillMdPath: skillPath,
        SkillMdContent: File.ReadAllText(skillPath)));

if (pluginContext is not null)
{
    Console.WriteLine($"Plugin root: {pluginContext.PluginRoot}");
    Console.WriteLine($"Plugin name: {pluginContext.Plugin.Name}");
}
else
{
    Console.WriteLine("No plugin.json found within 4 parent directories.");
}

```

### Adding New Skills to an Existing Plugin

To extend an existing plugin:

1. Create the Markdown file under the plugin's `skills/` folder, for example [`plugins/dotnet/skills/new-feature/SKILL.md`](https://github.com/dotnet/skills/blob/main/plugins/dotnet/skills/new-feature/SKILL.md)
2. Ensure the file follows the standard skill template
3. The validator automatically discovers the new skill because [`plugin.json`](https://github.com/dotnet/skills/blob/main/plugin.json) points to `./skills/`

No registry updates or manifest modifications are required when adding skills to existing directories.

## Summary

- **Self-contained structure**: Each plugin resides in `plugins/<name>/` with its own [`plugin.json`](https://github.com/dotnet/skills/blob/main/plugin.json) manifest
- **Relative path resolution**: The manifest uses relative paths for skills and agents, enabling portable plugin definitions
- **Upward traversal discovery**: `PluginDiscovery.FindPluginRoot` searches up to 4 parent directories to locate [`plugin.json`](https://github.com/dotnet/skills/blob/main/plugin.json)
- **Strong validation**: [`PluginProfiler.cs`](https://github.com/dotnet/skills/blob/main/PluginProfiler.cs) enforces required fields and path safety before execution
- **Automatic enumeration**: Skills and agents are discovered dynamically based on manifest declarations, not static registries

## Frequently Asked Questions

### What is the maximum directory depth for plugin discovery?

The validator searches up to **4 parent directories** from any skill file path to locate a [`plugin.json`](https://github.com/dotnet/skills/blob/main/plugin.json) manifest. This limit is implemented in [`eng/skill-validator/src/Shared/PluginDiscovery.cs`](https://github.com/dotnet/skills/blob/main/eng/skill-validator/src/Shared/PluginDiscovery.cs) to prevent excessive filesystem traversal while allowing flexible plugin nesting.

### Can a plugin exist without agents?

Yes. The `agents` field in [`plugin.json`](https://github.com/dotnet/skills/blob/main/plugin.json) is optional. A minimal plugin requires only `name`, `version`, and `skills` fields. Many plugins in the repository function solely as skill containers without associated automation agents.

### How does the validator handle missing required fields in plugin.json?

[`eng/skill-validator/src/Check/PluginProfiler.cs`](https://github.com/dotnet/skills/blob/main/eng/skill-validator/src/Check/PluginProfiler.cs) validates the deserialized manifest and generates explicit errors for missing required fields such as `name`, `version`, or `skills`. These errors surface during the validation phase, preventing execution of malformed plugins.

### Are plugin paths relative or absolute?

All paths in [`plugin.json`](https://github.com/dotnet/skills/blob/main/plugin.json) must be **relative** to the plugin root directory. Absolute paths are rejected by the discovery logic to ensure repository portability and prevent path fragmentation across different development environments.