Code 탭 자동 스킬 오탐과 런타임 도구 제한 회귀 수정
원인: proactive auto skill이 실제 매치가 없어도 기본 점수만으로 선택되고, guidance 단계에서 allowed_tools 같은 하드 런타임 정책까지 자동 주입되어 빈 작업 폴더 요청에서 file_write가 빠진 채 종료됐습니다. 수정: SkillService의 proactive skill 점수를 실제 키워드·경로 신호 중심으로 다시 계산하고, auto skill은 guidance만 제공하며 하드 runtime policy는 더 이상 자동 주입하지 않도록 변경했습니다. 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_auto_skill_runtime_fix\ -p:IntermediateOutputPath=obj\verify_auto_skill_runtime_fix\ (경고 0 / 오류 0), dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter SkillServiceRuntimePolicyTests|FullyQualifiedName~RunAsync_EmptyWorkspace_BlocksExternalFallbackAndRecoversToFileWrite|FullyQualifiedName~RunAsync_EmptyWorkspace_DisallowsSkillManagerAndRecoversToFileWrite -p:OutputPath=bin\verify_auto_skill_runtime_fix_tests\ -p:IntermediateOutputPath=obj\verify_auto_skill_runtime_fix_tests\ (통과 15)
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# AX Commander
|
||||
|
||||
- 업데이트: 2026-04-15 19:46 (KST)
|
||||
- Code 탭 auto skill 선택을 실제 키워드·경로 매치 기반으로 다시 조정했습니다. `src/AxCopilot/Services/Agent/SkillService.cs`가 기본 점수만으로 무관한 번들 스킬을 매 요청마다 붙이지 않도록 바뀌었습니다.
|
||||
- 같은 파일에서 proactive auto skill은 guidance만 주고 `allowed_tools` 같은 하드 런타임 정책은 더 이상 자동 주입하지 않습니다. 빈 작업 폴더 생성 요청이 `file_write` 없이 종료되던 회귀를 막는 목적입니다.
|
||||
- 테스트: `src/AxCopilot.Tests/Services/SkillServiceRuntimePolicyTests.cs`에 무관한 요청에서는 auto skill이 붙지 않는 케이스와, 매치된 auto skill도 런타임 정책을 강제 주입하지 않는 케이스를 추가했습니다.
|
||||
|
||||
- 업데이트: 2026-04-15 19:31 (KST)
|
||||
- AX Agent 상단 라이브 안내 카드가 실행 중인데도 사라지던 회귀를 수정했습니다. `src/AxCopilot/Views/ChatWindow.xaml.cs`는 이제 같은 탭에 실행이 살아 있는 동안 상단 안내 카드와 상태 바를 유지하고, 현재 대화가 실행 대화와 다를 때는 본문 실행 이력만 숨긴 채 상단 진행 안내는 계속 보여주도록 분리합니다.
|
||||
- `src/AxCopilot/Views/ChatStreamingUiPolicy.cs`를 추가해 `숨김 / 현재 대화 / 같은 탭 백그라운드 대화` 상태를 명시적으로 분류하고, `src/AxCopilot.Tests/Views/ChatStreamingUiPolicyTests.cs`로 상단 가이드 유지 정책 회귀 테스트를 고정했습니다.
|
||||
|
||||
@@ -1517,3 +1517,9 @@ UI ?遺우쁽????域뱀뮆???귐뗫솯?醫딆춦 ???袁る퓮 ?臾믩씜 ??疫
|
||||
- `src/AxCopilot/Views/ChatStreamingUiPolicy.cs`를 추가해 `Hidden`, `ActiveConversation`, `BackgroundConversation` 세 상태를 명시적으로 분류하고, `src/AxCopilot.Tests/Views/ChatStreamingUiPolicyTests.cs`에 상단 가이드 유지 및 본문 렌더 분리 회귀 테스트를 추가했습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_live_guide_persistence\\ -p:IntermediateOutputPath=obj\\verify_live_guide_persistence\\` 경고 0 / 오류 0
|
||||
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatStreamingUiPolicyTests|ChatWindowSlashPolicyTests|ChatSessionStateServiceTests|AxAgentExecutionEngineTests" -p:OutputPath=bin\\verify_live_guide_persistence_tests\\ -p:IntermediateOutputPath=obj\\verify_live_guide_persistence_tests\\` 통과 98
|
||||
업데이트: 2026-04-15 19:46 (KST)
|
||||
- Code 탭 proactive auto skill 선택을 실제 키워드·경로 신호 기반으로 다시 제한했습니다. `src/AxCopilot/Services/Agent/SkillService.cs`에서 기본 점수만으로 무관한 번들 스킬이 항상 선택되던 경로를 제거해, 일반 코드 생성 요청에 unrelated skill runtime이 덧붙지 않도록 했습니다.
|
||||
- 같은 파일에서 `BuildProactiveSkillSystemPromptAsync(...)`는 auto skill guidance에 더 이상 `[Skill Runtime Policy]`를 합치지 않도록 변경했습니다. 이 회귀 때문에 `allowed_tools`가 7개 수준으로 좁아지면서 빈 작업 폴더 생성 요청에서 `file_write`가 빠져 조기 종료되던 문제가 재현됐습니다.
|
||||
- `src/AxCopilot.Tests/Services/SkillServiceRuntimePolicyTests.cs`에 `BuildProactiveSkillSystemPromptAsync_ReturnsNull_WhenNothingMeaningfullyMatches`, `BuildProactiveSkillSystemPromptAsync_DoesNotInjectHardRuntimePolicy`를 추가했습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_auto_skill_runtime_fix\\ -p:IntermediateOutputPath=obj\\verify_auto_skill_runtime_fix\\` 경고 0 / 오류 0
|
||||
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "SkillServiceRuntimePolicyTests|FullyQualifiedName~RunAsync_EmptyWorkspace_BlocksExternalFallbackAndRecoversToFileWrite|FullyQualifiedName~RunAsync_EmptyWorkspace_DisallowsSkillManagerAndRecoversToFileWrite" -p:OutputPath=bin\\verify_auto_skill_runtime_fix_tests\\ -p:IntermediateOutputPath=obj\\verify_auto_skill_runtime_fix_tests\\` 통과 15
|
||||
|
||||
@@ -263,6 +263,70 @@ public class SkillServiceRuntimePolicyTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildProactiveSkillSystemPromptAsync_ReturnsNull_WhenNothingMeaningfullyMatches()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), "axcopilot-skill-proactive-none-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
try
|
||||
{
|
||||
SkillService.LoadSkills(projectRoot: tempDir);
|
||||
|
||||
var prompt = await SkillService.BuildProactiveSkillSystemPromptAsync(
|
||||
"실시간으로 시간을 표시해주는 웹페이지를 만들어",
|
||||
"Code",
|
||||
tempDir);
|
||||
|
||||
prompt.Should().BeNull();
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildProactiveSkillSystemPromptAsync_DoesNotInjectHardRuntimePolicy()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), "axcopilot-skill-proactive-runtime-" + Guid.NewGuid().ToString("N"));
|
||||
var skillDir = Path.Combine(tempDir, ".claude", "skills", "triage");
|
||||
Directory.CreateDirectory(skillDir);
|
||||
var skillPath = Path.Combine(skillDir, "SKILL.md");
|
||||
try
|
||||
{
|
||||
var content = """
|
||||
---
|
||||
user-invocable: false
|
||||
tabs: code
|
||||
when_to_use: build failure test error
|
||||
allowed-tools:
|
||||
- file_read
|
||||
- build_run
|
||||
---
|
||||
|
||||
Investigate failures.
|
||||
""";
|
||||
File.WriteAllText(skillPath, content, Encoding.UTF8);
|
||||
|
||||
SkillService.LoadSkills(projectRoot: tempDir);
|
||||
|
||||
var prompt = await SkillService.BuildProactiveSkillSystemPromptAsync(
|
||||
"build failure and test error in pipeline",
|
||||
"Code",
|
||||
tempDir);
|
||||
|
||||
prompt.Should().NotBeNull();
|
||||
prompt.Should().Contain("[Auto Skill: triage]");
|
||||
prompt.Should().Contain("Investigate failures.");
|
||||
prompt.Should().NotContain("[Skill Runtime Policy]");
|
||||
prompt.Should().NotContain("allowed_tools:");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoadSkills_LoadsAncestorProjectSkills_AndLegacyCommandFiles()
|
||||
{
|
||||
|
||||
@@ -178,11 +178,12 @@ public static class SkillService
|
||||
foreach (var skill in selected)
|
||||
{
|
||||
var compiledPrompt = await CompileSkillPromptAsync(skill, "", workFolder, ct).ConfigureAwait(false);
|
||||
var runtimePolicy = BuildRuntimeDirective(skill);
|
||||
var payload = string.IsNullOrWhiteSpace(runtimePolicy)
|
||||
? compiledPrompt
|
||||
: $"{compiledPrompt}\n\n{runtimePolicy}";
|
||||
sections.Add($"[Auto Skill: {skill.Name}]\n{payload}");
|
||||
if (string.IsNullOrWhiteSpace(compiledPrompt))
|
||||
continue;
|
||||
|
||||
// Auto skills are lightweight guidance only. Hard runtime overrides such as
|
||||
// allowed_tools or model/context switches must stay opt-in via explicit skill use.
|
||||
sections.Add($"[Auto Skill: {skill.Name}]\n{compiledPrompt}");
|
||||
}
|
||||
|
||||
return sections.Count == 0
|
||||
@@ -1292,10 +1293,12 @@ public static class SkillService
|
||||
.Select(skill => new
|
||||
{
|
||||
Skill = skill,
|
||||
Score = ScoreSkill(skill, userText, queryTokens)
|
||||
Score = ScoreSkill(skill, userText, queryTokens),
|
||||
Priority = GetSkillSourcePriority(skill.SourceScope),
|
||||
})
|
||||
.Where(x => x.Score > 0)
|
||||
.OrderByDescending(x => x.Score)
|
||||
.ThenByDescending(x => x.Priority)
|
||||
.ThenBy(x => x.Skill.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(x => x.Skill);
|
||||
}
|
||||
@@ -1304,12 +1307,7 @@ public static class SkillService
|
||||
{
|
||||
var score = 0;
|
||||
if (!string.IsNullOrWhiteSpace(skill.Paths) && _activeConditionalSkillNames.Contains(skill.Name))
|
||||
score += 4;
|
||||
if (!skill.UserInvocable)
|
||||
score += 1;
|
||||
score += Math.Max(0, GetSkillSourcePriority(skill.SourceScope) / 200);
|
||||
if (skill.DisableModelInvocation)
|
||||
score += 1;
|
||||
score += 8;
|
||||
|
||||
var haystack = $"{skill.Name} {skill.Label} {skill.Description} {skill.WhenToUse}".ToLowerInvariant();
|
||||
foreach (var token in queryTokens)
|
||||
@@ -1320,7 +1318,16 @@ public static class SkillService
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(skill.WhenToUse)
|
||||
&& userText.Contains(skill.WhenToUse, StringComparison.OrdinalIgnoreCase))
|
||||
score += 3;
|
||||
score += 4;
|
||||
|
||||
// Small boosts are useful only after a real semantic/path match exists.
|
||||
if (score > 0)
|
||||
{
|
||||
if (!skill.UserInvocable)
|
||||
score += 1;
|
||||
if (skill.DisableModelInvocation)
|
||||
score += 1;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user