166 lines
5.9 KiB
C#
166 lines
5.9 KiB
C#
using AxCopilot.Services.Agent;
|
|
using FluentAssertions;
|
|
using Xunit;
|
|
using System;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
|
|
namespace AxCopilot.Tests.Services;
|
|
|
|
public class SkillServiceRuntimePolicyTests
|
|
{
|
|
[Fact]
|
|
public void BuildRuntimeDirective_ReturnsEmpty_WhenNoRuntimeMetadata()
|
|
{
|
|
var skill = new SkillDefinition
|
|
{
|
|
Name = "plain-skill",
|
|
SystemPrompt = "do work"
|
|
};
|
|
|
|
var directive = SkillService.BuildRuntimeDirective(skill);
|
|
|
|
directive.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildRuntimeDirective_ContainsForkAgentEffortAndModelHints()
|
|
{
|
|
var skill = new SkillDefinition
|
|
{
|
|
Name = "advanced-skill",
|
|
ExecutionContext = "fork",
|
|
Agent = "worker",
|
|
Effort = "high",
|
|
Model = "gpt-5.4",
|
|
DisableModelInvocation = true,
|
|
AllowedTools = "Read, process, WebFetch",
|
|
Hooks = "lint-pre, verify-post",
|
|
HookFilters = "lint-pre@pre@file_edit, verify-post@post@*"
|
|
};
|
|
|
|
var directive = SkillService.BuildRuntimeDirective(skill);
|
|
|
|
directive.Should().Contain("[Skill Runtime Policy]");
|
|
directive.Should().Contain("execution_context: fork");
|
|
directive.Should().Contain("preferred_agent: worker");
|
|
directive.Should().Contain("reasoning_effort: high");
|
|
directive.Should().Contain("preferred_model: gpt-5.4");
|
|
directive.Should().Contain("allowed_tools: file_read, http_tool, process");
|
|
directive.Should().Contain("hook_names: lint-pre, verify-post");
|
|
directive.Should().Contain("hook_filters: lint-pre@pre@file_edit, verify-post@post@*");
|
|
directive.Should().Contain("disable_model_invocation");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSkillFile_HooksMapAndList_AreNormalizedIntoHooksField()
|
|
{
|
|
var tempDir = Path.Combine(Path.GetTempPath(), "axcopilot-skill-hooks-" + Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(tempDir);
|
|
var skillPath = Path.Combine(tempDir, "SKILL.md");
|
|
try
|
|
{
|
|
var content = """
|
|
---
|
|
name: hook-skill
|
|
hooks:
|
|
pre: lint-pre, verify-pre
|
|
post:
|
|
- verify-post
|
|
- report-post
|
|
---
|
|
|
|
body
|
|
""";
|
|
File.WriteAllText(skillPath, content, Encoding.UTF8);
|
|
|
|
var method = typeof(SkillService).GetMethod("ParseSkillFile", BindingFlags.NonPublic | BindingFlags.Static);
|
|
method.Should().NotBeNull();
|
|
|
|
var parsed = method!.Invoke(null, [skillPath]) as SkillDefinition;
|
|
parsed.Should().NotBeNull();
|
|
parsed!.Hooks.Should().Contain("lint-pre");
|
|
parsed.Hooks.Should().Contain("verify-pre");
|
|
parsed.Hooks.Should().Contain("verify-post");
|
|
parsed.Hooks.Should().Contain("report-post");
|
|
parsed.HookFilters.Should().Contain("lint-pre@pre@*");
|
|
parsed.HookFilters.Should().Contain("verify-pre@pre@*");
|
|
parsed.HookFilters.Should().Contain("*@post@*");
|
|
}
|
|
finally
|
|
{
|
|
try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSkillFile_NestedHooksMap_PreservesToolTimingFilters()
|
|
{
|
|
var tempDir = Path.Combine(Path.GetTempPath(), "axcopilot-skill-hooks-nested-" + Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(tempDir);
|
|
var skillPath = Path.Combine(tempDir, "SKILL.md");
|
|
try
|
|
{
|
|
var content = """
|
|
---
|
|
name: nested-hook-skill
|
|
hooks:
|
|
file_edit:
|
|
pre:
|
|
- lint-pre
|
|
post: verify-post
|
|
---
|
|
|
|
body
|
|
""";
|
|
File.WriteAllText(skillPath, content, Encoding.UTF8);
|
|
|
|
var method = typeof(SkillService).GetMethod("ParseSkillFile", BindingFlags.NonPublic | BindingFlags.Static);
|
|
method.Should().NotBeNull();
|
|
|
|
var parsed = method!.Invoke(null, [skillPath]) as SkillDefinition;
|
|
parsed.Should().NotBeNull();
|
|
parsed!.Hooks.Should().Contain("lint-pre");
|
|
parsed.Hooks.Should().Contain("verify-post");
|
|
parsed.HookFilters.Should().Contain("lint-pre@pre@file_edit");
|
|
parsed.HookFilters.Should().Contain("verify-post@post@file_edit");
|
|
}
|
|
finally
|
|
{
|
|
try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSkillFile_SampleFlag_SetsIsSample()
|
|
{
|
|
var tempDir = Path.Combine(Path.GetTempPath(), "axcopilot-skill-sample-" + Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(tempDir);
|
|
var skillPath = Path.Combine(tempDir, "SKILL.md");
|
|
try
|
|
{
|
|
var content = """
|
|
---
|
|
name: sample-skill
|
|
sample: true
|
|
---
|
|
|
|
body
|
|
""";
|
|
File.WriteAllText(skillPath, content, Encoding.UTF8);
|
|
|
|
var method = typeof(SkillService).GetMethod("ParseSkillFile", BindingFlags.NonPublic | BindingFlags.Static);
|
|
method.Should().NotBeNull();
|
|
|
|
var parsed = method!.Invoke(null, [skillPath]) as SkillDefinition;
|
|
parsed.Should().NotBeNull();
|
|
parsed!.IsSample.Should().BeTrue();
|
|
}
|
|
finally
|
|
{
|
|
try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
|
|
}
|
|
}
|
|
}
|