문서형 기본 제공 스킬 노출과 추천 메타데이터를 정리한다

- pptx/docx/report/prd/회의록/주간보고/markdown 변환 스킬에 when_to_use 및 argument-hint 메타를 추가해 자동 추천 품질을 높인다.

- 설정 화면과 스킬 갤러리에서 managed 스코프를 기본 제공 스킬로 분리해 배포 자산과 사용자 스킬이 섞여 보이지 않게 한다.

- README와 DEVELOPMENT 문서에 2026-04-14 18:37(KST) 기준 작업 이력과 검증 결과를 반영한다.

- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_docskills\\ -p:IntermediateOutputPath=obj\\verify_docskills\\ 경고 0 / 오류 0
This commit is contained in:
2026-04-14 18:41:20 +09:00
parent 8cf025e14d
commit ac37311e41
12 changed files with 83 additions and 24 deletions

View File

@@ -668,11 +668,12 @@ public partial class AgentSettingsWindow : Window
var groups = new[]
{
new { Title = "번들 스킬", Items = skills.Where(s => string.Equals(s.SourceScope, "bundled", StringComparison.OrdinalIgnoreCase)).ToList() },
new { Title = "기본 제공 스킬", Items = skills.Where(s => string.Equals(s.SourceScope, "managed", StringComparison.OrdinalIgnoreCase)).ToList() },
new { Title = "프로젝트 스킬", Items = skills.Where(s => string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase)).ToList() },
new { Title = "플러그인 스킬", Items = skills.Where(s => string.Equals(s.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase)).ToList() },
new { Title = "보조/공용 스킬", Items = skills.Where(s => string.Equals(s.SourceScope, "additional", StringComparison.OrdinalIgnoreCase)).ToList() },
new { Title = "레거시 명령 스킬", Items = skills.Where(s => string.Equals(s.SourceScope, "legacy", StringComparison.OrdinalIgnoreCase)).ToList() },
new { Title = "사용자/추가 스킬", Items = skills.Where(s => !string.Equals(s.SourceScope, "bundled", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "additional", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "legacy", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(s.Requires)).ToList() },
new { Title = "사용자 스킬", Items = skills.Where(s => !string.Equals(s.SourceScope, "bundled", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "managed", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "additional", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "legacy", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(s.Requires)).ToList() },
new { Title = "고급 스킬", Items = skills.Where(s => !string.IsNullOrWhiteSpace(s.Requires) && !string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase)).ToList() },
};

View File

@@ -654,11 +654,13 @@ public partial class SettingsWindow : Window
});
var bundled = skills.Where(s => string.Equals(s.SourceScope, "bundled", StringComparison.OrdinalIgnoreCase)).ToList();
var managed = skills.Where(s => string.Equals(s.SourceScope, "managed", StringComparison.OrdinalIgnoreCase)).ToList();
var project = skills.Where(s => string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase)).ToList();
var plugin = skills.Where(s => string.Equals(s.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase)).ToList();
var additional = skills.Where(s => string.Equals(s.SourceScope, "additional", StringComparison.OrdinalIgnoreCase)).ToList();
var legacy = skills.Where(s => string.Equals(s.SourceScope, "legacy", StringComparison.OrdinalIgnoreCase)).ToList();
var custom = skills.Where(s => !string.Equals(s.SourceScope, "bundled", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(s.SourceScope, "managed", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(s.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(s.SourceScope, "additional", StringComparison.OrdinalIgnoreCase)
@@ -673,6 +675,12 @@ public partial class SettingsWindow : Window
skillItems.Add(card);
}
if (managed.Count > 0)
{
var card = CreateSkillGroupCard("기본 제공 스킬", "\uE8FD", "#0EA5E9", managed);
skillItems.Add(card);
}
if (project.Count > 0)
{
var card = CreateSkillGroupCard("프로젝트 스킬", "\uE8F1", "#2563EB", project);
@@ -699,7 +707,7 @@ public partial class SettingsWindow : Window
if (custom.Count > 0)
{
var card = CreateSkillGroupCard("사용자/추가 스킬", "\uE70F", "#F59E0B", custom);
var card = CreateSkillGroupCard("사용자 스킬", "\uE70F", "#F59E0B", custom);
skillItems.Add(card);
}

View File

@@ -73,20 +73,9 @@ public partial class SkillGalleryWindow : Window
var skills = SkillService.Skills;
var categories = new[] { "전체" }
.Concat(skills
.Select(s => string.IsNullOrEmpty(s.Requires) ? "내장" : "고급 (런타임)")
.Distinct())
.Concat(GetSkillCategories(skills))
.ToList();
// 사용자 스킬이 있으면 추가
var userFolder = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"AxCopilot", "skills");
var hasUser = skills.Any(s =>
s.FilePath.StartsWith(userFolder, StringComparison.OrdinalIgnoreCase));
if (hasUser && !categories.Contains("사용자"))
categories.Add("사용자");
foreach (var cat in categories)
{
var btn = new Border
@@ -153,16 +142,13 @@ public partial class SkillGalleryWindow : Window
private List<SkillDefinition> FilterSkills(IReadOnlyList<SkillDefinition> skills)
{
var userFolder = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"AxCopilot", "skills");
return _selectedCategory switch
{
"내장" => skills.Where(s => string.IsNullOrEmpty(s.Requires)
&& !s.FilePath.StartsWith(userFolder, StringComparison.OrdinalIgnoreCase)).ToList(),
"기본 제공" => skills.Where(IsBuiltInSkill).ToList(),
"프로젝트" => skills.Where(s => string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase)).ToList(),
"플러그인" => skills.Where(s => string.Equals(s.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase)).ToList(),
"고급 (런타임)" => skills.Where(s => !string.IsNullOrEmpty(s.Requires)).ToList(),
"사용자" => skills.Where(s => s.FilePath.StartsWith(userFolder, StringComparison.OrdinalIgnoreCase)).ToList(),
"사용자" => skills.Where(IsUserOwnedSkill).ToList(),
_ => skills.ToList(),
};
}
@@ -173,8 +159,11 @@ public partial class SkillGalleryWindow : Window
var fgBrush = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
var subBrush = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
var isUser = skill.FilePath.StartsWith(userFolder, StringComparison.OrdinalIgnoreCase);
var isUser = IsUserOwnedSkill(skill);
var isAdvanced = !string.IsNullOrEmpty(skill.Requires);
var isBuiltIn = IsBuiltInSkill(skill);
var isProject = string.Equals(skill.SourceScope, "project", StringComparison.OrdinalIgnoreCase);
var isPlugin = string.Equals(skill.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase);
var card = new Border
{
@@ -238,12 +227,18 @@ public partial class SkillGalleryWindow : Window
});
// 소스/유형 뱃지
if (isUser)
if (isProject)
nameRow.Children.Add(MakeBadge("프로젝트", "#2563EB"));
else if (isPlugin)
nameRow.Children.Add(MakeBadge("플러그인", "#EC4899"));
else if (isUser)
nameRow.Children.Add(MakeBadge("사용자", "#34D399"));
else if (isAdvanced)
nameRow.Children.Add(MakeBadge("고급", "#A78BFA"));
else if (isBuiltIn)
nameRow.Children.Add(MakeBadge("기본 제공", "#0EA5E9"));
else
nameRow.Children.Add(MakeBadge("내장", "#9CA3AF"));
nameRow.Children.Add(MakeBadge("번들", "#9CA3AF"));
if (skill.IsSample)
nameRow.Children.Add(MakeBadge("예제", "#F59E0B"));
@@ -402,6 +397,33 @@ public partial class SkillGalleryWindow : Window
};
}
private static IEnumerable<string> GetSkillCategories(IReadOnlyList<SkillDefinition> skills)
{
if (skills.Any(IsBuiltInSkill))
yield return "기본 제공";
if (skills.Any(s => string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase)))
yield return "프로젝트";
if (skills.Any(s => string.Equals(s.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase)))
yield return "플러그인";
if (skills.Any(s => !string.IsNullOrEmpty(s.Requires)))
yield return "고급 (런타임)";
if (skills.Any(IsUserOwnedSkill))
yield return "사용자";
}
private static bool IsBuiltInSkill(SkillDefinition skill) =>
string.Equals(skill.SourceScope, "bundled", StringComparison.OrdinalIgnoreCase) ||
string.Equals(skill.SourceScope, "managed", StringComparison.OrdinalIgnoreCase);
private static bool IsUserOwnedSkill(SkillDefinition skill) =>
string.Equals(skill.SourceScope, "user", StringComparison.OrdinalIgnoreCase) ||
string.Equals(skill.SourceScope, "custom", StringComparison.OrdinalIgnoreCase) ||
string.Equals(skill.SourceScope, "additional", StringComparison.OrdinalIgnoreCase) ||
string.Equals(skill.SourceScope, "legacy", StringComparison.OrdinalIgnoreCase) ||
(!IsBuiltInSkill(skill)
&& !string.Equals(skill.SourceScope, "project", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(skill.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase));
private Border MakeActionBtn(string icon, string colorHex, string tooltip, Action action)
{
var col = (Color)ColorConverter.ConvertFromString(colorHex);