스킬 런타임 2차 고도화와 도구 노출 필터 정비

프로젝트 .claude/skills 재귀 로드와 namespaced SKILL.md 파싱을 추가하고 번들/사용자/프로젝트 스킬을 함께 노출하도록 SkillService와 설정 UI를 확장했다.

슬래시 스킬 호출 시 인자 치환, 스킬 폴더 변수 치환, inline shell 실행, when_to_use 기반 자동 스킬 가이드를 실제 ChatWindow 런타임 경로에 연결했다.

blanket deny 권한은 모델 노출 전 활성 도구 목록에서 먼저 제외하도록 AgentLoopService를 보강했고 관련 테스트와 README/DEVELOPMENT 문서를 업데이트했다.

검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_phase2\\ -p:IntermediateOutputPath=obj\\verify_phase2\\ (경고 0 / 오류 0)
검증: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AgentToolCatalogTests|SkillServiceRuntimePolicyTests" -p:OutputPath=bin\\verify_phase2_tests\\ -p:IntermediateOutputPath=obj\\verify_phase2_tests\\ (통과 16, 기존 WorkspaceContextGeneratorTests nullable 경고 1건 유지)
This commit is contained in:
2026-04-14 18:10:16 +09:00
parent 8cb08576d5
commit b17c865c4e
12 changed files with 805 additions and 81 deletions

View File

@@ -94,7 +94,9 @@ public partial class SettingsWindow : Window
await Task.Run(() =>
{
Services.Agent.SkillService.EnsureSkillFolder();
Services.Agent.SkillService.LoadSkills(app?.SettingsService?.Settings.Llm.SkillsFolderPath);
Services.Agent.SkillService.LoadSkills(
app?.SettingsService?.Settings.Llm.SkillsFolderPath,
ResolveSkillProjectRoot(app?.SettingsService?.Settings.Llm));
});
}
@@ -115,6 +117,27 @@ public partial class SettingsWindow : Window
};
}
private static string? ResolveSkillProjectRoot(Models.LlmSettings? llm)
{
if (llm == null)
return null;
var candidates = new[]
{
llm.CodeWorkFolder,
llm.CoworkWorkFolder,
llm.WorkFolder
};
foreach (var candidate in candidates)
{
if (!string.IsNullOrWhiteSpace(candidate) && Directory.Exists(candidate))
return candidate.Trim();
}
return null;
}
private void BindIndexProgress()
{
_indexService = (System.Windows.Application.Current as App)?.IndexService;
@@ -616,26 +639,40 @@ public partial class SettingsWindow : Window
// 설명
skillItems.Add(new TextBlock
{
Text = "/ 명령으로 호출할 수 있는 스킬 목록입니다. 앱 내장 + 사용자 추가 스킬 포함됩니다.\n" +
"(직접 호출 가능한 스킬과 런타임 정책에 연결되는 스킬을 함께 표시합니다.)",
Text = "/ 명령으로 호출할 수 있는 스킬 목록입니다. 번들 스킬, 사용자/추가 스킬, 프로젝트 `.claude/skills`가 함께 포함됩니다.\n" +
"(직접 호출 스킬과 자동/조건부 보조 스킬을 함께 표시합니다.)",
FontSize = 11,
Foreground = new SolidColorBrush(subtleText),
Margin = new Thickness(2, 0, 0, 10),
TextWrapping = TextWrapping.Wrap,
});
// 내장 스킬 / 고급 스킬 분류
var builtIn = skills.Where(s => string.IsNullOrEmpty(s.Requires)).ToList();
var advanced = skills.Where(s => !string.IsNullOrEmpty(s.Requires)).ToList();
var bundled = skills.Where(s => string.Equals(s.SourceScope, "bundled", StringComparison.OrdinalIgnoreCase)).ToList();
var project = skills.Where(s => string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase)).ToList();
var custom = skills.Where(s => !string.Equals(s.SourceScope, "bundled", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase)
&& string.IsNullOrEmpty(s.Requires)).ToList();
var advanced = skills.Where(s => !string.IsNullOrEmpty(s.Requires)
&& !string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase)).ToList();
// 내장 스킬 카드
if (builtIn.Count > 0)
if (bundled.Count > 0)
{
var card = CreateSkillGroupCard("내장 스킬", "\uE768", "#34D399", builtIn);
var card = CreateSkillGroupCard("번들 스킬", "\uE768", "#34D399", bundled);
skillItems.Add(card);
}
if (project.Count > 0)
{
var card = CreateSkillGroupCard("프로젝트 스킬", "\uE8F1", "#2563EB", project);
skillItems.Add(card);
}
if (custom.Count > 0)
{
var card = CreateSkillGroupCard("사용자/추가 스킬", "\uE70F", "#F59E0B", custom);
skillItems.Add(card);
}
// 고급 스킬 (런타임 의존) 카드
if (advanced.Count > 0)
{
var card = CreateSkillGroupCard("고급 스킬 (런타임 필요)", "\uE9D9", "#A78BFA", advanced);