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

Plugins in the dotnet/skills repository are self-contained units defined by a 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 file at its root. This manifest declares the plugin's identity and maps its contents.

The 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 demonstrates this structure:

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

Inside the plugin directory, the skills/ folder contains SKILL.md files, while the optional agents/ folder houses .agent.md definitions.

Plugin Discovery Mechanism

The discovery logic resides in 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.

Once discovered, ParsePluginJson deserializes the manifest into a PluginInfo record defined in 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 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 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:

{
  "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:

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
  2. Ensure the file follows the standard skill template
  3. The validator automatically discovers the new skill because 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 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
  • Strong validation: 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 manifest. This limit is implemented in 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 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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →