diff --git a/README.md b/README.md index c280088..7b93752 100644 --- a/README.md +++ b/README.md @@ -1865,3 +1865,12 @@ MIT License - 테스트로 [ArtifactRepairGuideServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ArtifactRepairGuideServiceTests.cs)를 추가했고, [ExcelSkillDashboardSummaryTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ExcelSkillDashboardSummaryTests.cs), [HtmlSkillConsultingSectionsTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/HtmlSkillConsultingSectionsTests.cs)를 확장해 새 archetype과 repair guide를 회귀 검증했습니다. - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_next6\\ -p:IntermediateOutputPath=obj\\verify_doc_next6\\` 경고 0 / 오류 0 - 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|ArtifactRepairGuideServiceTests|ExcelSkillDashboardSummaryTests|HtmlSkillConsultingSectionsTests|HtmlSkillPrintFrameTests|DocumentAssemblerStyleMapTests|DocumentAssemblerDocxFeaturesTests|DocumentAssemblerSemanticTests|PptxSkillGoldenDeckTests|DeckQualityReviewServiceTests" -p:OutputPath=bin\\verify_doc_next6_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_next6_tests\\` 통과 17 + +업데이트: 2026-04-15 00:19 (KST) +- 에이전틱 루프 입력 큐를 확장했습니다. [AgentCommandQueue.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentCommandQueue.cs)는 `steering`, `permission continuation`, `resume`, `user decision`까지 분리해서 담을 수 있고, [AgentLoopService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentLoopService.cs)는 이를 `queued_steering`, `queue_permission_continuation`, `queue_resume` 메타 메시지로 주입해 실행 중 새 지시와 승인 흐름을 더 안정적으로 다룹니다. +- 코드 탭 언어 지원도 넓혔습니다. [CodeLanguageCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/CodeLanguageCatalog.cs)는 `Go`, `Rust`, `PHP`, `Ruby`, `Kotlin`, `Swift`를 LSP 심화 지원군에 포함하고, [LspClientService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/LspClientService.cs)는 `gopls`, `rust-analyzer`, `intelephense`, `solargraph`, `kotlin-language-server`, `sourcekit-lsp`를 로컬 설치 서버 기준으로 탐지합니다. +- 내부 설정의 코드 탭 설명도 보강했습니다. [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml)과 [SettingsViewModel.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/ViewModels/SettingsViewModel.cs)는 `빠른 선택 언어`, `지원 언어(LSP)`, `코드 탭 기본 지원`을 분리 표기해 격리 환경에서 어떤 수준의 지원이 가능한지 더 명확히 보여줍니다. +- 워크스페이스 컨텍스트 생성기도 강화했습니다. [WorkspaceContextGenerator.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs)는 `Language Snapshot`, `Agent Context`, `Key Manifests` 섹션을 추가하고 `.claude/skills`, `.ax/rules`, `AXMEMORY.md`, 주요 manifest 파일을 함께 요약합니다. +- PPT는 품질 리뷰 뒤에 바로 실행 가능한 보정 가이드를 붙이도록 [DeckRepairGuideService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/DeckRepairGuideService.cs)를 추가했고, [PptxSkill.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/PptxSkill.cs)는 `Deck repair guide:`를 함께 반환합니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_master_batch\\ -p:IntermediateOutputPath=obj\\verify_master_batch\\` 경고 0 / 오류 0 +- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AgentCommandQueueTests|CodeLanguageCatalogTests|WorkspaceContextGeneratorTests|PptxSkillConsultingDeckTests|DeckRepairGuideServiceTests" -p:OutputPath=bin\\verify_master_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_master_batch_tests\\` 통과 35 diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 2061619..12a41df 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -939,3 +939,13 @@ UI ?붿옄???€洹쒕え 由ы뙥?좊쭅 ???꾪뿕 ?묒뾽 ??湲곕줉???덉쟾 - 테스트로 [ArtifactRepairGuideServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ArtifactRepairGuideServiceTests.cs)를 추가했고, [ExcelSkillDashboardSummaryTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ExcelSkillDashboardSummaryTests.cs), [HtmlSkillConsultingSectionsTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/HtmlSkillConsultingSectionsTests.cs)를 확장해 archetype과 repair guide를 함께 회귀 검증했습니다. - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_next6\\ -p:IntermediateOutputPath=obj\\verify_doc_next6\\` 경고 0 / 오류 0 - 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|ArtifactRepairGuideServiceTests|ExcelSkillDashboardSummaryTests|HtmlSkillConsultingSectionsTests|HtmlSkillPrintFrameTests|DocumentAssemblerStyleMapTests|DocumentAssemblerDocxFeaturesTests|DocumentAssemblerSemanticTests|PptxSkillGoldenDeckTests|DeckQualityReviewServiceTests" -p:OutputPath=bin\\verify_doc_next6_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_next6_tests\\` 통과 17 + +업데이트: 2026-04-15 00:19 (KST) +- `AgentCommandQueue`를 `Prompt/Notification` 2종에서 `Steering`, `PermissionContinuation`, `Resume`, `UserDecision`까지 포함하는 통합 큐로 확장했습니다. [AgentLoopService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentLoopService.cs)는 이를 `queued_steering`, `queue_permission_continuation`, `queue_resume` 메타 메시지로 주입해 실행 중 추가 입력과 승인 후 재개 문맥을 더 안정적으로 반영합니다. +- 코드 탭 언어 지원 카탈로그를 확장했습니다. [CodeLanguageCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/CodeLanguageCatalog.cs)는 `Go`, `Rust`, `PHP`, `Ruby`, `Kotlin`, `Swift`를 LSP 심화 지원군으로 승격했고, [LspClientService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/LspClientService.cs)는 `gopls`, `rust-analyzer`, `intelephense`, `solargraph`, `kotlin-language-server`, `sourcekit-lsp`를 로컬 설치 서버 기준으로 탐지합니다. +- 내부 설정의 코드 탭 설명을 더 명시적으로 정리했습니다. [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml), [SettingsViewModel.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/ViewModels/SettingsViewModel.cs)는 `빠른 선택 언어`, `지원 언어(LSP)`, `코드 탭 기본 지원`을 나눠 보여주도록 보강했습니다. +- 워크스페이스 컨텍스트 생성기를 강화했습니다. [WorkspaceContextGenerator.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs)는 `Language Snapshot`, `Agent Context`, `Key Manifests` 섹션을 추가하고 `.claude/skills`, `.ax/rules`, `AXMEMORY.md`, 주요 manifest 파일을 함께 요약합니다. +- PPT 품질 보정 가이드도 추가했습니다. [DeckRepairGuideService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/DeckRepairGuideService.cs)는 deck 품질 이슈를 바로 실행 가능한 개선 문장으로 바꾸고, [PptxSkill.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/PptxSkill.cs)는 `Deck repair guide:`를 품질 요약과 함께 반환합니다. +- 테스트: [AgentCommandQueueTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/AgentCommandQueueTests.cs), [CodeLanguageCatalogTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/CodeLanguageCatalogTests.cs), [WorkspaceContextGeneratorTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/WorkspaceContextGeneratorTests.cs), [DeckRepairGuideServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/DeckRepairGuideServiceTests.cs), [PptxSkillConsultingDeckTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/PptxSkillConsultingDeckTests.cs) +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_master_batch\\ -p:IntermediateOutputPath=obj\\verify_master_batch\\` 경고 0 / 오류 0 +- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AgentCommandQueueTests|CodeLanguageCatalogTests|WorkspaceContextGeneratorTests|PptxSkillConsultingDeckTests|DeckRepairGuideServiceTests" -p:OutputPath=bin\\verify_master_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_master_batch_tests\\` 통과 35 diff --git a/src/AxCopilot.Tests/Services/AgentCommandQueueTests.cs b/src/AxCopilot.Tests/Services/AgentCommandQueueTests.cs index 5e97125..b1c9770 100644 --- a/src/AxCopilot.Tests/Services/AgentCommandQueueTests.cs +++ b/src/AxCopilot.Tests/Services/AgentCommandQueueTests.cs @@ -31,4 +31,25 @@ public class AgentCommandQueueTests queue.DrainAll().Should().BeEmpty(); } + + [Fact] + public void DrainAll_ShouldIncludeSteeringPermissionAndResumeKinds() + { + var queue = new AgentCommandQueue(); + queue.EnqueueNotification("later-note"); + queue.EnqueueResume("resume"); + queue.EnqueuePermissionContinuation("continue-tool"); + queue.EnqueueSteering("steer-now"); + + var drained = queue.DrainAll(); + + drained.Select(x => x.Kind).Should().Equal( + AgentCommandKind.Resume, + AgentCommandKind.PermissionContinuation, + AgentCommandKind.Steering, + AgentCommandKind.Notification); + drained[0].RequestInterrupt.Should().BeFalse(); + drained[1].RequestInterrupt.Should().BeTrue(); + drained[2].RequestInterrupt.Should().BeTrue(); + } } diff --git a/src/AxCopilot.Tests/Services/CodeLanguageCatalogTests.cs b/src/AxCopilot.Tests/Services/CodeLanguageCatalogTests.cs index 4677c66..667857f 100644 --- a/src/AxCopilot.Tests/Services/CodeLanguageCatalogTests.cs +++ b/src/AxCopilot.Tests/Services/CodeLanguageCatalogTests.cs @@ -10,7 +10,9 @@ public class CodeLanguageCatalogTests [InlineData(".cs", "csharp")] [InlineData(".py", "python")] [InlineData(".java", "java")] - [InlineData(".go", null)] + [InlineData(".go", "go")] + [InlineData(".rs", "rust")] + [InlineData(".php", "php")] [InlineData(".unknown", null)] public void DetectLspLanguageId_ShouldMatchCatalog(string extension, string? expected) { @@ -33,6 +35,15 @@ public class CodeLanguageCatalogTests CodeLanguageCatalog.BuildStaticSupportDescription().Should().Contain("Go"); CodeLanguageCatalog.BuildStaticSupportDescription().Should().Contain("Rust"); CodeLanguageCatalog.BuildLspSupportDescription().Should().Contain("C# (.NET)"); - CodeLanguageCatalog.BuildLspSupportDescription().Should().NotContain("Go"); + CodeLanguageCatalog.BuildLspSupportDescription().Should().Contain("Go"); + CodeLanguageCatalog.BuildLspSupportDescription().Should().Contain("Swift"); + } + + [Fact] + public void QuickSelectDescription_ShouldRemainFocusedOnUiShortlist() + { + CodeLanguageCatalog.BuildQuickSelectSupportDescription().Should().Contain("C# (.NET)"); + CodeLanguageCatalog.BuildQuickSelectSupportDescription().Should().Contain("JavaScript / Vue"); + CodeLanguageCatalog.BuildQuickSelectSupportDescription().Should().NotContain("Go"); } } diff --git a/src/AxCopilot.Tests/Services/DeckRepairGuideServiceTests.cs b/src/AxCopilot.Tests/Services/DeckRepairGuideServiceTests.cs new file mode 100644 index 0000000..ea3cf6d --- /dev/null +++ b/src/AxCopilot.Tests/Services/DeckRepairGuideServiceTests.cs @@ -0,0 +1,38 @@ +using AxCopilot.Services.Agent; +using FluentAssertions; +using Xunit; + +namespace AxCopilot.Tests.Services; + +public class DeckRepairGuideServiceTests +{ + [Fact] + public void BuildGuide_ShouldReturnNone_WhenNoIssues() + { + var report = new DeckQualityReport(92, ["Includes Recommendation"], [], []); + + var guide = DeckRepairGuideService.BuildGuide(report); + + guide.Should().Be("Deck repair guide: none"); + } + + [Fact] + public void BuildGuide_ShouldTranslateDeckIssuesIntoActionableGuidance() + { + var report = new DeckQualityReport( + 61, + [], + [ + new DeckReviewIssue("Executive Summary slide is missing.", DeckReviewSeverity.Critical), + new DeckReviewIssue("Slide 3: content density is high and should be simplified.", DeckReviewSeverity.Warning), + new DeckReviewIssue("Evidence slides such as charts, tables, or comparisons are limited.", DeckReviewSeverity.Warning), + ], + []); + + var guide = DeckRepairGuideService.BuildGuide(report); + + guide.Should().Contain("Executive Summary"); + guide.Should().Contain("Reduce text density"); + guide.Should().Contain("evidence"); + } +} diff --git a/src/AxCopilot.Tests/Services/PptxSkillConsultingDeckTests.cs b/src/AxCopilot.Tests/Services/PptxSkillConsultingDeckTests.cs index 20017ba..c36c096 100644 --- a/src/AxCopilot.Tests/Services/PptxSkillConsultingDeckTests.cs +++ b/src/AxCopilot.Tests/Services/PptxSkillConsultingDeckTests.cs @@ -102,6 +102,7 @@ public class PptxSkillConsultingDeckTests result.Success.Should().BeTrue(); File.Exists(Path.Combine(workDir, "consulting-deck.pptx")).Should().BeTrue(); result.Output.Should().Contain("PPTX"); + result.Output.Should().Contain("Deck repair guide:"); } finally { diff --git a/src/AxCopilot.Tests/Services/WorkspaceContextGeneratorTests.cs b/src/AxCopilot.Tests/Services/WorkspaceContextGeneratorTests.cs index d0846b9..4af4fc5 100644 --- a/src/AxCopilot.Tests/Services/WorkspaceContextGeneratorTests.cs +++ b/src/AxCopilot.Tests/Services/WorkspaceContextGeneratorTests.cs @@ -182,6 +182,30 @@ public class WorkspaceContextGeneratorTests var result = await WorkspaceContextGenerator.GenerateAsync(tempDir); result.Should().Contain("Node.js"); + result.Should().Contain("## Key Manifests"); + result.Should().Contain("Node package"); + } + finally + { + Directory.Delete(tempDir, recursive: true); + } + } + + [Fact] + public async Task GenerateAsync_IncludesLanguageSnapshot() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(tempDir); + try + { + File.WriteAllText(Path.Combine(tempDir, "main.go"), "package main"); + File.WriteAllText(Path.Combine(tempDir, "worker.go"), "package main"); + File.WriteAllText(Path.Combine(tempDir, "helper.py"), "print('hi')"); + + var result = await WorkspaceContextGenerator.GenerateAsync(tempDir); + + result.Should().Contain("## Language Snapshot"); + result.Should().Contain("Go:"); } finally { @@ -217,10 +241,15 @@ public class WorkspaceContextGeneratorTests try { File.WriteAllText(Path.Combine(tempDir, "AGENTS.md"), "Agent rules here"); + var skillsDir = Path.Combine(tempDir, ".claude", "skills", "deck"); + Directory.CreateDirectory(skillsDir); + File.WriteAllText(Path.Combine(skillsDir, "SKILL.md"), "# skill"); var result = await WorkspaceContextGenerator.GenerateAsync(tempDir); result.Should().Contain("## Existing Context Files"); result.Should().Contain("AGENTS.md"); + result.Should().Contain("## Agent Context"); + result.Should().Contain(".claude/skills"); } finally { diff --git a/src/AxCopilot/Services/Agent/AgentCommandQueue.cs b/src/AxCopilot/Services/Agent/AgentCommandQueue.cs index 61c1e82..dfc6bb2 100644 --- a/src/AxCopilot/Services/Agent/AgentCommandQueue.cs +++ b/src/AxCopilot/Services/Agent/AgentCommandQueue.cs @@ -17,6 +17,10 @@ public enum AgentCommandKind { Prompt, Notification, + Steering, + PermissionContinuation, + Resume, + UserDecision, } public sealed record AgentQueuedCommand( @@ -45,6 +49,18 @@ public sealed class AgentCommandQueue public void EnqueueNotification(string content, string priority = "later") => Enqueue(AgentCommandKind.Notification, content, priority, requestInterrupt: false); + public void EnqueueSteering(string content, string priority = "now", bool requestInterrupt = true) + => Enqueue(AgentCommandKind.Steering, content, priority, requestInterrupt); + + public void EnqueuePermissionContinuation(string content, string priority = "now") + => Enqueue(AgentCommandKind.PermissionContinuation, content, priority, requestInterrupt: true); + + public void EnqueueResume(string content, string priority = "now") + => Enqueue(AgentCommandKind.Resume, content, priority, requestInterrupt: false); + + public void EnqueueUserDecision(string content, string priority = "next", bool requestInterrupt = false) + => Enqueue(AgentCommandKind.UserDecision, content, priority, requestInterrupt); + public void Clear() { while (_now.TryDequeue(out _)) { } diff --git a/src/AxCopilot/Services/Agent/AgentLoopService.cs b/src/AxCopilot/Services/Agent/AgentLoopService.cs index 89ea9fc..22e38ba 100644 --- a/src/AxCopilot/Services/Agent/AgentLoopService.cs +++ b/src/AxCopilot/Services/Agent/AgentLoopService.cs @@ -49,20 +49,60 @@ public partial class AgentLoopService requestInterrupt: IsRunning); } + /// 실행 중 사용자 지시 보강 메시지를 우선순위 높게 주입합니다. + public void InjectSteeringMessage(string message, bool requestInterrupt = true) + { + if (!string.IsNullOrWhiteSpace(message)) + _pendingCommands.EnqueueSteering( + message, + IsRunning ? "now" : "next", + requestInterrupt: IsRunning && requestInterrupt); + } + + /// 권한 승인 후 이어서 진행할 내용을 큐에 넣습니다. + public void EnqueuePermissionContinuation(string toolName, string? target, string decisionSummary) + { + if (string.IsNullOrWhiteSpace(decisionSummary)) + return; + + var targetLabel = string.IsNullOrWhiteSpace(target) ? "" : $" ({target})"; + _pendingCommands.EnqueuePermissionContinuation( + $"[permission continuation] Continue {toolName}{targetLabel}: {decisionSummary}"); + } + + /// 도구 실행 중 참고용 시스템 알림을 큐에 넣습니다. + public void EnqueueNotification(string message, string priority = "later") + { + if (!string.IsNullOrWhiteSpace(message)) + _pendingCommands.EnqueueNotification(message, priority); + } + + /// 사용자 의사결정 결과를 다음 턴 입력으로 주입합니다. + public void EnqueueUserDecision(string message, bool requestInterrupt = false) + { + if (!string.IsNullOrWhiteSpace(message)) + _pendingCommands.EnqueueUserDecision( + message, + IsRunning ? "next" : "now", + requestInterrupt && IsRunning); + } + private void DrainPendingCommands(List messages) { var drained = _pendingCommands.DrainAll(); if (drained.Count == 0) return; - var interruptingPrompts = drained.Count(x => x.Kind == AgentCommandKind.Prompt && x.RequestInterrupt); - if (interruptingPrompts > 0) + var interruptingCommands = drained.Count(x => + x.RequestInterrupt && + x.Kind is AgentCommandKind.Prompt or AgentCommandKind.Steering or AgentCommandKind.UserDecision or AgentCommandKind.PermissionContinuation); + if (interruptingCommands > 0) { messages.Add(new ChatMessage { Role = "system", MetaKind = "queued_input_interrupt", - Content = $"[queued input] {interruptingPrompts} new prompt(s) arrived during execution. Prioritize the newest user direction before continuing.", + Content = $"[queued input] {interruptingCommands} new instruction(s) arrived during execution. Prioritize the newest user direction before continuing.", Timestamp = DateTime.Now, }); } @@ -82,6 +122,40 @@ public partial class AgentLoopService EmitEvent(AgentEventType.Thinking, "", item.Content); break; + case AgentCommandKind.PermissionContinuation: + messages.Add(new ChatMessage + { + Role = "system", + MetaKind = "queue_permission_continuation", + Content = item.Content, + Timestamp = item.CreatedAt, + }); + EmitEvent(AgentEventType.Thinking, "", item.Content); + break; + + case AgentCommandKind.Resume: + messages.Add(new ChatMessage + { + Role = "system", + MetaKind = "queue_resume", + Content = item.Content, + Timestamp = item.CreatedAt, + }); + EmitEvent(AgentEventType.Thinking, "", item.Content); + break; + + case AgentCommandKind.Steering: + case AgentCommandKind.UserDecision: + messages.Add(new ChatMessage + { + Role = "user", + MetaKind = item.Kind == AgentCommandKind.Steering ? "queued_steering" : "queued_user_decision", + Content = item.Content, + Timestamp = item.CreatedAt, + }); + EmitEvent(AgentEventType.UserMessage, "", item.Content); + break; + default: messages.Add(new ChatMessage { @@ -209,6 +283,8 @@ public partial class AgentLoopService { // 이미 릴리즈된 상태 — 무시 } + if (IsRunning) + _pendingCommands.EnqueueResume("Execution resumed after pause. Re-evaluate the latest queued context before proceeding."); EmitEvent(AgentEventType.Resumed, "", "에이전트가 재개되었습니다"); } diff --git a/src/AxCopilot/Services/Agent/DeckRepairGuideService.cs b/src/AxCopilot/Services/Agent/DeckRepairGuideService.cs new file mode 100644 index 0000000..b26e571 --- /dev/null +++ b/src/AxCopilot/Services/Agent/DeckRepairGuideService.cs @@ -0,0 +1,55 @@ +namespace AxCopilot.Services.Agent; + +public static class DeckRepairGuideService +{ + public static string BuildGuide(DeckQualityReport review) + { + if (review.Issues.Count == 0) + return "Deck repair guide: none"; + + var actions = new List(); + foreach (var issue in review.Issues) + { + var action = issue.Message switch + { + var message when message.Contains("Executive Summary", StringComparison.OrdinalIgnoreCase) + => "Add or strengthen the Executive Summary with 2-3 evidence-backed takeaways", + var message when message.Contains("Recommendation", StringComparison.OrdinalIgnoreCase) + => "Add a clear recommendation or decision request slide near the end of the deck", + var message when message.Contains("roadmap", StringComparison.OrdinalIgnoreCase) + => "Add a roadmap slide with phases, owners, and timing", + var message when message.Contains("headline is too long", StringComparison.OrdinalIgnoreCase) + => "Tighten slide headlines to one clear message sentence", + var message when message.Contains("content density is high", StringComparison.OrdinalIgnoreCase) + => "Reduce text density and convert bullets into cards, visuals, or sharper evidence points", + var message when message.Contains("comparison slide", StringComparison.OrdinalIgnoreCase) + => "Expand the comparison slide to show at least two real options and a verdict", + var message when message.Contains("chart slide", StringComparison.OrdinalIgnoreCase) + => "Provide chart labels and values or replace the slide with a comparison/evidence layout", + var message when message.Contains("table slide", StringComparison.OrdinalIgnoreCase) + => "Provide complete table headers and rows or simplify the slide to key callouts", + var message when message.Contains("text-heavy", StringComparison.OrdinalIgnoreCase) + => "Convert text-heavy slides into message-led visuals with fewer bullets", + var message when message.Contains("Evidence slides", StringComparison.OrdinalIgnoreCase) + => "Add evidence slides such as charts, tables, appendix evidence, or structured comparisons", + var message when message.Contains("placeholder", StringComparison.OrdinalIgnoreCase) + => "Replace placeholder text before export and verify each slide has final copy", + _ => null, + }; + + if (!string.IsNullOrWhiteSpace(action) && + !actions.Contains(action, StringComparer.OrdinalIgnoreCase)) + { + actions.Add(action); + } + + if (actions.Count >= 3) + break; + } + + if (actions.Count == 0) + return "Deck repair guide: review slide alerts and reinforce storyline, evidence, and decision ask"; + + return "Deck repair guide: " + string.Join(" | ", actions); + } +} diff --git a/src/AxCopilot/Services/Agent/PptxSkill.cs b/src/AxCopilot/Services/Agent/PptxSkill.cs index 1501324..bac9c7d 100644 --- a/src/AxCopilot/Services/Agent/PptxSkill.cs +++ b/src/AxCopilot/Services/Agent/PptxSkill.cs @@ -1029,7 +1029,10 @@ public class PptxSkill : IAgentTool if (!string.IsNullOrWhiteSpace(templatePackName)) outputParts.Add($"Template pack: {templatePackName}"); if (deckReview != null) + { outputParts.Add(deckReview.ToToolSummary()); + outputParts.Add(DeckRepairGuideService.BuildGuide(deckReview)); + } return ToolResult.Ok(string.Join("\n", outputParts), fullPath); } catch (Exception ex) diff --git a/src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs b/src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs index 5f2d309..9404ea9 100644 --- a/src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs +++ b/src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs @@ -84,6 +84,15 @@ internal static class WorkspaceContextGenerator sb.AppendLine(); } + var languageSnapshot = BuildLanguageSnapshot(extDist); + if (languageSnapshot.Count > 0) + { + sb.AppendLine("## Language Snapshot"); + foreach (var line in languageSnapshot) + sb.AppendLine($"- {line}"); + sb.AppendLine(); + } + // 4. 기존 컨텍스트 파일 감지 var contextFiles = DetectContextFiles(workFolder); if (contextFiles.Count > 0) @@ -95,6 +104,24 @@ internal static class WorkspaceContextGenerator } // 5. README 요약 + var agentContextSummary = DetectAgentContextSummary(workFolder); + if (agentContextSummary.Count > 0) + { + sb.AppendLine("## Agent Context"); + foreach (var line in agentContextSummary) + sb.AppendLine($"- {line}"); + sb.AppendLine(); + } + + var keyManifests = DetectKeyManifests(workFolder); + if (keyManifests.Count > 0) + { + sb.AppendLine("## Key Manifests"); + foreach (var line in keyManifests) + sb.AppendLine($"- {line}"); + sb.AppendLine(); + } + var readmeSummary = ExtractReadmeSummary(workFolder); if (readmeSummary != null) { @@ -328,6 +355,91 @@ internal static class WorkspaceContextGenerator return files; } + private static List DetectAgentContextSummary(string folder) + { + var lines = new List(); + + try + { + var claudeSkillsDir = Path.Combine(folder, ".claude", "skills"); + if (Directory.Exists(claudeSkillsDir)) + { + var skillFiles = Directory.GetFiles(claudeSkillsDir, "SKILL.md", SearchOption.AllDirectories); + if (skillFiles.Length > 0) + lines.Add($".claude/skills 호환 스킬 {skillFiles.Length}개 감지"); + } + } + catch { } + + try + { + var axRulesDir = Path.Combine(folder, ".ax", "rules"); + if (Directory.Exists(axRulesDir)) + { + var ruleFiles = Directory.GetFiles(axRulesDir, "*.md", SearchOption.TopDirectoryOnly); + if (ruleFiles.Length > 0) + lines.Add($".ax/rules 규칙 {ruleFiles.Length}개 감지"); + } + } + catch { } + + try + { + var memoryFile = Path.Combine(folder, "AXMEMORY.md"); + if (File.Exists(memoryFile)) + lines.Add("AXMEMORY.md 메모리 파일 감지"); + } + catch { } + + return lines; + } + + private static List DetectKeyManifests(string folder) + { + var manifests = new List(); + var patterns = new (string Pattern, string Label)[] + { + ("*.sln", "Solution"), + ("*.csproj", ".NET project"), + ("package.json", "Node package"), + ("pyproject.toml", "Python project"), + ("requirements.txt", "Python requirements"), + ("Cargo.toml", "Rust package"), + ("go.mod", "Go module"), + ("pom.xml", "Maven project"), + ("build.gradle", "Gradle build"), + }; + + foreach (var (pattern, label) in patterns) + { + try + { + var matches = Directory.GetFiles(folder, pattern, SearchOption.TopDirectoryOnly) + .Select(Path.GetFileName) + .Where(name => !string.IsNullOrWhiteSpace(name)) + .Cast() + .Take(3) + .ToList(); + if (matches.Count > 0) + manifests.Add($"{label}: {string.Join(", ", matches)}"); + } + catch { } + } + + return manifests; + } + + private static List BuildLanguageSnapshot(List> extDist) + => extDist + .Where(kv => kv.Value > 0) + .Select(kv => new { Language = GetLanguageName(kv.Key), kv.Value }) + .GroupBy(x => x.Language, StringComparer.OrdinalIgnoreCase) + .Select(group => new { Language = group.Key, Count = group.Sum(item => item.Value) }) + .OrderByDescending(x => x.Count) + .Take(6) + .Select(x => $"{x.Language}: {x.Count} file(s)") + .ToList(); + private static async Task<(string? Branch, string? Remote)> GetGitInfoAsync( string folder, CancellationToken ct) { diff --git a/src/AxCopilot/Services/CodeLanguageCatalog.cs b/src/AxCopilot/Services/CodeLanguageCatalog.cs index 968851b..d24ac8a 100644 --- a/src/AxCopilot/Services/CodeLanguageCatalog.cs +++ b/src/AxCopilot/Services/CodeLanguageCatalog.cs @@ -119,7 +119,8 @@ public static class CodeLanguageCatalog [ "Preserve package boundaries, error-first flow, and gofmt-style formatting.", "Check interfaces, exported identifiers, and concurrency-sensitive changes together." - ]), + ], + LspLanguageId: "go"), new( "rust", "Rust", @@ -127,7 +128,8 @@ public static class CodeLanguageCatalog [ "Respect Cargo workspace structure, ownership/borrowing rules, and crate boundaries.", "Prefer explicit enums/results and verify compiler diagnostics after edits." - ]), + ], + LspLanguageId: "rust"), new( "php", "PHP", @@ -135,7 +137,8 @@ public static class CodeLanguageCatalog [ "Follow the framework and autoloading structure already present in the project.", "Be careful with runtime includes, container wiring, and mixed template/application files." - ]), + ], + LspLanguageId: "php"), new( "ruby", "Ruby", @@ -143,7 +146,8 @@ public static class CodeLanguageCatalog [ "Preserve gem structure, Rails or plain Ruby conventions already used in the repository.", "Check dynamic dispatch, concerns/modules, and tests together after edits." - ]), + ], + LspLanguageId: "ruby"), new( "kotlin", "Kotlin", @@ -151,7 +155,8 @@ public static class CodeLanguageCatalog [ "Preserve Gradle structure, package layout, and nullability intent.", "Be careful with JVM interop boundaries and Android-specific module structure when present." - ]), + ], + LspLanguageId: "kotlin"), new( "swift", "Swift", @@ -159,7 +164,8 @@ public static class CodeLanguageCatalog [ "Preserve target structure, Apple framework imports, and protocol-oriented design already in use.", "Check app lifecycle and platform-specific behavior after edits." - ]), + ], + LspLanguageId: "swift"), new( "scala", "Scala", @@ -255,6 +261,9 @@ public static class CodeLanguageCatalog public static string GetQuickSelectLabel(string? key) => FindByKey(key)?.DisplayName ?? key ?? "Auto"; + public static string BuildQuickSelectSupportDescription() + => string.Join(", ", QuickSelectLanguages.Select(x => x.DisplayName)); + public static string BuildSelectedLanguagePrompt(string? key) { if (string.IsNullOrWhiteSpace(key) || string.Equals(key, "auto", StringComparison.OrdinalIgnoreCase)) @@ -292,6 +301,9 @@ public static class CodeLanguageCatalog public static string BuildStaticSupportDescription() => string.Join(", ", s_all.Select(x => x.DisplayName)); + public static string BuildSupportSummaryDescription() + => $"빠른 선택: {BuildQuickSelectSupportDescription()} | 내장 분석: {BuildStaticSupportDescription()} | LSP 심화 분석: {BuildLspSupportDescription()}"; + public static string BuildCodeTabSupportDescription() { var sb = new StringBuilder(); diff --git a/src/AxCopilot/Services/LspClientService.cs b/src/AxCopilot/Services/LspClientService.cs index a7d792f..b3cd3d7 100644 --- a/src/AxCopilot/Services/LspClientService.cs +++ b/src/AxCopilot/Services/LspClientService.cs @@ -9,7 +9,8 @@ namespace AxCopilot.Services; /// /// Language Server Protocol 클라이언트. /// 외부 언어 서버 프로세스와 JSON-RPC 2.0으로 통신합니다. -/// 지원: OmniSharp (C#), typescript-language-server, pyright, clangd +/// 지원: OmniSharp (C#), typescript-language-server, pyright/pylsp, clangd, jdtls, +/// gopls, rust-analyzer, intelephense, solargraph, kotlin-language-server, sourcekit-lsp /// public class LspClientService : IDisposable { @@ -637,6 +638,18 @@ public class LspClientService : IDisposable FindCommand("C/C++", new[] { "clangd" }, Array.Empty()), "java" => FindCommand("Java", new[] { "jdtls" }, Array.Empty()), + "go" => + FindCommand("Go", new[] { "gopls" }, Array.Empty()), + "rust" => + FindCommand("Rust", new[] { "rust-analyzer" }, Array.Empty()), + "php" => + FindCommand("PHP", new[] { "intelephense" }, new[] { "--stdio" }), + "ruby" => + FindCommand("Ruby", new[] { "solargraph" }, new[] { "stdio" }), + "kotlin" => + FindCommand("Kotlin", new[] { "kotlin-language-server" }, Array.Empty()), + "swift" => + FindCommand("Swift", new[] { "sourcekit-lsp" }, Array.Empty()), _ => (null, Array.Empty()) }; diff --git a/src/AxCopilot/ViewModels/SettingsViewModel.cs b/src/AxCopilot/ViewModels/SettingsViewModel.cs index 9e0658f..29f5b41 100644 --- a/src/AxCopilot/ViewModels/SettingsViewModel.cs +++ b/src/AxCopilot/ViewModels/SettingsViewModel.cs @@ -138,9 +138,10 @@ public class SettingsViewModel : INotifyPropertyChanged /// CodeSettings 바인딩용 프로퍼티. XAML에서 {Binding Code.EnableLsp} 등으로 접근. public Models.CodeSettings Code => _service.Settings.Llm.Code; + public string CodeQuickSelectLanguagesText => CodeLanguageCatalog.BuildQuickSelectSupportDescription(); public string CodeLspSupportedLanguagesText => CodeLanguageCatalog.BuildLspSupportDescription(); public string CodeStaticSupportedLanguagesText => CodeLanguageCatalog.BuildStaticSupportDescription(); - public string CodeTabSupportSummaryText => CodeLanguageCatalog.BuildCodeTabSupportDescription(); + public string CodeTabSupportSummaryText => CodeLanguageCatalog.BuildSupportSummaryDescription(); // ─── 작업 복사본 ─────────────────────────────────────────────────────── private string _hotkey; diff --git a/src/AxCopilot/Views/SettingsWindow.xaml b/src/AxCopilot/Views/SettingsWindow.xaml index df49bcf..e015821 100644 --- a/src/AxCopilot/Views/SettingsWindow.xaml +++ b/src/AxCopilot/Views/SettingsWindow.xaml @@ -5448,7 +5448,7 @@ - + @@ -5466,6 +5466,7 @@ +