???? ?? ?? ?? ??? ?? fallback ???? ?? ??

- CodeLanguageCatalog? UTF-8 ???? ????? ?? fallback ???? ??? ??? manifest/build/test/lint ?? ?? ??? ????
- WorkspaceContextGenerator? ?? ??? ????? ?? Language Workflow ??? ?????? ??? no-LSP ?????? ?? ??? ?? ??? ?? ???
- AgentLoopLlmRequestPreparationService? ??? ?? tool-call ??? pre-call reminder ?? ??? AgentLoopService?? ???
- CodeLanguageCatalogTests, WorkspaceContextGeneratorTests, AgentLoopLlmRequestPreparationServiceTests? ??? ?? fallback/????/LLM ?? ?? ??? ???
- ??: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_final_batch\\ -p:IntermediateOutputPath=obj\\verify_final_batch\\ (?? 0 / ?? 0)
- ??: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "CodeLanguageCatalogTests|WorkspaceContextGeneratorTests|AgentLoopLlmRequestPreparationServiceTests|AgentLoopIterationPreparationServiceTests|AgentMessageInvariantHelperTests|AgentToolResultBudgetTests|ChatStorageServiceTests|HtmlSkillGoldenReportTests|PptxSkillGoldenDeckTests|DocxSkillGoldenDocumentTests|ExcelSkillGoldenWorkbookTests" -p:OutputPath=bin\\verify_final_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_final_batch_tests\\ (?? 54)
This commit is contained in:
2026-04-15 10:51:44 +09:00
parent 91c4dc74c3
commit 48e8c57cf3
11 changed files with 456 additions and 235 deletions

View File

@@ -1976,3 +1976,17 @@ MIT License
- golden 회귀를 확대했습니다. [HtmlSkillGoldenReportTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/HtmlSkillGoldenReportTests.cs)에 strategy brief 시나리오를 추가했고, [PptxSkillGoldenDeckTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/PptxSkillGoldenDeckTests.cs)는 PMO steering deck까지 고정했습니다. [ArtifactQualityReviewServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ArtifactQualityReviewServiceTests.cs), [ArtifactRepairGuideServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ArtifactRepairGuideServiceTests.cs), [DeckQualityReviewServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/DeckQualityReviewServiceTests.cs), [DeckRepairGuideServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/DeckRepairGuideServiceTests.cs)도 함께 확장했습니다. - golden 회귀를 확대했습니다. [HtmlSkillGoldenReportTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/HtmlSkillGoldenReportTests.cs)에 strategy brief 시나리오를 추가했고, [PptxSkillGoldenDeckTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/PptxSkillGoldenDeckTests.cs)는 PMO steering deck까지 고정했습니다. [ArtifactQualityReviewServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ArtifactQualityReviewServiceTests.cs), [ArtifactRepairGuideServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ArtifactRepairGuideServiceTests.cs), [DeckQualityReviewServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/DeckQualityReviewServiceTests.cs), [DeckRepairGuideServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/DeckRepairGuideServiceTests.cs)도 함께 확장했습니다.
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_finish_batch\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch\\` 경고 0 / 오류 0 - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_finish_batch\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch\\` 경고 0 / 오류 0
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|ArtifactRepairGuideServiceTests|DeckQualityReviewServiceTests|DeckRepairGuideServiceTests|HtmlSkillGoldenReportTests|PptxSkillGoldenDeckTests|DocxSkillGoldenDocumentTests|ExcelSkillGoldenWorkbookTests" -p:OutputPath=bin\\verify_doc_finish_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch_tests\\` 통과 26 - 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|ArtifactRepairGuideServiceTests|DeckQualityReviewServiceTests|DeckRepairGuideServiceTests|HtmlSkillGoldenReportTests|PptxSkillGoldenDeckTests|DocxSkillGoldenDocumentTests|ExcelSkillGoldenWorkbookTests" -p:OutputPath=bin\\verify_doc_finish_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch_tests\\` 통과 26
업데이트: 2026-04-15 10:50 (KST)
- 개발언어 fallback을 실제 코드 시스템 프롬프트와 워크스페이스 컨텍스트에 연결했습니다.
- [CodeLanguageCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/CodeLanguageCatalog.cs)를 UTF-8 기준으로 재정리하고, 언어별 `manifest/build/test/lint` 힌트를 조합하는 `BuildWorkspaceWorkflowSummaries()`를 추가했습니다.
- [WorkspaceContextGenerator.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs)는 새 `DetectLanguageWorkflowHints()`를 통해 `.ax-context.md``## Language Workflow` 섹션과 런타임 힌트 생성을 같은 카탈로그 기준으로 공유합니다.
- [ChatWindow.SystemPromptBuilder.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.SystemPromptBuilder.cs)는 코드 탭 시스템 프롬프트에 `## Repository Language Workflow` 섹션을 주입해 no-LSP 저장소에서도 실제 실행 가능한 힌트를 먼저 보게 했습니다.
- 에이전틱 루프의 LLM 요청 준비를 helper로 더 분리했습니다.
- 새 [AgentLoopLlmRequestPreparationService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentLoopLlmRequestPreparationService.cs)가 초기 tool-call 강제 여부와 pre-call reminder 주입을 담당하고, [AgentLoopService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentLoopService.cs)는 orchestration 중심 구조를 더 유지하도록 정리했습니다.
- 테스트를 추가/보강했습니다.
- 새 [AgentLoopLlmRequestPreparationServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/AgentLoopLlmRequestPreparationServiceTests.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) 확장
- 검증:
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_final_batch\\ -p:IntermediateOutputPath=obj\\verify_final_batch\\` 경고 0 / 오류 0
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "CodeLanguageCatalogTests|WorkspaceContextGeneratorTests|AgentLoopLlmRequestPreparationServiceTests|AgentLoopIterationPreparationServiceTests|AgentMessageInvariantHelperTests|AgentToolResultBudgetTests|ChatStorageServiceTests|HtmlSkillGoldenReportTests|PptxSkillGoldenDeckTests|DocxSkillGoldenDocumentTests|ExcelSkillGoldenWorkbookTests" -p:OutputPath=bin\\verify_final_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_final_batch_tests\\` 통과 54

View File

@@ -1116,3 +1116,30 @@ UI ?붿옄???€洹쒕え 由ы뙥?좊쭅 ???꾪뿕 ?묒뾽 ??湲곕줉???덉쟾
- 검증: - 검증:
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_finish_batch\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch\\` - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_finish_batch\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch\\`
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|ArtifactRepairGuideServiceTests|DeckQualityReviewServiceTests|DeckRepairGuideServiceTests|HtmlSkillGoldenReportTests|PptxSkillGoldenDeckTests|DocxSkillGoldenDocumentTests|ExcelSkillGoldenWorkbookTests" -p:OutputPath=bin\\verify_doc_finish_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch_tests\\` - `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|ArtifactRepairGuideServiceTests|DeckQualityReviewServiceTests|DeckRepairGuideServiceTests|HtmlSkillGoldenReportTests|PptxSkillGoldenDeckTests|DocxSkillGoldenDocumentTests|ExcelSkillGoldenWorkbookTests" -p:OutputPath=bin\\verify_doc_finish_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch_tests\\`
업데이트: 2026-04-15 10:50 (KST)
- 개발언어 fallback 심화:
- `CodeLanguageCatalog.cs`
- 파일을 UTF-8 기준으로 재정리하고 중복 `BuildFallbackSupportDescription()`을 제거
- `BuildWorkspaceWorkflowSummaries()` 추가
- 키, quick select key, 확장자, 파일 경로를 모두 받아 capability로 정규화하는 `ResolveCapabilityFromKeyOrExtension()` 추가
- `WorkspaceContextGenerator.cs`
- `DetectLanguageWorkflowHints()` 추가
- `.ax-context.md`의 `Language Workflow` 생성이 카탈로그 공용 API를 사용하도록 정리
- `ChatWindow.SystemPromptBuilder.cs`
- 코드 시스템 프롬프트에 `## Repository Language Workflow` 섹션 주입
- no-LSP 저장소에서도 실제 manifest/build/test/lint 힌트를 prompt 안에서 직접 활용
- 에이전틱 루프 분리:
- 새 `AgentLoopLlmRequestPreparationService.cs`
- 초기 tool-call 강제 여부 계산
- pre-call tool reminder 삽입 여부 계산
- 실제 LLM 전송용 `sendMessages` 배열 조립
- `AgentLoopService.cs`
- LLM 요청 전 메시지 조립 책임을 helper 호출로 대체해 orchestration 집중도 향상
- 테스트 보강:
- 새 `AgentLoopLlmRequestPreparationServiceTests.cs`
- `CodeLanguageCatalogTests.cs`: fallback summary, workflow summary, workspace workflow dedupe/우선순위 검증
- `WorkspaceContextGeneratorTests.cs`: preferred language 우선 `Language Workflow` 힌트 검증
- 검증:
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_final_batch\\ -p:IntermediateOutputPath=obj\\verify_final_batch\\` 경고 0 / 오류 0
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "CodeLanguageCatalogTests|WorkspaceContextGeneratorTests|AgentLoopLlmRequestPreparationServiceTests|AgentLoopIterationPreparationServiceTests|AgentMessageInvariantHelperTests|AgentToolResultBudgetTests|ChatStorageServiceTests|HtmlSkillGoldenReportTests|PptxSkillGoldenDeckTests|DocxSkillGoldenDocumentTests|ExcelSkillGoldenWorkbookTests" -p:OutputPath=bin\\verify_final_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_final_batch_tests\\` 통과 54

View File

@@ -158,6 +158,13 @@
2. `tool_result replacement state`는 synthetic preview를 넘어 fingerprint 재바인딩까지 들어갔습니다. 남은 방향은 compact/branch 이후의 replacement policy를 세션 단위 상태로 더 오래 유지하는 것입니다. 2. `tool_result replacement state`는 synthetic preview를 넘어 fingerprint 재바인딩까지 들어갔습니다. 남은 방향은 compact/branch 이후의 replacement policy를 세션 단위 상태로 더 오래 유지하는 것입니다.
3. 문서 포맷은 `ArtifactQualityOutputFormatter`가 HTML/XLSX뿐 아니라 DOCX/PPTX까지 확장되었습니다. 다음 마감은 포맷별 critic/repair 자체를 더 깊게 하고, golden fixture 샘플을 확대하는 단계입니다. 3. 문서 포맷은 `ArtifactQualityOutputFormatter`가 HTML/XLSX뿐 아니라 DOCX/PPTX까지 확장되었습니다. 다음 마감은 포맷별 critic/repair 자체를 더 깊게 하고, golden fixture 샘플을 확대하는 단계입니다.
업데이트: 2026-04-15 10:50 (KST)
### 추가 진행 메모
1. 개발언어 fallback은 이제 `Language Workflow``.ax-context.md`와 코드 시스템 프롬프트에 모두 주입하는 단계까지 올라왔습니다. 남은 마감은 no-LSP 환경에서 이 힌트를 실제 도구 사용/검증 흐름과 더 촘촘히 묶는 것입니다.
2. 에이전틱 루프는 `run lifecycle`, `queued command projection`, `iteration preparation`, `LLM request preparation` helper까지 분리됐습니다. 다음 큰 축은 `RunAsync`의 tool dispatch/finalize 분리를 더 진행해 본체 책임을 더 줄이는 것입니다.
3. 명령/스킬 합성은 우선순위 충돌 해소가 대부분 정리됐고, 이후 작업은 릴리즈 게이트와 체크리스트를 최종 상태에 맞춰 닫는 단계입니다.
업데이트: 2026-04-15 10:10 (KST) 업데이트: 2026-04-15 10:10 (KST)
### 통합 마감 계획 ### 통합 마감 계획

View File

@@ -0,0 +1,58 @@
using AxCopilot.Models;
using AxCopilot.Services.Agent;
using FluentAssertions;
using Xunit;
namespace AxCopilot.Tests.Services;
public class AgentLoopLlmRequestPreparationServiceTests
{
[Fact]
public void Prepare_ShouldInjectToolReminderOnFirstForcedCall()
{
var queryMessages = new List<ChatMessage>
{
new()
{
Role = "user",
Content = "inspect the repository"
}
};
var result = AgentLoopLlmRequestPreparationService.Prepare(
queryMessages,
totalToolCalls: 0,
forceInitialToolCallEnabled: true,
injectPreCallToolReminder: true,
noToolCallLoopRetry: 0);
result.ForceInitialToolCall.Should().BeTrue();
result.InjectedToolReminder.Should().BeTrue();
result.SendMessages.Should().HaveCount(2);
result.SendMessages.Last().Content.Should().Contain("[TOOL_REQUIRED]");
}
[Fact]
public void Prepare_ShouldSkipReminderWhenRetryLoopIsAlreadyActive()
{
var queryMessages = new List<ChatMessage>
{
new()
{
Role = "user",
Content = "inspect the repository"
}
};
var result = AgentLoopLlmRequestPreparationService.Prepare(
queryMessages,
totalToolCalls: 0,
forceInitialToolCallEnabled: true,
injectPreCallToolReminder: true,
noToolCallLoopRetry: 1);
result.ForceInitialToolCall.Should().BeTrue();
result.InjectedToolReminder.Should().BeFalse();
result.SendMessages.Should().HaveCount(1);
}
}

View File

@@ -57,6 +57,7 @@ public class CodeLanguageCatalogTests
summary.Should().Contain("go build ./..."); summary.Should().Contain("go build ./...");
summary.Should().Contain("go test ./..."); summary.Should().Contain("go test ./...");
} }
[Fact] [Fact]
public void BuildWorkflowSummary_ShouldExposeActionableWorkflowHints() public void BuildWorkflowSummary_ShouldExposeActionableWorkflowHints()
{ {
@@ -77,4 +78,18 @@ public class CodeLanguageCatalogTests
CodeLanguageCatalog.GetTestHints("kotlin").Should().Contain("./gradlew test"); CodeLanguageCatalog.GetTestHints("kotlin").Should().Contain("./gradlew test");
CodeLanguageCatalog.GetLintHints("javascript").Should().Contain("eslint ."); CodeLanguageCatalog.GetLintHints("javascript").Should().Contain("eslint .");
} }
[Fact]
public void BuildWorkspaceWorkflowSummaries_ShouldPreferSelectedLanguageAndDedupeEntries()
{
var summaries = CodeLanguageCatalog.BuildWorkspaceWorkflowSummaries(
[".go", ".py", ".go", ".rs"],
preferredKey: "python",
maxLanguages: 3);
summaries.Should().HaveCount(3);
summaries[0].Should().StartWith("Python:");
summaries.Should().Contain(summary => summary.StartsWith("Go:"));
summaries.Should().Contain(summary => summary.StartsWith("Rust:"));
}
} }

View File

@@ -338,4 +338,27 @@ public class WorkspaceContextGeneratorTests
Directory.Delete(tempDir, recursive: true); Directory.Delete(tempDir, recursive: true);
} }
} }
[Fact]
public void DetectLanguageWorkflowHints_ShouldPreferSelectedLanguage()
{
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 hints = WorkspaceContextGenerator.DetectLanguageWorkflowHints(tempDir, preferredLanguage: "python");
hints.Should().NotBeEmpty();
hints[0].Should().StartWith("Python:");
hints.Should().Contain(hint => hint.StartsWith("Go:"));
}
finally
{
Directory.Delete(tempDir, recursive: true);
}
}
} }

View File

@@ -0,0 +1,52 @@
using AxCopilot.Models;
namespace AxCopilot.Services.Agent;
internal sealed record AgentLoopLlmRequestPreparationResult(
List<ChatMessage> SendMessages,
bool ForceInitialToolCall,
bool InjectedToolReminder);
/// <summary>
/// query view가 만들어진 뒤 실제 LLM 요청 배열을 조립합니다.
/// 초기 tool call 강제 여부와 사전 reminder 주입을 한곳에서 결정해
/// AgentLoopService 본체가 orchestration에 더 집중하도록 분리합니다.
/// </summary>
internal static class AgentLoopLlmRequestPreparationService
{
public static AgentLoopLlmRequestPreparationResult Prepare(
IReadOnlyList<ChatMessage> queryMessages,
int totalToolCalls,
bool forceInitialToolCallEnabled,
bool injectPreCallToolReminder,
int noToolCallLoopRetry)
{
var forceInitialToolCall = totalToolCalls == 0 && forceInitialToolCallEnabled;
if (!forceInitialToolCall
|| !injectPreCallToolReminder
|| noToolCallLoopRetry > 0)
{
return new AgentLoopLlmRequestPreparationResult(
queryMessages.ToList(),
forceInitialToolCall,
false);
}
var sendMessages = queryMessages.ToList();
sendMessages.Add(BuildToolReminderMessage());
return new AgentLoopLlmRequestPreparationResult(
sendMessages,
forceInitialToolCall,
true);
}
internal static ChatMessage BuildToolReminderMessage()
{
return new ChatMessage
{
Role = "user",
Content = "[TOOL_REQUIRED] 지금 즉시 <tool_call> 형식으로 도구를 호출하세요. 텍스트만 반환하면 거부됩니다.\n" +
"Output format:\n<tool_call>\n{\"name\": \"TOOL_NAME\", \"arguments\": {\"param\": \"value\"}}\n</tool_call>"
};
}
}

View File

@@ -87,22 +87,6 @@ public partial class AgentLoopService
requestInterrupt && IsRunning); requestInterrupt && IsRunning);
} }
private void DrainPendingCommands(List<ChatMessage> messages)
{
var queuedSnapshot = _pendingCommands.Snapshot();
if (queuedSnapshot.Count == 0)
return;
var drained = _pendingCommands.DequeuePriorityBatch();
if (drained.Count == 0)
return;
var projection = AgentQueuedCommandProjector.Project(drained, queuedSnapshot.Count - drained.Count);
messages.AddRange(projection.Messages);
foreach (var evt in projection.Events)
EmitEvent(evt.Type, evt.ToolName, evt.Summary);
}
/// <summary>에이전트 이벤트 스트림 (UI 바인딩용).</summary> /// <summary>에이전트 이벤트 스트림 (UI 바인딩용).</summary>
public ObservableCollection<AgentEvent> Events { get; } = new(); public ObservableCollection<AgentEvent> Events { get; } = new();
@@ -557,25 +541,14 @@ public partial class AgentLoopService
EmitEvent(AgentEventType.Error, "", "현재 스킬 런타임 정책으로 사용 가능한 도구가 없습니다."); EmitEvent(AgentEventType.Error, "", "현재 스킬 런타임 정책으로 사용 가능한 도구가 없습니다.");
return "⚠ 현재 스킬 정책에서 허용된 도구가 없어 작업을 진행할 수 없습니다. allowed-tools 설정을 확인하세요."; return "⚠ 현재 스킬 정책에서 허용된 도구가 없어 작업을 진행할 수 없습니다. allowed-tools 설정을 확인하세요.";
} }
// totalToolCalls == 0: 아직 한 번도 도구를 안 불렀으면 tool_choice:"required" 강제 var llmRequest = AgentLoopLlmRequestPreparationService.Prepare(
// → chatty 모델(Qwen 등)이 텍스트 설명만 하고 도구를 안 부르는 현상 방지 queryMessages,
var forceFirst = totalToolCalls == 0 && executionPolicy.ForceInitialToolCall; totalToolCalls,
executionPolicy.ForceInitialToolCall,
// IBM/Qwen 등 chatty 모델 대응: 첫 번째 호출 직전 마지막 user 메시지로 도구 호출 강제 reminder 주입. executionPolicy.InjectPreCallToolReminder,
// recovery 메시지가 이미 추가된 경우(NoToolCallLoopRetry > 0)에는 중복 주입하지 않음. runState.NoToolCallLoopRetry);
// 임시 메시지이므로 실제 messages 목록은 수정하지 않고, 별도 sendMessages로 전달. var forceFirst = llmRequest.ForceInitialToolCall;
List<ChatMessage> sendMessages = queryMessages; var sendMessages = llmRequest.SendMessages;
if (forceFirst
&& executionPolicy.InjectPreCallToolReminder
&& runState.NoToolCallLoopRetry == 0)
{
sendMessages = [.. queryMessages, new ChatMessage
{
Role = "user",
Content = "[TOOL_REQUIRED] 지금 즉시 <tool_call> 형식으로 도구를 호출하세요. 텍스트만 반환하면 거부됩니다.\n" +
"Output format:\n<tool_call>\n{\"name\": \"TOOL_NAME\", \"arguments\": {\"param\": \"value\"}}\n</tool_call>"
}];
}
// 워크플로우 상세 로그: LLM 요청 // 워크플로우 상세 로그: LLM 요청
llmCallSw.Restart(); llmCallSw.Restart();

View File

@@ -1,5 +1,6 @@
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -174,6 +175,21 @@ internal static class WorkspaceContextGenerator
catch { return null; } catch { return null; }
} }
internal static IReadOnlyList<string> DetectLanguageWorkflowHints(
string? workFolder,
string? preferredLanguage = null,
int maxLanguages = 3)
{
if (string.IsNullOrWhiteSpace(workFolder) || !Directory.Exists(workFolder))
return [];
var extDist = GetExtensionDistribution(workFolder, CancellationToken.None);
return CodeLanguageCatalog.BuildWorkspaceWorkflowSummaries(
extDist.Select(x => x.Key),
preferredLanguage,
maxLanguages);
}
// ════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════
// 분석 로직 // 분석 로직
// ════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════
@@ -450,26 +466,7 @@ internal static class WorkspaceContextGenerator
.ToList(); .ToList();
private static List<string> BuildLanguageWorkflow(List<KeyValuePair<string, int>> extDist) private static List<string> BuildLanguageWorkflow(List<KeyValuePair<string, int>> extDist)
{ => CodeLanguageCatalog.BuildWorkspaceWorkflowSummaries(extDist.Select(x => x.Key)).ToList();
var workflow = new List<string>();
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var (extension, _) in extDist)
{
var capability = Services.CodeLanguageCatalog.FindByExtension(extension);
if (capability == null || !seen.Add(capability.Key))
continue;
var summary = Services.CodeLanguageCatalog.BuildWorkflowSummary(capability.Key);
if (!string.IsNullOrWhiteSpace(summary))
workflow.Add(summary);
if (workflow.Count >= 3)
break;
}
return workflow;
}
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

@@ -20,12 +20,7 @@ public sealed record CodeLanguageCapability(
/// <summary> /// <summary>
/// 코드 탭과 에이전트가 공통으로 참조하는 언어 지원 카탈로그. /// 코드 탭과 에이전트가 공통으로 참조하는 언어 지원 카탈로그.
/// - 파일 분류 /// 파일 분류, 시스템 프롬프트 가이드, build/test/lint 힌트, LSP 가능 여부를 한 곳에서 관리합니다.
/// - 인덱싱 대상 확장자
/// - 시스템 프롬프트 언어 가이드
/// - LSP 연동 가능 언어
/// - 설정 UI 설명 문자열
/// 를 한 곳에서 관리합니다.
/// </summary> /// </summary>
public static class CodeLanguageCatalog public static class CodeLanguageCatalog
{ {
@@ -312,6 +307,7 @@ public static class CodeLanguageCatalog
{ {
if (string.IsNullOrWhiteSpace(key)) if (string.IsNullOrWhiteSpace(key))
return null; return null;
return s_byKey.TryGetValue(key.Trim(), out var found) ? found : null; return s_byKey.TryGetValue(key.Trim(), out var found) ? found : null;
} }
@@ -319,6 +315,7 @@ public static class CodeLanguageCatalog
{ {
if (string.IsNullOrWhiteSpace(extension)) if (string.IsNullOrWhiteSpace(extension))
return null; return null;
return s_byExtension.TryGetValue(extension.Trim(), out var found) ? found : null; return s_byExtension.TryGetValue(extension.Trim(), out var found) ? found : null;
} }
@@ -380,6 +377,7 @@ public static class CodeLanguageCatalog
sb.Append(BuildLspSupportDescription()); sb.Append(BuildLspSupportDescription());
return sb.ToString(); return sb.ToString();
} }
public static IReadOnlyList<string> GetManifestHints(string? key) public static IReadOnlyList<string> GetManifestHints(string? key)
{ {
var normalizedKey = FindByKey(key)?.Key; var normalizedKey = FindByKey(key)?.Key;
@@ -446,21 +444,45 @@ public static class CodeLanguageCatalog
return $"{capability.DisplayName}: {string.Join(" | ", parts)}"; return $"{capability.DisplayName}: {string.Join(" | ", parts)}";
} }
public static IReadOnlyList<string> BuildWorkspaceWorkflowSummaries(
IEnumerable<string?> extensionsOrKeys,
string? preferredKey = null,
int maxLanguages = 3,
int maxHintsPerKind = 2)
{
var results = new List<string>();
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
void TryAppend(CodeLanguageCapability? capability)
{
if (capability == null || !seen.Add(capability.Key))
return;
var summary = BuildWorkflowSummary(capability.Key, maxHintsPerKind);
if (!string.IsNullOrWhiteSpace(summary))
results.Add(summary);
}
TryAppend(ResolveCapabilityFromKeyOrExtension(preferredKey));
foreach (var value in extensionsOrKeys ?? [])
{
TryAppend(ResolveCapabilityFromKeyOrExtension(value));
if (results.Count >= Math.Max(1, maxLanguages))
break;
}
return results;
}
public static string BuildFallbackSupportDescription() public static string BuildFallbackSupportDescription()
=> "LSP 서버가 없거나 연결되지 않아도 확장자, 매니페스트, build/test/lint 힌트 기반의 정적 분석을 계속 제공합니다."; => "LSP 서버가 없거나 연결되지 않아도 확장자, 매니페스트, build/test/lint 힌트 기반의 정적 fallback 분석을 계속 제공합니다.";
public static string BuildFallbackSummary(string? filePathOrExtension) public static string BuildFallbackSummary(string? filePathOrExtension)
{ {
CodeLanguageCapability? capability = null; var capability = ResolveCapabilityFromKeyOrExtension(filePathOrExtension);
if (!string.IsNullOrWhiteSpace(filePathOrExtension))
{
capability = filePathOrExtension.StartsWith('.')
? FindByExtension(filePathOrExtension)
: FindByExtension(Path.GetExtension(filePathOrExtension));
}
if (capability == null) if (capability == null)
return "정적 fallback: 확장자와 프로젝트 매니페스트를 먼저 확인하고 관련 build/test 명령 힌트를 따라 수동 검증하세요."; return "정적 fallback: 확장자와 프로젝트 매니페스트를 먼저 확인하고 관련 build/test/lint 힌트를 따라 수동 검증하세요.";
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine($"정적 fallback 분석: {capability.DisplayName}"); sb.AppendLine($"정적 fallback 분석: {capability.DisplayName}");
@@ -484,6 +506,30 @@ public static class CodeLanguageCatalog
return sb.ToString().TrimEnd(); return sb.ToString().TrimEnd();
} }
private static CodeLanguageCapability? ResolveCapabilityFromKeyOrExtension(string? value)
{
if (string.IsNullOrWhiteSpace(value))
return null;
var normalized = value.Trim();
var capability = FindByKey(normalized);
if (capability != null)
return capability;
if (normalized.StartsWith('.'))
return FindByExtension(normalized);
capability = QuickSelectLanguages
.FirstOrDefault(x => string.Equals(x.QuickSelectKey, normalized, StringComparison.OrdinalIgnoreCase));
if (capability != null)
return capability;
var extension = Path.GetExtension(normalized);
return string.IsNullOrWhiteSpace(extension)
? null
: FindByExtension(extension);
}
private static void AppendHintBlock(List<string> parts, string label, IReadOnlyList<string> hints, int maxHintsPerKind) private static void AppendHintBlock(List<string> parts, string label, IReadOnlyList<string> hints, int maxHintsPerKind)
{ {
if (hints.Count == 0) if (hints.Count == 0)

View File

@@ -290,6 +290,15 @@ public partial class ChatWindow
foreach (var guidance in CodeLanguageCatalog.GetGuidanceLines(_selectedLanguage == "auto" ? null : _selectedLanguage)) foreach (var guidance in CodeLanguageCatalog.GetGuidanceLines(_selectedLanguage == "auto" ? null : _selectedLanguage))
sb.AppendLine(guidance); sb.AppendLine(guidance);
sb.AppendLine("- Fallback: If LSP is unavailable, continue with extension-based detection, manifest files, and likely build/test/lint commands instead of stopping."); sb.AppendLine("- Fallback: If LSP is unavailable, continue with extension-based detection, manifest files, and likely build/test/lint commands instead of stopping.");
var repositoryLanguageWorkflow = WorkspaceContextGenerator.DetectLanguageWorkflowHints(
workFolder,
_selectedLanguage == "auto" ? null : _selectedLanguage);
if (repositoryLanguageWorkflow.Count > 0)
{
sb.AppendLine("\n## Repository Language Workflow");
foreach (var workflow in repositoryLanguageWorkflow)
sb.AppendLine("- " + workflow);
}
// 코드 품질 + 안전 수칙 // 코드 품질 + 안전 수칙
sb.AppendLine("\n## Code Quality & Safety"); sb.AppendLine("\n## Code Quality & Safety");