???? ?? ?? ?? ??? ?? 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)도 함께 확장했습니다.
- 검증: `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
업데이트: 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 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를 세션 단위 상태로 더 오래 유지하는 것입니다.
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)
### 통합 마감 계획

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 test ./...");
}
[Fact]
public void BuildWorkflowSummary_ShouldExposeActionableWorkflowHints()
{
@@ -77,4 +78,18 @@ public class CodeLanguageCatalogTests
CodeLanguageCatalog.GetTestHints("kotlin").Should().Contain("./gradlew test");
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);
}
}
[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);
}
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>
public ObservableCollection<AgentEvent> Events { get; } = new();
@@ -557,25 +541,14 @@ public partial class AgentLoopService
EmitEvent(AgentEventType.Error, "", "현재 스킬 런타임 정책으로 사용 가능한 도구가 없습니다.");
return "⚠ 현재 스킬 정책에서 허용된 도구가 없어 작업을 진행할 수 없습니다. allowed-tools 설정을 확인하세요.";
}
// totalToolCalls == 0: 아직 한 번도 도구를 안 불렀으면 tool_choice:"required" 강제
// → chatty 모델(Qwen 등)이 텍스트 설명만 하고 도구를 안 부르는 현상 방지
var forceFirst = totalToolCalls == 0 && executionPolicy.ForceInitialToolCall;
// IBM/Qwen 등 chatty 모델 대응: 첫 번째 호출 직전 마지막 user 메시지로 도구 호출 강제 reminder 주입.
// recovery 메시지가 이미 추가된 경우(NoToolCallLoopRetry > 0)에는 중복 주입하지 않음.
// 임시 메시지이므로 실제 messages 목록은 수정하지 않고, 별도 sendMessages로 전달.
List<ChatMessage> sendMessages = queryMessages;
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>"
}];
}
var llmRequest = AgentLoopLlmRequestPreparationService.Prepare(
queryMessages,
totalToolCalls,
executionPolicy.ForceInitialToolCall,
executionPolicy.InjectPreCallToolReminder,
runState.NoToolCallLoopRetry);
var forceFirst = llmRequest.ForceInitialToolCall;
var sendMessages = llmRequest.SendMessages;
// 워크플로우 상세 로그: LLM 요청
llmCallSw.Restart();

View File

@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@@ -174,6 +175,21 @@ internal static class WorkspaceContextGenerator
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();
private static List<string> BuildLanguageWorkflow(List<KeyValuePair<string, int>> extDist)
{
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;
}
=> CodeLanguageCatalog.BuildWorkspaceWorkflowSummaries(extDist.Select(x => x.Key)).ToList();
private static async Task<(string? Branch, string? Remote)> GetGitInfoAsync(
string folder, CancellationToken ct)

View File

@@ -20,12 +20,7 @@ public sealed record CodeLanguageCapability(
/// <summary>
/// 코드 탭과 에이전트가 공통으로 참조하는 언어 지원 카탈로그.
/// - 파일 분류
/// - 인덱싱 대상 확장자
/// - 시스템 프롬프트 언어 가이드
/// - LSP 연동 가능 언어
/// - 설정 UI 설명 문자열
/// 를 한 곳에서 관리합니다.
/// 파일 분류, 시스템 프롬프트 가이드, build/test/lint 힌트, LSP 가능 여부를 한 곳에서 관리합니다.
/// </summary>
public static class CodeLanguageCatalog
{
@@ -100,187 +95,187 @@ public static class CodeLanguageCatalog
new(new List<CodeLanguageCapability>
{
new(
"csharp",
"C# (.NET)",
[".cs", ".csx", ".csproj", ".sln"],
[
"Use dotnet CLI, solution/project files, and NuGet package conventions.",
"Follow Microsoft naming conventions and prefer targeted edits over broad rewrites.",
"Verify impact on callers, DI registration, nullable flow, and build configuration."
],
LspLanguageId: "csharp",
ShowInQuickSelect: true,
QuickSelectKey: "csharp",
QuickSelectLabel: "C# (.NET)",
QuickSelectIcon: "\uD83D\uDD39"),
"csharp",
"C# (.NET)",
[".cs", ".csx", ".csproj", ".sln"],
[
"Use dotnet CLI, solution/project files, and NuGet package conventions.",
"Follow Microsoft naming conventions and prefer targeted edits over broad rewrites.",
"Verify impact on callers, DI registration, nullable flow, and build configuration."
],
LspLanguageId: "csharp",
ShowInQuickSelect: true,
QuickSelectKey: "csharp",
QuickSelectLabel: "C# (.NET)",
QuickSelectIcon: "\uD83D\uDD39"),
new(
"python",
"Python",
[".py", ".pyi", ".ipynb"],
[
"Use pip/venv or conda only if already available in the environment.",
"Follow PEP 8, type hints, and module/package boundaries.",
"Prefer small focused functions and verify import/runtime errors after edits."
],
LspLanguageId: "python",
ShowInQuickSelect: true,
QuickSelectKey: "python",
QuickSelectLabel: "Python",
QuickSelectIcon: "\uD83D\uDC0D"),
"python",
"Python",
[".py", ".pyi", ".ipynb"],
[
"Use pip/venv or conda only if already available in the environment.",
"Follow PEP 8, type hints, and module/package boundaries.",
"Prefer small focused functions and verify import/runtime errors after edits."
],
LspLanguageId: "python",
ShowInQuickSelect: true,
QuickSelectKey: "python",
QuickSelectLabel: "Python",
QuickSelectIcon: "\uD83D\uDC0D"),
new(
"java",
"Java",
[".java", ".gradle", ".pom"],
[
"Use Maven or Gradle conventions already present in the repository.",
"Follow package structure, visibility rules, and style consistent with the existing codebase.",
"Check interfaces, implementations, and test fixtures together when modifying shared behavior."
],
LspLanguageId: "java",
ShowInQuickSelect: true,
QuickSelectKey: "java",
QuickSelectLabel: "Java",
QuickSelectIcon: "\u2615"),
"java",
"Java",
[".java", ".gradle", ".pom"],
[
"Use Maven or Gradle conventions already present in the repository.",
"Follow package structure, visibility rules, and style consistent with the existing codebase.",
"Check interfaces, implementations, and test fixtures together when modifying shared behavior."
],
LspLanguageId: "java",
ShowInQuickSelect: true,
QuickSelectKey: "java",
QuickSelectLabel: "Java",
QuickSelectIcon: "\u2615"),
new(
"cpp",
"C / C++",
[".c", ".cc", ".cxx", ".cpp", ".h", ".hh", ".hpp", ".inl"],
[
"Respect the repository's existing build system, usually CMake, MSBuild, or compiler-specific scripts.",
"Be careful with headers, include order, ownership, ABI-sensitive changes, and platform guards.",
"Validate both declaration and implementation impact when editing shared types."
],
LspLanguageId: "cpp",
ShowInQuickSelect: true,
QuickSelectKey: "cpp",
QuickSelectLabel: "C/C++",
QuickSelectIcon: "\u2699"),
"cpp",
"C / C++",
[".c", ".cc", ".cxx", ".cpp", ".h", ".hh", ".hpp", ".inl"],
[
"Respect the repository's existing build system, usually CMake, MSBuild, or compiler-specific scripts.",
"Be careful with headers, include order, ownership, ABI-sensitive changes, and platform guards.",
"Validate both declaration and implementation impact when editing shared types."
],
LspLanguageId: "cpp",
ShowInQuickSelect: true,
QuickSelectKey: "cpp",
QuickSelectLabel: "C/C++",
QuickSelectIcon: "\u2699"),
new(
"typescript",
"TypeScript",
[".ts", ".tsx", ".mts", ".cts"],
[
"Use the existing package manager and tsconfig structure.",
"Prefer explicit types on public boundaries and check build/lint config before changing module format.",
"Preserve framework conventions already used by the project."
],
LspLanguageId: "typescript"),
"typescript",
"TypeScript",
[".ts", ".tsx", ".mts", ".cts"],
[
"Use the existing package manager and tsconfig structure.",
"Prefer explicit types on public boundaries and check build/lint config before changing module format.",
"Preserve framework conventions already used by the project."
],
LspLanguageId: "typescript"),
new(
"javascript",
"JavaScript / Vue",
[".js", ".jsx", ".mjs", ".cjs", ".vue"],
[
"Use the existing Node package manager and lint/format rules.",
"For Vue, preserve the current component style and API pattern used by the project.",
"Check module boundaries, imports, and runtime side effects after edits."
],
LspLanguageId: "javascript",
ShowInQuickSelect: true,
QuickSelectKey: "javascript",
QuickSelectLabel: "JavaScript / Vue",
QuickSelectIcon: "\uD83C\uDF10"),
"javascript",
"JavaScript / Vue",
[".js", ".jsx", ".mjs", ".cjs", ".vue"],
[
"Use the existing Node package manager and lint/format rules.",
"For Vue, preserve the current component style and API pattern used by the project.",
"Check module boundaries, imports, and runtime side effects after edits."
],
LspLanguageId: "javascript",
ShowInQuickSelect: true,
QuickSelectKey: "javascript",
QuickSelectLabel: "JavaScript / Vue",
QuickSelectIcon: "\uD83C\uDF10"),
new(
"go",
"Go",
[".go", ".mod", ".sum"],
[
"Preserve package boundaries, error-first flow, and gofmt-style formatting.",
"Check interfaces, exported identifiers, and concurrency-sensitive changes together."
],
LspLanguageId: "go"),
"go",
"Go",
[".go", ".mod", ".sum"],
[
"Preserve package boundaries, error-first flow, and gofmt-style formatting.",
"Check interfaces, exported identifiers, and concurrency-sensitive changes together."
],
LspLanguageId: "go"),
new(
"rust",
"Rust",
[".rs", ".toml"],
[
"Respect Cargo workspace structure, ownership/borrowing rules, and crate boundaries.",
"Prefer explicit enums/results and verify compiler diagnostics after edits."
],
LspLanguageId: "rust"),
"rust",
"Rust",
[".rs", ".toml"],
[
"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",
[".php", ".phtml"],
[
"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"),
"php",
"PHP",
[".php", ".phtml"],
[
"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",
[".rb", ".rake", ".gemspec"],
[
"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"),
"ruby",
"Ruby",
[".rb", ".rake", ".gemspec"],
[
"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",
[".kt", ".kts"],
[
"Preserve Gradle structure, package layout, and nullability intent.",
"Be careful with JVM interop boundaries and Android-specific module structure when present."
],
LspLanguageId: "kotlin"),
"kotlin",
"Kotlin",
[".kt", ".kts"],
[
"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",
[".swift"],
[
"Preserve target structure, Apple framework imports, and protocol-oriented design already in use.",
"Check app lifecycle and platform-specific behavior after edits."
],
LspLanguageId: "swift"),
"swift",
"Swift",
[".swift"],
[
"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",
[".scala", ".sc"],
[
"Respect sbt/module structure, functional style, and existing typeclass or Akka patterns if present.",
"Keep public APIs simple and avoid unnecessary type-level churn."
]),
"scala",
"Scala",
[".scala", ".sc"],
[
"Respect sbt/module structure, functional style, and existing typeclass or Akka patterns if present.",
"Keep public APIs simple and avoid unnecessary type-level churn."
]),
new(
"shell",
"Shell",
[".sh", ".bash", ".zsh"],
[
"Prefer safe quoting, explicit exit handling, and repository-local scripts over one-off inline shell.",
"Check portability assumptions and environment-specific commands."
]),
"shell",
"Shell",
[".sh", ".bash", ".zsh"],
[
"Prefer safe quoting, explicit exit handling, and repository-local scripts over one-off inline shell.",
"Check portability assumptions and environment-specific commands."
]),
new(
"powershell",
"PowerShell",
[".ps1", ".psm1", ".psd1", ".bat", ".cmd"],
[
"Prefer native PowerShell cmdlets and safe path handling.",
"Be careful with Windows-specific side effects, quoting, and admin-sensitive operations."
]),
"powershell",
"PowerShell",
[".ps1", ".psm1", ".psd1", ".bat", ".cmd"],
[
"Prefer native PowerShell cmdlets and safe path handling.",
"Be careful with Windows-specific side effects, quoting, and admin-sensitive operations."
]),
new(
"sql",
"SQL",
[".sql"],
[
"Preserve migration ordering, transactional safety, and index/constraint compatibility.",
"Call out destructive or data-migrating changes explicitly."
]),
"sql",
"SQL",
[".sql"],
[
"Preserve migration ordering, transactional safety, and index/constraint compatibility.",
"Call out destructive or data-migrating changes explicitly."
]),
new(
"web",
"HTML / CSS / SCSS",
[".html", ".htm", ".css", ".scss", ".sass", ".less", ".xaml"],
[
"Preserve the existing design system, layout structure, and accessibility semantics.",
"Prefer incremental visual changes and keep selectors/components scoped."
]),
"web",
"HTML / CSS / SCSS",
[".html", ".htm", ".css", ".scss", ".sass", ".less", ".xaml"],
[
"Preserve the existing design system, layout structure, and accessibility semantics.",
"Prefer incremental visual changes and keep selectors/components scoped."
]),
new(
"markup",
"JSON / YAML / XML / Markdown",
[".json", ".jsonc", ".xml", ".yaml", ".yml", ".md", ".txt"],
[
"Preserve schema shape, indentation style, and comment/document conventions already used in the repository.",
"Validate references, keys, and generated consumer impact after edits."
]),
"markup",
"JSON / YAML / XML / Markdown",
[".json", ".jsonc", ".xml", ".yaml", ".yml", ".md", ".txt"],
[
"Preserve schema shape, indentation style, and comment/document conventions already used in the repository.",
"Validate references, keys, and generated consumer impact after edits."
]),
});
private static readonly ReadOnlyDictionary<string, CodeLanguageCapability> s_byKey =
@@ -312,6 +307,7 @@ public static class CodeLanguageCatalog
{
if (string.IsNullOrWhiteSpace(key))
return null;
return s_byKey.TryGetValue(key.Trim(), out var found) ? found : null;
}
@@ -319,6 +315,7 @@ public static class CodeLanguageCatalog
{
if (string.IsNullOrWhiteSpace(extension))
return null;
return s_byExtension.TryGetValue(extension.Trim(), out var found) ? found : null;
}
@@ -380,6 +377,7 @@ public static class CodeLanguageCatalog
sb.Append(BuildLspSupportDescription());
return sb.ToString();
}
public static IReadOnlyList<string> GetManifestHints(string? key)
{
var normalizedKey = FindByKey(key)?.Key;
@@ -446,21 +444,45 @@ public static class CodeLanguageCatalog
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()
=> "LSP 서버가 없거나 연결되지 않아도 확장자, 매니페스트, build/test/lint 힌트 기반의 정적 분석을 계속 제공합니다.";
=> "LSP 서버가 없거나 연결되지 않아도 확장자, 매니페스트, build/test/lint 힌트 기반의 정적 fallback 분석을 계속 제공합니다.";
public static string BuildFallbackSummary(string? filePathOrExtension)
{
CodeLanguageCapability? capability = null;
if (!string.IsNullOrWhiteSpace(filePathOrExtension))
{
capability = filePathOrExtension.StartsWith('.')
? FindByExtension(filePathOrExtension)
: FindByExtension(Path.GetExtension(filePathOrExtension));
}
var capability = ResolveCapabilityFromKeyOrExtension(filePathOrExtension);
if (capability == null)
return "정적 fallback: 확장자와 프로젝트 매니페스트를 먼저 확인하고 관련 build/test 명령 힌트를 따라 수동 검증하세요.";
return "정적 fallback: 확장자와 프로젝트 매니페스트를 먼저 확인하고 관련 build/test/lint 힌트를 따라 수동 검증하세요.";
var sb = new StringBuilder();
sb.AppendLine($"정적 fallback 분석: {capability.DisplayName}");
@@ -484,6 +506,30 @@ public static class CodeLanguageCatalog
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)
{
if (hints.Count == 0)

View File

@@ -290,6 +290,15 @@ public partial class ChatWindow
foreach (var guidance in CodeLanguageCatalog.GetGuidanceLines(_selectedLanguage == "auto" ? null : _selectedLanguage))
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.");
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");