diff --git a/README.md b/README.md index f6fe1ca..5fbab6f 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,13 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저 개발 참고: Claw Code 동등성 작업 추적 문서 `docs/claw-code-parity-plan.md` +- 업데이트: 2026-04-14 18:37 (KST) +- `claude-code` 로컬 스냅샷을 다시 확인했지만, 바로 가져다 쓸 만한 PPT/문서 전용 번들 스킬은 뚜렷하지 않았습니다. 대신 AX에 이미 포함된 `pptx-creator`, `docx-creator`, `report-writer`, `prd-generator`, `meeting-minutes`, `weekly-report`, `markdown-to-doc` 같은 문서형 스킬을 기본 제공 자산으로 더 분명하게 노출하도록 정리했습니다. +- 문서형 스킬에는 `when_to_use`와 `argument-hint` 메타를 보강해 자동 추천 품질을 높였습니다. 이제 발표자료, Word 문서, 보고서, PRD, 회의록, Markdown 문서 변환 요청에서 관련 스킬이 더 자연스럽게 선택될 수 있습니다. +- 설정 화면과 스킬 갤러리도 `managed` 스코프를 별도 `기본 제공 스킬`로 분리해, 앱에 기본 포함되어 배포되는 문서/프레젠테이션 스킬이 사용자 추가 스킬과 섞여 보이지 않게 맞췄습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_docskills\\ -p:IntermediateOutputPath=obj\\verify_docskills\\` 경고 0 / 오류 0 +- 참고: 테스트 빌드 중 기존 파일 `src/AxCopilot.Tests/Services/WorkspaceContextGeneratorTests.cs`의 nullable 경고 1건은 유지됩니다. + - 업데이트: 2026-04-14 18:33 (KST) - 스킬 소스 정책과 inline shell 안전장치를 추가로 정리했습니다. 프로젝트 스킬 자동 탐색, 플러그인 스킬 탐색, 레거시 command 스킬 호환, inline shell 허용 여부와 시간/출력 제한을 설정으로 제어할 수 있습니다. - 스킬 재탐색도 더 민감하게 바꿨습니다. 이제 로드 시그니처가 소스 디렉터리뿐 아니라 실제 스킬 파일 수와 최근 수정 시각을 함께 반영해, 같은 폴더라도 파일이 바뀌면 다음 로드 요청에서 자동으로 다시 읽습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 0f9b239..f30c490 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -824,3 +824,10 @@ UI 디자인 대규모 리팩토링 등 위험 작업 전 기록한 안전 복 > - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_phase4b\\ -p:IntermediateOutputPath=obj\\verify_phase4b\\` 경고 0 / 오류 0 > - 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AgentToolCatalogTests|SkillServiceRuntimePolicyTests" -p:OutputPath=bin\\verify_phase4b_tests\\ -p:IntermediateOutputPath=obj\\verify_phase4b_tests\\` 통과 18 > - 참고: 테스트 프로젝트의 기존 nullable 경고 `src/AxCopilot.Tests/Services/WorkspaceContextGeneratorTests.cs(76)` 1건은 유지됩니다. + +- 업데이트: 2026-04-14 18:37 (KST) +- claude-code 로컬 스냅샷을 다시 확인했지만, 현재 스냅샷에는 PPT/문서 전용 번들 스킬이 뚜렷하지 않았습니다. 대신 AX가 기본 포함하고 있는 문서형 managed skill 세트를 중심으로 배포 자산 품질을 다듬었습니다. +- pptx-creator, docx-creator, report-writer, prd-generator, meeting-minutes, weekly-report, markdown-to-doc에 when_to_use와 argument-hint 메타를 추가해 proactive skill 선택과 슬래시 호출 가이드를 보강했습니다. +- 일반 설정과 AX Agent 설정의 스킬 목록은 managed 스코프를 별도 기본 제공 스킬 그룹으로 분리했고, 스킬 갤러리도 기본 제공 / 프로젝트 / 플러그인 / 사용자 / 고급 필터와 배지를 사용하도록 정리했습니다. +- 이 변경으로 문서·프레젠테이션 스킬은 빌드 출력 skills 폴더를 통해 기본 배포되면서도, UI에서 사용자 스킬과 구분된 상태로 확인할 수 있습니다. +- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_docskills\\ -p:IntermediateOutputPath=obj\\verify_docskills\\ 경고 0 / 오류 0 diff --git a/src/AxCopilot/Views/AgentSettingsWindow.xaml.cs b/src/AxCopilot/Views/AgentSettingsWindow.xaml.cs index 216a077..ba9ebd6 100644 --- a/src/AxCopilot/Views/AgentSettingsWindow.xaml.cs +++ b/src/AxCopilot/Views/AgentSettingsWindow.xaml.cs @@ -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() }, }; diff --git a/src/AxCopilot/Views/SettingsWindow.xaml.cs b/src/AxCopilot/Views/SettingsWindow.xaml.cs index b1f05a7..5043198 100644 --- a/src/AxCopilot/Views/SettingsWindow.xaml.cs +++ b/src/AxCopilot/Views/SettingsWindow.xaml.cs @@ -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); } diff --git a/src/AxCopilot/Views/SkillGalleryWindow.xaml.cs b/src/AxCopilot/Views/SkillGalleryWindow.xaml.cs index c55ba84..3e0e58d 100644 --- a/src/AxCopilot/Views/SkillGalleryWindow.xaml.cs +++ b/src/AxCopilot/Views/SkillGalleryWindow.xaml.cs @@ -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 FilterSkills(IReadOnlyList 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 GetSkillCategories(IReadOnlyList 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); diff --git a/src/AxCopilot/skills/docx-creator.skill.md b/src/AxCopilot/skills/docx-creator.skill.md index 4bd15fd..544e255 100644 --- a/src/AxCopilot/skills/docx-creator.skill.md +++ b/src/AxCopilot/skills/docx-creator.skill.md @@ -3,6 +3,8 @@ name: docx-creator label: Word 문서 생성 description: Python을 사용하여 전문적인 Word 문서(.docx)를 생성합니다. 작업 폴더의 양식 파일을 자동 활용합니다. icon: \uE8A5 +when_to_use: 보고서, 제안서, 공문, 가이드 문서처럼 정식 Word 문서를 새로 만들거나 기존 양식 DOCX를 이어서 작성해야 할 때 +argument-hint: <문서 종류 또는 목적> allowed-tools: - folder_map - document_read diff --git a/src/AxCopilot/skills/markdown-to-doc.skill.md b/src/AxCopilot/skills/markdown-to-doc.skill.md index 94004f3..492c08a 100644 --- a/src/AxCopilot/skills/markdown-to-doc.skill.md +++ b/src/AxCopilot/skills/markdown-to-doc.skill.md @@ -3,6 +3,8 @@ name: markdown-to-doc label: Markdown → 문서 변환 description: Markdown 파일을 서식이 적용된 Word(.docx) 또는 PDF 문서로 변환합니다. icon: \uE8A5 +when_to_use: 이미 정리된 Markdown 초안을 외부 공유용 Word/PDF 문서로 바꿔야 할 때 +argument-hint: <입력 markdown 파일 또는 출력 형식> allowed-tools: - file_read - file_write diff --git a/src/AxCopilot/skills/meeting-minutes.skill.md b/src/AxCopilot/skills/meeting-minutes.skill.md index 9ba9439..0993e3c 100644 --- a/src/AxCopilot/skills/meeting-minutes.skill.md +++ b/src/AxCopilot/skills/meeting-minutes.skill.md @@ -3,6 +3,8 @@ name: meeting-minutes label: 회의록 정리 description: 회의 내용을 체계적으로 정리하여 회의록을 생성합니다. icon: \uE771 +when_to_use: 회의 메모, 녹취 정리본, 액션 아이템을 바탕으로 공식 회의록이나 의사결정 기록을 남겨야 할 때 +argument-hint: <회의 주제> allowed-tools: - file_read - file_write diff --git a/src/AxCopilot/skills/pptx-creator.skill.md b/src/AxCopilot/skills/pptx-creator.skill.md index a8670e8..e8a7396 100644 --- a/src/AxCopilot/skills/pptx-creator.skill.md +++ b/src/AxCopilot/skills/pptx-creator.skill.md @@ -3,6 +3,8 @@ name: pptx-creator label: PPT 프레젠테이션 생성 description: Python을 사용하여 전문적인 PowerPoint 프레젠테이션을 생성합니다. 작업 폴더의 양식 파일을 자동 활용합니다. icon: \uE7BE +when_to_use: 발표 자료, 제안서 deck, 보고용 슬라이드, 교육용 프레젠테이션을 새로 만들어야 하거나 기존 양식 PPT를 활용해야 할 때 +argument-hint: <주제 또는 문서 목적> allowed-tools: - folder_map - document_read diff --git a/src/AxCopilot/skills/prd-generator.skill.md b/src/AxCopilot/skills/prd-generator.skill.md index e284096..5a2263e 100644 --- a/src/AxCopilot/skills/prd-generator.skill.md +++ b/src/AxCopilot/skills/prd-generator.skill.md @@ -3,6 +3,8 @@ name: prd-generator label: 요구사항 정의서 (PRD) description: 제품 요구사항 정의서, 유저 스토리, 수용 기준을 체계적으로 생성합니다. icon: \uE8A5 +when_to_use: 신규 기능, 제품 개선안, PoC 범위를 정리하면서 PRD 초안과 수용 기준이 필요할 때 +argument-hint: <제품명 또는 기능명> allowed-tools: - file_read - file_write diff --git a/src/AxCopilot/skills/report-writer.skill.md b/src/AxCopilot/skills/report-writer.skill.md index 664802b..90db5c7 100644 --- a/src/AxCopilot/skills/report-writer.skill.md +++ b/src/AxCopilot/skills/report-writer.skill.md @@ -3,6 +3,8 @@ name: report-writer label: 보고서 작성 description: 작업 폴더의 데이터를 분석하여 체계적인 업무 보고서를 생성합��다. icon: \uE9F9 +when_to_use: 파일과 데이터 근거를 바탕으로 주간·월간·현황·분석 보고서를 빠르게 정리해야 할 때 +argument-hint: <보고 목적 또는 대상> allowed-tools: - folder_map - file_read diff --git a/src/AxCopilot/skills/weekly-report.skill.md b/src/AxCopilot/skills/weekly-report.skill.md index 304d257..b5a587c 100644 --- a/src/AxCopilot/skills/weekly-report.skill.md +++ b/src/AxCopilot/skills/weekly-report.skill.md @@ -3,6 +3,8 @@ name: weekly-report label: 주간 보고서 description: 작업 폴더의 변경 이력을 기반으로 자동 주간보고 초안을 생성합니다. icon: \uE787 +when_to_use: 최근 1주일 변경 이력과 작업 상황을 바탕으로 팀/리더 공유용 주간 보고서를 준비할 때 +argument-hint: <보고 대상 또는 기간> allowed-tools: - git_tool - folder_map