diff --git a/eng/skill-validator/src/Commands/CheckCommand.cs b/eng/skill-validator/src/Commands/CheckCommand.cs index 1905bb7605..e6001c107b 100644 --- a/eng/skill-validator/src/Commands/CheckCommand.cs +++ b/eng/skill-validator/src/Commands/CheckCommand.cs @@ -111,11 +111,20 @@ private static async Task RunPluginCheck(CheckConfig config) allPlugins.Add(plugin); // Resolve skills path from plugin.json and discover skills per plugin - if (!string.IsNullOrWhiteSpace(plugin?.SkillsPath) - && PluginValidator.TryGetSafeSubdirectory(fullPath, plugin.SkillsPath, out var skillsDir, out _) - && Directory.Exists(skillsDir)) + var skillsDirs = new List(); + if (plugin is not null) { - var skills = await SkillDiscovery.DiscoverSkills(skillsDir!); + foreach (var sp in plugin.SkillPaths) + { + if (PluginValidator.TryGetSafeSubdirectory(fullPath, sp, out var dir, out _) + && Directory.Exists(dir)) + skillsDirs.Add(dir!); + } + } + + foreach (var dir in skillsDirs) + { + var skills = await SkillDiscovery.DiscoverSkills(dir); pluginSkills[pluginName] = new List(skills); allSkillsList.AddRange(skills); } diff --git a/eng/skill-validator/src/Models/Models.cs b/eng/skill-validator/src/Models/Models.cs index 659229ffda..ea299068d1 100644 --- a/eng/skill-validator/src/Models/Models.cs +++ b/eng/skill-validator/src/Models/Models.cs @@ -114,8 +114,8 @@ public sealed record PluginInfo( string Name, string? Version, string? Description, - string? SkillsPath, - string? AgentsPath, + IReadOnlyList SkillPaths, + IReadOnlyList AgentPaths, string DirectoryPath, string DirectoryName); diff --git a/eng/skill-validator/src/Services/AgentRunner.cs b/eng/skill-validator/src/Services/AgentRunner.cs index 1a79e819c8..6fb53c2629 100644 --- a/eng/skill-validator/src/Services/AgentRunner.cs +++ b/eng/skill-validator/src/Services/AgentRunner.cs @@ -415,15 +415,17 @@ internal static string[] ResolvePluginSkillDirectories(string pluginRoot) // without extra skill directories; validation surfaces the real error. return []; } - if (pluginInfo?.SkillsPath is null) return []; + if (pluginInfo is null || pluginInfo.SkillPaths.Count == 0) return []; - if (!PluginValidator.TryGetSafeSubdirectory( - pluginRoot, pluginInfo.SkillsPath, out var skillsDir, out _)) - return []; - - if (!Directory.Exists(skillsDir!)) return []; - - return [skillsDir!]; + var dirs = new List(); + foreach (var relativePath in pluginInfo.SkillPaths) + { + if (!PluginValidator.TryGetSafeSubdirectory(pluginRoot, relativePath, out var fullPath, out _)) + continue; + if (Directory.Exists(fullPath!)) + dirs.Add(fullPath!); + } + return dirs.ToArray(); } public static async Task RunAgent(RunOptions options) diff --git a/eng/skill-validator/src/Services/PluginValidator.cs b/eng/skill-validator/src/Services/PluginValidator.cs index 7e8ad07800..ce3137d244 100644 --- a/eng/skill-validator/src/Services/PluginValidator.cs +++ b/eng/skill-validator/src/Services/PluginValidator.cs @@ -45,29 +45,41 @@ public static PluginValidationResult ValidatePlugin(PluginInfo plugin) } // --- Skills path validation --- - if (string.IsNullOrWhiteSpace(plugin.SkillsPath)) + if (plugin.SkillPaths.Count == 0) { errors.Add("plugin.json has no 'skills' field — required."); } - else if (!TryGetSafeSubdirectory(plugin.DirectoryPath, plugin.SkillsPath, out var skillsDir, out var skillsPathError)) - { - errors.Add($"Plugin skills path is invalid: {skillsPathError}"); - } - else if (!Directory.Exists(skillsDir!)) + else { - errors.Add($"Plugin skills path '{plugin.SkillsPath}' does not exist at '{skillsDir}'."); + foreach (var skillPath in plugin.SkillPaths) + { + if (!TryGetSafeSubdirectory(plugin.DirectoryPath, skillPath, out var resolved, out var skillPathError)) + { + errors.Add($"Plugin skills path is invalid: {skillPathError}"); + } + else if (!Directory.Exists(resolved!) && !File.Exists(resolved!)) + { + errors.Add($"Plugin skills path '{skillPath}' does not exist at '{resolved}'."); + } + } } // --- Agents path validation (optional, but warn if specified and missing) --- - if (!string.IsNullOrWhiteSpace(plugin.AgentsPath)) + foreach (var agentPath in plugin.AgentPaths) { - if (!TryGetSafeSubdirectory(plugin.DirectoryPath, plugin.AgentsPath, out var agentsDir, out var agentsPathError)) + if (string.IsNullOrWhiteSpace(agentPath)) { - warnings.Add($"Plugin agents path is invalid: {agentsPathError}"); + warnings.Add("Plugin agents entry is empty or whitespace and will be ignored."); + continue; } - else if (!Directory.Exists(agentsDir!)) + + if (!TryGetSafeSubdirectory(plugin.DirectoryPath, agentPath, out var resolved, out var agentPathError)) { - warnings.Add($"Plugin agents path '{plugin.AgentsPath}' does not exist at '{agentsDir}'."); + warnings.Add($"Plugin agent path is invalid: {agentPathError}"); + } + else if (!Directory.Exists(resolved!) && !File.Exists(resolved!)) + { + warnings.Add($"Plugin agent path '{agentPath}' does not exist at '{resolved}'."); } } @@ -129,12 +141,43 @@ internal static bool TryGetSafeSubdirectory(string rootDirectory, string relativ var name = doc.TryGetProperty("name", out var n) ? n.GetString() : null; var version = doc.TryGetProperty("version", out var v) ? v.GetString() : null; var description = doc.TryGetProperty("description", out var d) ? d.GetString() : null; - var skills = doc.TryGetProperty("skills", out var s) ? s.GetString() : null; - var agents = doc.TryGetProperty("agents", out var a) ? a.GetString() : null; + // skills and agents can be an array of strings (Claude Code schema, preferred) + // or a string path (legacy). Normalize both into the array form. + IReadOnlyList skillPaths = []; + if (doc.TryGetProperty("skills", out var s)) + { + if (s.ValueKind == JsonValueKind.Array) + { + skillPaths = s.EnumerateArray() + .Where(e => e.ValueKind == JsonValueKind.String) + .Select(e => e.GetString()!) + .ToList(); + } + else if (s.ValueKind == JsonValueKind.String && s.GetString() is { } sv) + { + skillPaths = [sv]; + } + } + + IReadOnlyList agentPaths = []; + if (doc.TryGetProperty("agents", out var a)) + { + if (a.ValueKind == JsonValueKind.Array) + { + agentPaths = a.EnumerateArray() + .Where(e => e.ValueKind == JsonValueKind.String) + .Select(e => e.GetString()!) + .ToList(); + } + else if (a.ValueKind == JsonValueKind.String && a.GetString() is { } av) + { + agentPaths = [av]; + } + } var dirPath = Path.GetDirectoryName(Path.GetFullPath(pluginJsonPath))!; var dirName = Path.GetFileName(dirPath); - return new PluginInfo(name ?? "", version, description, skills, agents, dirPath, dirName); + return new PluginInfo(name ?? "", version, description, skillPaths, agentPaths, dirPath, dirName); } } diff --git a/eng/skill-validator/src/Services/SkillDiscovery.cs b/eng/skill-validator/src/Services/SkillDiscovery.cs index e158ae4524..a56df9e3c1 100644 --- a/eng/skill-validator/src/Services/SkillDiscovery.cs +++ b/eng/skill-validator/src/Services/SkillDiscovery.cs @@ -58,7 +58,7 @@ public static async Task> DiscoverSkillsRecursive(strin /// /// Discover skills within a plugin root directory. - /// Uses plugin.json to determine the skills path. + /// Uses plugin.json to determine the skills paths. /// public static async Task> DiscoverSkillsInPlugin(string pluginRoot) { @@ -67,13 +67,18 @@ public static async Task> DiscoverSkillsInPlugin(string return []; var plugin = PluginValidator.ParsePluginJson(pluginJsonPath); - if (plugin is null || string.IsNullOrWhiteSpace(plugin.SkillsPath)) + if (plugin is null || plugin.SkillPaths.Count == 0) return []; - if (!PluginValidator.TryGetSafeSubdirectory(pluginRoot, plugin.SkillsPath, out var skillsDir, out _)) - return []; - - return await DiscoverSkills(skillsDir!); + var skills = new List(); + foreach (var relativePath in plugin.SkillPaths) + { + if (!PluginValidator.TryGetSafeSubdirectory(pluginRoot, relativePath, out var fullPath, out _)) + continue; + if (Directory.Exists(fullPath!)) + skills.AddRange(await DiscoverSkills(fullPath!)); + } + return skills; } /// @@ -253,7 +258,8 @@ public static async Task> DiscoverAgents(IReadOnlyList< /// /// Discover agent files (.agent.md) within a plugin root directory. - /// Uses plugin.json to determine the agents path. + /// Uses plugin.json to determine the agents paths; falls back to + /// the "agents" directory by convention when none are specified. /// public static async Task> DiscoverAgentsInPlugin(string pluginRoot) { @@ -265,14 +271,31 @@ public static async Task> DiscoverAgentsInPlugin(string if (plugin is null) return []; - var agentsPath = !string.IsNullOrWhiteSpace(plugin.AgentsPath) - ? plugin.AgentsPath - : "agents"; + // Use declared paths; fall back to "agents" directory convention. + var paths = plugin.AgentPaths.Count > 0 + ? plugin.AgentPaths + : (IReadOnlyList)["agents"]; - if (!PluginValidator.TryGetSafeSubdirectory(pluginRoot, agentsPath, out var agentsDir, out _)) - return []; + var agents = new List(); + foreach (var relativePath in paths) + { + if (string.IsNullOrWhiteSpace(relativePath)) + continue; - return await DiscoverAgentsInDirectory(agentsDir!); + if (!PluginValidator.TryGetSafeSubdirectory(pluginRoot, relativePath, out var fullPath, out _)) + continue; + if (Directory.Exists(fullPath!)) + { + agents.AddRange(await DiscoverAgentsInDirectory(fullPath!)); + } + else + { + var agent = await DiscoverAgentAt(fullPath!); + if (agent is not null) + agents.Add(agent); + } + } + return agents; } /// diff --git a/eng/skill-validator/tests/AgentPluginTests.cs b/eng/skill-validator/tests/AgentPluginTests.cs index b5d27c3f96..5c51bea786 100644 --- a/eng/skill-validator/tests/AgentPluginTests.cs +++ b/eng/skill-validator/tests/AgentPluginTests.cs @@ -180,7 +180,7 @@ public void ValidPluginProducesNoErrors() Directory.CreateDirectory(Path.Combine(pluginDir, "agents")); var dirName = Path.GetFileName(pluginDir); - var plugin = new PluginInfo(dirName, "1.0.0", "A test plugin.", "./skills/", "./agents/", pluginDir, dirName); + var plugin = new PluginInfo(dirName, "1.0.0", "A test plugin.", ["./skills/"], ["./agents/"], pluginDir, dirName); var result = PluginValidator.ValidatePlugin(plugin); Assert.Empty(result.Errors); Assert.Empty(result.Warnings); @@ -194,7 +194,7 @@ public void ValidPluginProducesNoErrors() [Fact] public void MissingNameErrors() { - var plugin = new PluginInfo("", "1.0.0", "desc", "./skills/", null, "/tmp/test", "test"); + var plugin = new PluginInfo("", "1.0.0", "desc", ["./skills/"], [], "/tmp/test", "test"); var result = PluginValidator.ValidatePlugin(plugin); Assert.Contains(result.Errors, e => e.Contains("name")); } @@ -202,7 +202,7 @@ public void MissingNameErrors() [Fact] public void NameNotMatchingDirectoryErrors() { - var plugin = new PluginInfo("wrong-name", "1.0.0", "desc", "./skills/", null, "/tmp/my-plugin", "my-plugin"); + var plugin = new PluginInfo("wrong-name", "1.0.0", "desc", ["./skills/"], [], "/tmp/my-plugin", "my-plugin"); var result = PluginValidator.ValidatePlugin(plugin); Assert.Contains(result.Errors, e => e.Contains("does not match directory")); } @@ -210,7 +210,7 @@ public void NameNotMatchingDirectoryErrors() [Fact] public void MissingVersionErrors() { - var plugin = new PluginInfo("test", null, "desc", "./skills/", null, "/tmp/test", "test"); + var plugin = new PluginInfo("test", null, "desc", ["./skills/"], [], "/tmp/test", "test"); var result = PluginValidator.ValidatePlugin(plugin); Assert.Contains(result.Errors, e => e.Contains("version")); } @@ -218,7 +218,7 @@ public void MissingVersionErrors() [Fact] public void MissingDescriptionErrors() { - var plugin = new PluginInfo("test", "1.0.0", null, "./skills/", null, "/tmp/test", "test"); + var plugin = new PluginInfo("test", "1.0.0", null, ["./skills/"], [], "/tmp/test", "test"); var result = PluginValidator.ValidatePlugin(plugin); Assert.Contains(result.Errors, e => e.Contains("description")); } @@ -227,7 +227,7 @@ public void MissingDescriptionErrors() public void DescriptionOverLimitErrors() { var desc = new string('a', 1025); - var plugin = new PluginInfo("test", "1.0.0", desc, "./skills/", null, "/tmp/test", "test"); + var plugin = new PluginInfo("test", "1.0.0", desc, ["./skills/"], [], "/tmp/test", "test"); var result = PluginValidator.ValidatePlugin(plugin); Assert.Contains(result.Errors, e => e.Contains("maximum")); } @@ -235,7 +235,7 @@ public void DescriptionOverLimitErrors() [Fact] public void MissingSkillsPathErrors() { - var plugin = new PluginInfo("test", "1.0.0", "desc", null, null, "/tmp/test", "test"); + var plugin = new PluginInfo("test", "1.0.0", "desc", [], [], "/tmp/test", "test"); var result = PluginValidator.ValidatePlugin(plugin); Assert.Contains(result.Errors, e => e.Contains("skills")); } @@ -243,7 +243,7 @@ public void MissingSkillsPathErrors() [Fact] public void NonexistentSkillsPathErrors() { - var plugin = new PluginInfo("test", "1.0.0", "desc", "./nonexistent/", null, "/tmp/test", "test"); + var plugin = new PluginInfo("test", "1.0.0", "desc", ["./nonexistent/"], [], "/tmp/test", "test"); var result = PluginValidator.ValidatePlugin(plugin); Assert.Contains(result.Errors, e => e.Contains("does not exist")); } @@ -258,7 +258,7 @@ public void NonexistentAgentsPathWarns() Directory.CreateDirectory(Path.Combine(pluginDir, "skills")); var dirName = Path.GetFileName(pluginDir); - var plugin = new PluginInfo(dirName, "1.0.0", "desc", "./skills/", "./nonexistent/", pluginDir, dirName); + var plugin = new PluginInfo(dirName, "1.0.0", "desc", ["./skills/"], ["./nonexistent/"], pluginDir, dirName); var result = PluginValidator.ValidatePlugin(plugin); Assert.Empty(result.Errors); Assert.Contains(result.Warnings, w => w.Contains("does not exist")); @@ -269,10 +269,93 @@ public void NonexistentAgentsPathWarns() } } + [Fact] + public void NonexistentAgentPathInArrayWarns() + { + var pluginDir = Path.Combine(Path.GetTempPath(), "plugin-test-" + Guid.NewGuid().ToString("N")); + try + { + Directory.CreateDirectory(pluginDir); + Directory.CreateDirectory(Path.Combine(pluginDir, "skills")); + var dirName = Path.GetFileName(pluginDir); + + var plugin = new PluginInfo(dirName, "1.0.0", "desc", ["./skills/"], ["./agents/"], pluginDir, dirName); + var result = PluginValidator.ValidatePlugin(plugin); + Assert.Empty(result.Errors); + Assert.Contains(result.Warnings, w => w.Contains("does not exist")); + } + finally + { + Directory.Delete(pluginDir, true); + } + } + + [Fact] + public void ValidAgentPathsArrayProducesNoWarnings() + { + var pluginDir = Path.Combine(Path.GetTempPath(), "plugin-test-" + Guid.NewGuid().ToString("N")); + try + { + Directory.CreateDirectory(pluginDir); + Directory.CreateDirectory(Path.Combine(pluginDir, "skills")); + Directory.CreateDirectory(Path.Combine(pluginDir, "agents")); + File.WriteAllText(Path.Combine(pluginDir, "agents", "test.agent.md"), "---\nname: test\ndescription: test\n---\n# Test\n"); + var dirName = Path.GetFileName(pluginDir); + + var plugin = new PluginInfo(dirName, "1.0.0", "desc", ["./skills/"], ["./agents/"], pluginDir, dirName); + var result = PluginValidator.ValidatePlugin(plugin); + Assert.Empty(result.Errors); + Assert.Empty(result.Warnings); + } + finally + { + Directory.Delete(pluginDir, true); + } + } + + [Fact] + public void ValidSkillPathsArrayProducesNoErrors() + { + var pluginDir = Path.Combine(Path.GetTempPath(), "plugin-test-" + Guid.NewGuid().ToString("N")); + try + { + Directory.CreateDirectory(pluginDir); + Directory.CreateDirectory(Path.Combine(pluginDir, "skills")); + var dirName = Path.GetFileName(pluginDir); + + var plugin = new PluginInfo(dirName, "1.0.0", "desc", ["./skills/"], [], pluginDir, dirName); + var result = PluginValidator.ValidatePlugin(plugin); + Assert.Empty(result.Errors); + } + finally + { + Directory.Delete(pluginDir, true); + } + } + + [Fact] + public void NonexistentSkillPathInArrayErrors() + { + var pluginDir = Path.Combine(Path.GetTempPath(), "plugin-test-" + Guid.NewGuid().ToString("N")); + try + { + Directory.CreateDirectory(pluginDir); + var dirName = Path.GetFileName(pluginDir); + + var plugin = new PluginInfo(dirName, "1.0.0", "desc", ["./nonexistent/"], [], pluginDir, dirName); + var result = PluginValidator.ValidatePlugin(plugin); + Assert.Contains(result.Errors, e => e.Contains("does not exist")); + } + finally + { + Directory.Delete(pluginDir, true); + } + } + [Fact] public void NameFormatErrors() { - var plugin = new PluginInfo("My_Plugin", "1.0.0", "desc", "./skills/", null, "/tmp/My_Plugin", "My_Plugin"); + var plugin = new PluginInfo("My_Plugin", "1.0.0", "desc", ["./skills/"], [], "/tmp/My_Plugin", "My_Plugin"); var result = PluginValidator.ValidatePlugin(plugin); Assert.Contains(result.Errors, e => e.Contains("invalid characters")); } @@ -280,7 +363,7 @@ public void NameFormatErrors() [Fact] public void ErrorMessagesSayPluginNotSkill() { - var plugin = new PluginInfo("My_Plugin", "1.0.0", "desc", "./skills/", null, "/tmp/My_Plugin", "My_Plugin"); + var plugin = new PluginInfo("My_Plugin", "1.0.0", "desc", ["./skills/"], [], "/tmp/My_Plugin", "My_Plugin"); var result = PluginValidator.ValidatePlugin(plugin); Assert.Contains(result.Errors, e => e.StartsWith("Plugin name")); Assert.DoesNotContain(result.Errors, e => e.StartsWith("Skill name")); @@ -301,15 +384,60 @@ public void ParsePluginJsonParsesValidFile() { Directory.CreateDirectory(dir); var jsonPath = Path.Combine(dir, "plugin.json"); - File.WriteAllText(jsonPath, """{"name":"my-plugin","version":"0.1.0","description":"A plugin.","skills":"./skills/","agents":"./agents/"}"""); + File.WriteAllText(jsonPath, """{"name":"my-plugin","version":"0.1.0","description":"A plugin.","skills":["./skills/"],"agents":["./agents/"]}"""); var plugin = PluginValidator.ParsePluginJson(jsonPath); Assert.NotNull(plugin); Assert.Equal("my-plugin", plugin.Name); Assert.Equal("0.1.0", plugin.Version); Assert.Equal("A plugin.", plugin.Description); - Assert.Equal("./skills/", plugin.SkillsPath); - Assert.Equal("./agents/", plugin.AgentsPath); + Assert.Single(plugin.SkillPaths); + Assert.Equal("./skills/", plugin.SkillPaths[0]); + Assert.Single(plugin.AgentPaths); + Assert.Equal("./agents/", plugin.AgentPaths[0]); + } + finally + { + Directory.Delete(dir, true); + } + } + + [Fact] + public void ParsePluginJsonNormalizesStringToArray() + { + var dir = Path.Combine(Path.GetTempPath(), "parse-test-" + Guid.NewGuid().ToString("N")); + try + { + Directory.CreateDirectory(dir); + var jsonPath = Path.Combine(dir, "plugin.json"); + File.WriteAllText(jsonPath, """{"name":"my-plugin","version":"0.1.0","description":"A plugin.","skills":"./skills/","agents":"./agents/"}"""); + + var plugin = PluginValidator.ParsePluginJson(jsonPath); + Assert.NotNull(plugin); + Assert.Single(plugin.SkillPaths); + Assert.Equal("./skills/", plugin.SkillPaths[0]); + Assert.Single(plugin.AgentPaths); + Assert.Equal("./agents/", plugin.AgentPaths[0]); + } + finally + { + Directory.Delete(dir, true); + } + } + + [Fact] + public void ParsePluginJsonNoAgentsField() + { + var dir = Path.Combine(Path.GetTempPath(), "parse-test-" + Guid.NewGuid().ToString("N")); + try + { + Directory.CreateDirectory(dir); + var jsonPath = Path.Combine(dir, "plugin.json"); + File.WriteAllText(jsonPath, """{"name":"my-plugin","version":"0.1.0","description":"A plugin.","skills":"./skills/"}"""); + + var plugin = PluginValidator.ParsePluginJson(jsonPath); + Assert.NotNull(plugin); + Assert.Empty(plugin.AgentPaths); } finally { @@ -338,7 +466,7 @@ public void ParsePluginJsonThrowsOnMalformedJson() [Fact] public void AbsoluteSkillsPathErrors() { - var plugin = new PluginInfo("test", "1.0.0", "desc", "/etc/skills/", null, "/tmp/test", "test"); + var plugin = new PluginInfo("test", "1.0.0", "desc", ["/etc/skills/"], [], "/tmp/test", "test"); var result = PluginValidator.ValidatePlugin(plugin); Assert.Contains(result.Errors, e => e.Contains("invalid") && e.Contains("absolute")); } @@ -351,7 +479,7 @@ public void TraversalSkillsPathErrors() { Directory.CreateDirectory(pluginDir); var dirName = Path.GetFileName(pluginDir); - var plugin = new PluginInfo(dirName, "1.0.0", "desc", "../../../etc/", null, pluginDir, dirName); + var plugin = new PluginInfo(dirName, "1.0.0", "desc", ["../../../etc/"], [], pluginDir, dirName); var result = PluginValidator.ValidatePlugin(plugin); Assert.Contains(result.Errors, e => e.Contains("invalid") && e.Contains("outside")); } @@ -364,7 +492,7 @@ public void TraversalSkillsPathErrors() [Fact] public void MissingNameFallsBackToDirectoryName() { - var plugin = new PluginInfo("", "1.0.0", "desc", "./skills/", null, "/tmp/my-plugin", "my-plugin"); + var plugin = new PluginInfo("", "1.0.0", "desc", ["./skills/"], [], "/tmp/my-plugin", "my-plugin"); var result = PluginValidator.ValidatePlugin(plugin); Assert.Equal("my-plugin", result.Name); } diff --git a/eng/skill-validator/tests/ExternalDependencyTests.cs b/eng/skill-validator/tests/ExternalDependencyTests.cs index f6c2c86210..944496bf8d 100644 --- a/eng/skill-validator/tests/ExternalDependencyTests.cs +++ b/eng/skill-validator/tests/ExternalDependencyTests.cs @@ -39,7 +39,7 @@ private static (PluginInfo Plugin, string Dir) MakePlugin(string name = "test-pl var json = extraJson ?? $@"{{""name"":""{name}"",""version"":""0.1.0"",""description"":""Test."",""skills"":""./skills/""}}"; File.WriteAllText(Path.Combine(dir, "plugin.json"), json); - var plugin = new PluginInfo(name, "0.1.0", "Test.", "./skills/", null, dir, Path.GetFileName(dir)); + var plugin = new PluginInfo(name, "0.1.0", "Test.", ["./skills/"], [], dir, Path.GetFileName(dir)); return (plugin, dir); } diff --git a/plugins/dotnet-ai/plugin.json b/plugins/dotnet-ai/plugin.json index aca5815a18..20fbc8bec8 100644 --- a/plugins/dotnet-ai/plugin.json +++ b/plugins/dotnet-ai/plugin.json @@ -2,5 +2,5 @@ "name": "dotnet-ai", "version": "0.1.0", "description": "AI and ML skills for .NET: technology selection, LLM integration, agentic workflows, RAG pipelines, MCP, and classic ML with ML.NET.", - "skills": "./skills/" + "skills": ["./skills/"] } diff --git a/plugins/dotnet-data/plugin.json b/plugins/dotnet-data/plugin.json index 87c64fdc2e..5fef08c1a6 100644 --- a/plugins/dotnet-data/plugin.json +++ b/plugins/dotnet-data/plugin.json @@ -2,5 +2,5 @@ "name": "dotnet-data", "version": "0.1.0", "description": "Skills for .NET data access and Entity Framework related tasks.", - "skills": "./skills/" + "skills": ["./skills/"] } diff --git a/plugins/dotnet-diag/plugin.json b/plugins/dotnet-diag/plugin.json index e77893ac21..3bd3deb6fb 100644 --- a/plugins/dotnet-diag/plugin.json +++ b/plugins/dotnet-diag/plugin.json @@ -2,6 +2,6 @@ "name": "dotnet-diag", "version": "0.1.0", "description": "Skills for .NET performance investigations, debugging, and incident analysis.", - "skills": "./skills/", - "agents": "./agents/" + "skills": ["./skills/"], + "agents": ["./agents/"] } diff --git a/plugins/dotnet-maui/plugin.json b/plugins/dotnet-maui/plugin.json index 0160aa99f8..c50869fac8 100644 --- a/plugins/dotnet-maui/plugin.json +++ b/plugins/dotnet-maui/plugin.json @@ -2,5 +2,5 @@ "name": "dotnet-maui", "version": "0.1.0", "description": "Skills for .NET MAUI development: environment setup, diagnostics, and troubleshooting.", - "skills": "./skills/" + "skills": ["./skills/"] } diff --git a/plugins/dotnet-msbuild/plugin.json b/plugins/dotnet-msbuild/plugin.json index 0dd1648e19..2aa5c87d15 100644 --- a/plugins/dotnet-msbuild/plugin.json +++ b/plugins/dotnet-msbuild/plugin.json @@ -2,6 +2,6 @@ "name": "dotnet-msbuild", "version": "0.1.0", "description": "Comprehensive MSBuild and .NET build skills: failure diagnosis, performance optimization, code quality, and modernization.", - "skills": "./skills/", - "agents": "./agents/" + "skills": ["./skills/"], + "agents": ["./agents/"] } diff --git a/plugins/dotnet-nuget/plugin.json b/plugins/dotnet-nuget/plugin.json index 1d83c7b0fb..11b45202c2 100644 --- a/plugins/dotnet-nuget/plugin.json +++ b/plugins/dotnet-nuget/plugin.json @@ -2,5 +2,5 @@ "name": "dotnet-nuget", "version": "0.1.0", "description": "NuGet and .NET package management skills: dependency management and modernization.", - "skills": "./skills/" + "skills": ["./skills/"] } diff --git a/plugins/dotnet-template-engine/plugin.json b/plugins/dotnet-template-engine/plugin.json index 8e7d722dce..902519bd2f 100644 --- a/plugins/dotnet-template-engine/plugin.json +++ b/plugins/dotnet-template-engine/plugin.json @@ -2,6 +2,6 @@ "name": "dotnet-template-engine", "version": "0.1.0", "description": ".NET Template Engine skills: template discovery, project scaffolding, and template authoring.", - "skills": "./skills/", - "agents": "./agents/" + "skills": ["./skills/"], + "agents": ["./agents/"] } diff --git a/plugins/dotnet-test/plugin.json b/plugins/dotnet-test/plugin.json index e270b4eea3..e7a3ebd8b1 100644 --- a/plugins/dotnet-test/plugin.json +++ b/plugins/dotnet-test/plugin.json @@ -2,5 +2,5 @@ "name": "dotnet-test", "version": "0.1.0", "description": "Skills for running, diagnosing, and migrating .NET tests: test execution, filtering, platform detection, and MSTest workflows.", - "skills": "./skills/" + "skills": ["./skills/"] } diff --git a/plugins/dotnet-upgrade/plugin.json b/plugins/dotnet-upgrade/plugin.json index 2773e2c277..0f3bc203bd 100644 --- a/plugins/dotnet-upgrade/plugin.json +++ b/plugins/dotnet-upgrade/plugin.json @@ -2,5 +2,5 @@ "name": "dotnet-upgrade", "version": "0.1.0", "description": "Skills for migrating and upgrading .NET projects across framework versions, language features, and compatibility targets.", - "skills": "./skills/" + "skills": ["./skills/"] } diff --git a/plugins/dotnet/plugin.json b/plugins/dotnet/plugin.json index b521a0fb87..c657ad54f2 100644 --- a/plugins/dotnet/plugin.json +++ b/plugins/dotnet/plugin.json @@ -2,6 +2,6 @@ "name": "dotnet", "version": "0.1.0", "description": "Common everyday C#/.NET coding skills. Expected to be useful to all .NET developers.", - "skills": "./skills/", + "skills": ["./skills/"], "lspServers": "./lsp.json" }