mirror of
https://github.com/dotnet/skills.git
synced 2026-09-20 09:49:54 +08:00
b92dde1854
Reorganize the skill-validator project from layer-based grouping (Commands/, Services/, Models/, Utilities/) to feature-based slices (Check/, Evaluate/, Consolidate/, Shared/). Key changes: - Check/: CheckCommand, SkillProfiler, AgentProfiler, PluginValidator, ExternalDependencyChecker, ReferenceScanner - Evaluate/: EvaluateCommand, RejudgeCommand (now a subcommand of evaluate), AgentRunner, Judge, PairwiseJudge, and other eval services - Consolidate/: ConsolidateCommand - Shared/: Models, SkillDiscovery, PluginParser (extracted from PluginValidator), Reporter, and utilities Split PluginValidator: parsing helpers (ParsePluginJson, TryGetSafeSubdirectory) moved to Shared/PluginParser.cs; validation logic stays in Check/PluginValidator.cs. Move RawFrontmatter and RawAgentFrontmatter types from EvalSchema into SkillDiscovery since they are only consumed by discovery. Wire RejudgeCommand as subcommand: skill-validator evaluate rejudge. Co-authored-by: Viktor Hofer <vihofer@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
125 lines
4.3 KiB
C#
125 lines
4.3 KiB
C#
using SkillValidator.Shared;
|
|
|
|
namespace SkillValidator.Tests;
|
|
|
|
public class DiscoverSkillsTests
|
|
{
|
|
private static string FixturesPath => Path.Combine(AppContext.BaseDirectory, "fixtures");
|
|
|
|
[Fact]
|
|
public async Task DiscoversASingleSkillDirectly()
|
|
{
|
|
var skills = await SkillDiscovery.DiscoverSkills(Path.Combine(FixturesPath, "sample-skill"));
|
|
Assert.Single(skills);
|
|
Assert.Equal("sample-skill", skills[0].Name);
|
|
Assert.Contains("greeting", skills[0].Description);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DiscoversSkillsInParentDirectory()
|
|
{
|
|
var skills = await SkillDiscovery.DiscoverSkills(FixturesPath);
|
|
Assert.True(skills.Count >= 2);
|
|
var names = skills.Select(s => s.Name).ToList();
|
|
Assert.Contains("sample-skill", names);
|
|
Assert.Contains("no-eval-skill", names);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandlesSkillWithNoEvalYaml()
|
|
{
|
|
var skills = await SkillDiscovery.DiscoverSkills(Path.Combine(FixturesPath, "no-eval-skill"));
|
|
Assert.Single(skills);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReturnsEmptyForNonSkillDirectory()
|
|
{
|
|
var tmpDir = Path.Combine(Path.GetTempPath(), $"skill-test-{Guid.NewGuid():N}");
|
|
Directory.CreateDirectory(tmpDir);
|
|
try
|
|
{
|
|
var skills = await SkillDiscovery.DiscoverSkills(tmpDir);
|
|
Assert.Empty(skills);
|
|
}
|
|
finally
|
|
{
|
|
Directory.Delete(tmpDir, recursive: true);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DiscoverSkillsRecursiveFindsNestedSkills()
|
|
{
|
|
// Simulates plugins/<plugin>/skills/<skill>/SKILL.md layout
|
|
var tmpDir = Path.Combine(Path.GetTempPath(), $"skill-test-{Guid.NewGuid():N}");
|
|
var skill1Dir = Path.Combine(tmpDir, "plugin-a", "skills", "skill-one");
|
|
var skill2Dir = Path.Combine(tmpDir, "plugin-b", "skills", "skill-two");
|
|
Directory.CreateDirectory(skill1Dir);
|
|
Directory.CreateDirectory(skill2Dir);
|
|
try
|
|
{
|
|
await File.WriteAllTextAsync(Path.Combine(skill1Dir, "SKILL.md"), "---\nname: skill-one\ndescription: first\n---\nBody", TestContext.Current.CancellationToken);
|
|
await File.WriteAllTextAsync(Path.Combine(skill2Dir, "SKILL.md"), "---\nname: skill-two\ndescription: second\n---\nBody", TestContext.Current.CancellationToken);
|
|
|
|
var skills = await SkillDiscovery.DiscoverSkillsRecursive(tmpDir);
|
|
Assert.Equal(2, skills.Count);
|
|
var names = skills.Select(s => s.Name).OrderBy(n => n).ToList();
|
|
Assert.Equal("skill-one", names[0]);
|
|
Assert.Equal("skill-two", names[1]);
|
|
}
|
|
finally
|
|
{
|
|
Directory.Delete(tmpDir, true);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DiscoverSkillsRecursiveReturnsEmptyForMissingDir()
|
|
{
|
|
var skills = await SkillDiscovery.DiscoverSkillsRecursive(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")));
|
|
Assert.Empty(skills);
|
|
}
|
|
}
|
|
|
|
public class PluginDiscoveryTests
|
|
{
|
|
[Fact]
|
|
public void FindPluginContextReturnsNullWithoutPluginJson()
|
|
{
|
|
var tmpDir = Path.Combine(Path.GetTempPath(), $"ctx-test-{Guid.NewGuid():N}");
|
|
Directory.CreateDirectory(tmpDir);
|
|
try
|
|
{
|
|
var skill = new SkillInfo("test", "T", tmpDir, Path.Combine(tmpDir, "SKILL.md"), "# T");
|
|
var result = PluginDiscovery.FindPluginContext(skill);
|
|
Assert.Null(result);
|
|
}
|
|
finally
|
|
{
|
|
Directory.Delete(tmpDir, true);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void FindPluginContextReturnsPluginInfo()
|
|
{
|
|
var tmpDir = Path.Combine(Path.GetTempPath(), $"ctx-test-{Guid.NewGuid():N}");
|
|
var pluginDir = Path.Combine(tmpDir, "my-plugin");
|
|
var skillDir = Path.Combine(pluginDir, "skills", "test-skill");
|
|
Directory.CreateDirectory(skillDir);
|
|
try
|
|
{
|
|
File.WriteAllText(Path.Combine(pluginDir, "plugin.json"), """{ "name": "my-plugin" }""");
|
|
var skill = new SkillInfo("test-skill", "T", skillDir, Path.Combine(skillDir, "SKILL.md"), "# T");
|
|
var result = PluginDiscovery.FindPluginContext(skill);
|
|
Assert.NotNull(result);
|
|
Assert.Equal(pluginDir, result!.Value.PluginRoot);
|
|
}
|
|
finally
|
|
{
|
|
Directory.Delete(tmpDir, true);
|
|
}
|
|
}
|
|
}
|