???? ???? ?? ????????? ????? ?? ????? PPT ?? ???? ??

??:
- ?? ?? ???? ?? ?? ??, ?? ? ?? ?? ??, ?????? ???? ??? ? ?? ?????.
- ?? ?? ??? ??? ?? PPT? ?? ??? ?? ???? ?? ????? ????.

?? ????:
- AgentCommandQueue? steering, permission continuation, resume, user decision ? ??? ???? AgentLoopService?? ?? ???? ????? ??
- CodeLanguageCatalog? LspClientService? ??? Go, Rust, PHP, Ruby, Kotlin, Swift? ?? LSP ?? ???? ??
- SettingsWindow? SettingsViewModel?? ?? ? ?? ??? ?? ?? / LSP / ?? ???? ????? ??
- WorkspaceContextGenerator? Language Snapshot, Agent Context, Key Manifests ??? ???? .claude/skills, .ax/rules, AXMEMORY.md ??? ??
- DeckRepairGuideService? ???? PptxSkill ??? 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\\
- 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\\
This commit is contained in:
2026-04-15 00:21:15 +09:00
parent 59ec4a1371
commit f33ee7f7db
16 changed files with 422 additions and 14 deletions

View File

@@ -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를 회귀 검증했습니다. - 테스트로 [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 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 - 검증: `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

View File

@@ -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를 함께 회귀 검증했습니다. - 테스트로 [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 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 - 검증: `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

View File

@@ -31,4 +31,25 @@ public class AgentCommandQueueTests
queue.DrainAll().Should().BeEmpty(); 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();
}
} }

View File

@@ -10,7 +10,9 @@ public class CodeLanguageCatalogTests
[InlineData(".cs", "csharp")] [InlineData(".cs", "csharp")]
[InlineData(".py", "python")] [InlineData(".py", "python")]
[InlineData(".java", "java")] [InlineData(".java", "java")]
[InlineData(".go", null)] [InlineData(".go", "go")]
[InlineData(".rs", "rust")]
[InlineData(".php", "php")]
[InlineData(".unknown", null)] [InlineData(".unknown", null)]
public void DetectLspLanguageId_ShouldMatchCatalog(string extension, string? expected) public void DetectLspLanguageId_ShouldMatchCatalog(string extension, string? expected)
{ {
@@ -33,6 +35,15 @@ public class CodeLanguageCatalogTests
CodeLanguageCatalog.BuildStaticSupportDescription().Should().Contain("Go"); CodeLanguageCatalog.BuildStaticSupportDescription().Should().Contain("Go");
CodeLanguageCatalog.BuildStaticSupportDescription().Should().Contain("Rust"); CodeLanguageCatalog.BuildStaticSupportDescription().Should().Contain("Rust");
CodeLanguageCatalog.BuildLspSupportDescription().Should().Contain("C# (.NET)"); 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");
} }
} }

View File

@@ -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");
}
}

View File

@@ -102,6 +102,7 @@ public class PptxSkillConsultingDeckTests
result.Success.Should().BeTrue(); result.Success.Should().BeTrue();
File.Exists(Path.Combine(workDir, "consulting-deck.pptx")).Should().BeTrue(); File.Exists(Path.Combine(workDir, "consulting-deck.pptx")).Should().BeTrue();
result.Output.Should().Contain("PPTX"); result.Output.Should().Contain("PPTX");
result.Output.Should().Contain("Deck repair guide:");
} }
finally finally
{ {

View File

@@ -182,6 +182,30 @@ public class WorkspaceContextGeneratorTests
var result = await WorkspaceContextGenerator.GenerateAsync(tempDir); var result = await WorkspaceContextGenerator.GenerateAsync(tempDir);
result.Should().Contain("Node.js"); 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 finally
{ {
@@ -217,10 +241,15 @@ public class WorkspaceContextGeneratorTests
try try
{ {
File.WriteAllText(Path.Combine(tempDir, "AGENTS.md"), "Agent rules here"); 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); var result = await WorkspaceContextGenerator.GenerateAsync(tempDir);
result.Should().Contain("## Existing Context Files"); result.Should().Contain("## Existing Context Files");
result.Should().Contain("AGENTS.md"); result.Should().Contain("AGENTS.md");
result.Should().Contain("## Agent Context");
result.Should().Contain(".claude/skills");
} }
finally finally
{ {

View File

@@ -17,6 +17,10 @@ public enum AgentCommandKind
{ {
Prompt, Prompt,
Notification, Notification,
Steering,
PermissionContinuation,
Resume,
UserDecision,
} }
public sealed record AgentQueuedCommand( public sealed record AgentQueuedCommand(
@@ -45,6 +49,18 @@ public sealed class AgentCommandQueue
public void EnqueueNotification(string content, string priority = "later") public void EnqueueNotification(string content, string priority = "later")
=> Enqueue(AgentCommandKind.Notification, content, priority, requestInterrupt: false); => 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() public void Clear()
{ {
while (_now.TryDequeue(out _)) { } while (_now.TryDequeue(out _)) { }

View File

@@ -49,20 +49,60 @@ public partial class AgentLoopService
requestInterrupt: IsRunning); requestInterrupt: IsRunning);
} }
/// <summary>실행 중 사용자 지시 보강 메시지를 우선순위 높게 주입합니다.</summary>
public void InjectSteeringMessage(string message, bool requestInterrupt = true)
{
if (!string.IsNullOrWhiteSpace(message))
_pendingCommands.EnqueueSteering(
message,
IsRunning ? "now" : "next",
requestInterrupt: IsRunning && requestInterrupt);
}
/// <summary>권한 승인 후 이어서 진행할 내용을 큐에 넣습니다.</summary>
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}");
}
/// <summary>도구 실행 중 참고용 시스템 알림을 큐에 넣습니다.</summary>
public void EnqueueNotification(string message, string priority = "later")
{
if (!string.IsNullOrWhiteSpace(message))
_pendingCommands.EnqueueNotification(message, priority);
}
/// <summary>사용자 의사결정 결과를 다음 턴 입력으로 주입합니다.</summary>
public void EnqueueUserDecision(string message, bool requestInterrupt = false)
{
if (!string.IsNullOrWhiteSpace(message))
_pendingCommands.EnqueueUserDecision(
message,
IsRunning ? "next" : "now",
requestInterrupt && IsRunning);
}
private void DrainPendingCommands(List<ChatMessage> messages) private void DrainPendingCommands(List<ChatMessage> messages)
{ {
var drained = _pendingCommands.DrainAll(); var drained = _pendingCommands.DrainAll();
if (drained.Count == 0) if (drained.Count == 0)
return; return;
var interruptingPrompts = drained.Count(x => x.Kind == AgentCommandKind.Prompt && x.RequestInterrupt); var interruptingCommands = drained.Count(x =>
if (interruptingPrompts > 0) x.RequestInterrupt &&
x.Kind is AgentCommandKind.Prompt or AgentCommandKind.Steering or AgentCommandKind.UserDecision or AgentCommandKind.PermissionContinuation);
if (interruptingCommands > 0)
{ {
messages.Add(new ChatMessage messages.Add(new ChatMessage
{ {
Role = "system", Role = "system",
MetaKind = "queued_input_interrupt", 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, Timestamp = DateTime.Now,
}); });
} }
@@ -82,6 +122,40 @@ public partial class AgentLoopService
EmitEvent(AgentEventType.Thinking, "", item.Content); EmitEvent(AgentEventType.Thinking, "", item.Content);
break; 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: default:
messages.Add(new ChatMessage 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, "", "에이전트가 재개되었습니다"); EmitEvent(AgentEventType.Resumed, "", "에이전트가 재개되었습니다");
} }

View File

@@ -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<string>();
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);
}
}

View File

@@ -1029,7 +1029,10 @@ public class PptxSkill : IAgentTool
if (!string.IsNullOrWhiteSpace(templatePackName)) if (!string.IsNullOrWhiteSpace(templatePackName))
outputParts.Add($"Template pack: {templatePackName}"); outputParts.Add($"Template pack: {templatePackName}");
if (deckReview != null) if (deckReview != null)
{
outputParts.Add(deckReview.ToToolSummary()); outputParts.Add(deckReview.ToToolSummary());
outputParts.Add(DeckRepairGuideService.BuildGuide(deckReview));
}
return ToolResult.Ok(string.Join("\n", outputParts), fullPath); return ToolResult.Ok(string.Join("\n", outputParts), fullPath);
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -84,6 +84,15 @@ internal static class WorkspaceContextGenerator
sb.AppendLine(); 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. 기존 컨텍스트 파일 감지 // 4. 기존 컨텍스트 파일 감지
var contextFiles = DetectContextFiles(workFolder); var contextFiles = DetectContextFiles(workFolder);
if (contextFiles.Count > 0) if (contextFiles.Count > 0)
@@ -95,6 +104,24 @@ internal static class WorkspaceContextGenerator
} }
// 5. README 요약 // 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); var readmeSummary = ExtractReadmeSummary(workFolder);
if (readmeSummary != null) if (readmeSummary != null)
{ {
@@ -328,6 +355,91 @@ internal static class WorkspaceContextGenerator
return files; return files;
} }
private static List<string> DetectAgentContextSummary(string folder)
{
var lines = new List<string>();
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<string> DetectKeyManifests(string folder)
{
var manifests = new List<string>();
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<string>()
.Take(3)
.ToList();
if (matches.Count > 0)
manifests.Add($"{label}: {string.Join(", ", matches)}");
}
catch { }
}
return manifests;
}
private static List<string> BuildLanguageSnapshot(List<KeyValuePair<string, int>> 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( private static async Task<(string? Branch, string? Remote)> GetGitInfoAsync(
string folder, CancellationToken ct) string folder, CancellationToken ct)
{ {

View File

@@ -119,7 +119,8 @@ public static class CodeLanguageCatalog
[ [
"Preserve package boundaries, error-first flow, and gofmt-style formatting.", "Preserve package boundaries, error-first flow, and gofmt-style formatting.",
"Check interfaces, exported identifiers, and concurrency-sensitive changes together." "Check interfaces, exported identifiers, and concurrency-sensitive changes together."
]), ],
LspLanguageId: "go"),
new( new(
"rust", "rust",
"Rust", "Rust",
@@ -127,7 +128,8 @@ public static class CodeLanguageCatalog
[ [
"Respect Cargo workspace structure, ownership/borrowing rules, and crate boundaries.", "Respect Cargo workspace structure, ownership/borrowing rules, and crate boundaries.",
"Prefer explicit enums/results and verify compiler diagnostics after edits." "Prefer explicit enums/results and verify compiler diagnostics after edits."
]), ],
LspLanguageId: "rust"),
new( new(
"php", "php",
"PHP", "PHP",
@@ -135,7 +137,8 @@ public static class CodeLanguageCatalog
[ [
"Follow the framework and autoloading structure already present in the project.", "Follow the framework and autoloading structure already present in the project.",
"Be careful with runtime includes, container wiring, and mixed template/application files." "Be careful with runtime includes, container wiring, and mixed template/application files."
]), ],
LspLanguageId: "php"),
new( new(
"ruby", "ruby",
"Ruby", "Ruby",
@@ -143,7 +146,8 @@ public static class CodeLanguageCatalog
[ [
"Preserve gem structure, Rails or plain Ruby conventions already used in the repository.", "Preserve gem structure, Rails or plain Ruby conventions already used in the repository.",
"Check dynamic dispatch, concerns/modules, and tests together after edits." "Check dynamic dispatch, concerns/modules, and tests together after edits."
]), ],
LspLanguageId: "ruby"),
new( new(
"kotlin", "kotlin",
"Kotlin", "Kotlin",
@@ -151,7 +155,8 @@ public static class CodeLanguageCatalog
[ [
"Preserve Gradle structure, package layout, and nullability intent.", "Preserve Gradle structure, package layout, and nullability intent.",
"Be careful with JVM interop boundaries and Android-specific module structure when present." "Be careful with JVM interop boundaries and Android-specific module structure when present."
]), ],
LspLanguageId: "kotlin"),
new( new(
"swift", "swift",
"Swift", "Swift",
@@ -159,7 +164,8 @@ public static class CodeLanguageCatalog
[ [
"Preserve target structure, Apple framework imports, and protocol-oriented design already in use.", "Preserve target structure, Apple framework imports, and protocol-oriented design already in use.",
"Check app lifecycle and platform-specific behavior after edits." "Check app lifecycle and platform-specific behavior after edits."
]), ],
LspLanguageId: "swift"),
new( new(
"scala", "scala",
"Scala", "Scala",
@@ -255,6 +261,9 @@ public static class CodeLanguageCatalog
public static string GetQuickSelectLabel(string? key) public static string GetQuickSelectLabel(string? key)
=> FindByKey(key)?.DisplayName ?? key ?? "Auto"; => FindByKey(key)?.DisplayName ?? key ?? "Auto";
public static string BuildQuickSelectSupportDescription()
=> string.Join(", ", QuickSelectLanguages.Select(x => x.DisplayName));
public static string BuildSelectedLanguagePrompt(string? key) public static string BuildSelectedLanguagePrompt(string? key)
{ {
if (string.IsNullOrWhiteSpace(key) || string.Equals(key, "auto", StringComparison.OrdinalIgnoreCase)) if (string.IsNullOrWhiteSpace(key) || string.Equals(key, "auto", StringComparison.OrdinalIgnoreCase))
@@ -292,6 +301,9 @@ public static class CodeLanguageCatalog
public static string BuildStaticSupportDescription() public static string BuildStaticSupportDescription()
=> string.Join(", ", s_all.Select(x => x.DisplayName)); => string.Join(", ", s_all.Select(x => x.DisplayName));
public static string BuildSupportSummaryDescription()
=> $"빠른 선택: {BuildQuickSelectSupportDescription()} | 내장 분석: {BuildStaticSupportDescription()} | LSP 심화 분석: {BuildLspSupportDescription()}";
public static string BuildCodeTabSupportDescription() public static string BuildCodeTabSupportDescription()
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();

View File

@@ -9,7 +9,8 @@ namespace AxCopilot.Services;
/// <summary> /// <summary>
/// Language Server Protocol 클라이언트. /// Language Server Protocol 클라이언트.
/// 외부 언어 서버 프로세스와 JSON-RPC 2.0으로 통신합니다. /// 외부 언어 서버 프로세스와 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
/// </summary> /// </summary>
public class LspClientService : IDisposable public class LspClientService : IDisposable
{ {
@@ -637,6 +638,18 @@ public class LspClientService : IDisposable
FindCommand("C/C++", new[] { "clangd" }, Array.Empty<string>()), FindCommand("C/C++", new[] { "clangd" }, Array.Empty<string>()),
"java" => "java" =>
FindCommand("Java", new[] { "jdtls" }, Array.Empty<string>()), FindCommand("Java", new[] { "jdtls" }, Array.Empty<string>()),
"go" =>
FindCommand("Go", new[] { "gopls" }, Array.Empty<string>()),
"rust" =>
FindCommand("Rust", new[] { "rust-analyzer" }, Array.Empty<string>()),
"php" =>
FindCommand("PHP", new[] { "intelephense" }, new[] { "--stdio" }),
"ruby" =>
FindCommand("Ruby", new[] { "solargraph" }, new[] { "stdio" }),
"kotlin" =>
FindCommand("Kotlin", new[] { "kotlin-language-server" }, Array.Empty<string>()),
"swift" =>
FindCommand("Swift", new[] { "sourcekit-lsp" }, Array.Empty<string>()),
_ => (null, Array.Empty<string>()) _ => (null, Array.Empty<string>())
}; };

View File

@@ -138,9 +138,10 @@ public class SettingsViewModel : INotifyPropertyChanged
/// <summary>CodeSettings 바인딩용 프로퍼티. XAML에서 {Binding Code.EnableLsp} 등으로 접근.</summary> /// <summary>CodeSettings 바인딩용 프로퍼티. XAML에서 {Binding Code.EnableLsp} 등으로 접근.</summary>
public Models.CodeSettings Code => _service.Settings.Llm.Code; public Models.CodeSettings Code => _service.Settings.Llm.Code;
public string CodeQuickSelectLanguagesText => CodeLanguageCatalog.BuildQuickSelectSupportDescription();
public string CodeLspSupportedLanguagesText => CodeLanguageCatalog.BuildLspSupportDescription(); public string CodeLspSupportedLanguagesText => CodeLanguageCatalog.BuildLspSupportDescription();
public string CodeStaticSupportedLanguagesText => CodeLanguageCatalog.BuildStaticSupportDescription(); public string CodeStaticSupportedLanguagesText => CodeLanguageCatalog.BuildStaticSupportDescription();
public string CodeTabSupportSummaryText => CodeLanguageCatalog.BuildCodeTabSupportDescription(); public string CodeTabSupportSummaryText => CodeLanguageCatalog.BuildSupportSummaryDescription();
// ─── 작업 복사본 ─────────────────────────────────────────────────────── // ─── 작업 복사본 ───────────────────────────────────────────────────────
private string _hotkey; private string _hotkey;

View File

@@ -5466,6 +5466,7 @@
</Border> </Border>
</StackPanel> </StackPanel>
<TextBlock Style="{StaticResource RowHint}" Text="언어 서버로 정의 이동, 참조 검색 등 코드 분석을 지원합니다"/> <TextBlock Style="{StaticResource RowHint}" Text="언어 서버로 정의 이동, 참조 검색 등 코드 분석을 지원합니다"/>
<TextBlock Style="{StaticResource RowHint}" Margin="0,4,0,0" Text="{Binding CodeQuickSelectLanguagesText, StringFormat=빠른 선택 언어: {0}}"/>
<TextBlock Style="{StaticResource RowHint}" Margin="0,4,0,0" Text="{Binding CodeLspSupportedLanguagesText, StringFormat=지원 언어(LSP): {0}}"/> <TextBlock Style="{StaticResource RowHint}" Margin="0,4,0,0" Text="{Binding CodeLspSupportedLanguagesText, StringFormat=지원 언어(LSP): {0}}"/>
<TextBlock Style="{StaticResource RowHint}" Margin="0,2,0,0" Text="{Binding CodeStaticSupportedLanguagesText, StringFormat=코드 탭 기본 지원: {0}}"/> <TextBlock Style="{StaticResource RowHint}" Margin="0,2,0,0" Text="{Binding CodeStaticSupportedLanguagesText, StringFormat=코드 탭 기본 지원: {0}}"/>
<TextBlock Style="{StaticResource RowHint}" Margin="0,2,0,0" Text="격리 환경에서는 내장 분석과 로컬에 이미 설치된 언어 서버만 활용합니다"/> <TextBlock Style="{StaticResource RowHint}" Margin="0,2,0,0" Text="격리 환경에서는 내장 분석과 로컬에 이미 설치된 언어 서버만 활용합니다"/>