using AxCopilot.Models; namespace AxCopilot.Services.Agent; public partial class AgentLoopService { private void ApplyCodeQualityFollowUpTransition( ContentBlock call, ToolResult result, List messages, TaskTypePolicy taskPolicy, ref bool requireHighImpactCodeVerification, ref string? lastModifiedCodeFilePath) { var highImpactCodeChange = IsHighImpactCodeModification(ActiveTab ?? "", call.ToolName, result); requireHighImpactCodeVerification = highImpactCodeChange; if (highImpactCodeChange && ShouldInjectCodeQualityFollowUp(ActiveTab ?? "", call.ToolName, result)) { lastModifiedCodeFilePath = result.FilePath; messages.Add(new ChatMessage { Role = "user", Content = BuildCodeQualityFollowUpPrompt( call.ToolName, result, highImpactCodeChange, HasAnyBuildOrTestEvidence(messages), taskPolicy) }); EmitEvent(AgentEventType.Thinking, "", highImpactCodeChange ? "고영향 코드 변경으로 분류돼 참조 검증과 build/test 검증을 더 엄격하게 이어갑니다." : "코드 변경 후 build/test/diff 검증을 이어갑니다."); } else if (HasCodeVerificationEvidenceAfterLastModification(messages, requireHighImpactCodeVerification)) { requireHighImpactCodeVerification = false; } } private bool TryApplyCodeCompletionGateTransition( List messages, string? textResponse, TaskTypePolicy taskPolicy, bool requireHighImpactCodeVerification, int totalToolCalls, RunState runState, ModelExecutionProfileCatalog.ExecutionPolicy executionPolicy) { if (!string.Equals(ActiveTab, "Code", StringComparison.OrdinalIgnoreCase) || totalToolCalls <= 0) return false; var hasCodeVerificationEvidence = HasCodeVerificationEvidenceAfterLastModification( messages, requireHighImpactCodeVerification); var hasDiffEvidence = HasDiffEvidenceAfterLastModification(messages); var hasRecentBuildOrTestEvidence = HasBuildOrTestEvidenceAfterLastModification(messages); var hasSuccessfulBuildAndTestEvidence = HasSuccessfulBuildAndTestAfterLastModification(messages); var hasLightweightCompletionEvidence = requireHighImpactCodeVerification ? hasCodeVerificationEvidence : hasDiffEvidence || hasRecentBuildOrTestEvidence || hasCodeVerificationEvidence; if (executionPolicy.CodeVerificationGateMaxRetries > 0 && !(requireHighImpactCodeVerification ? hasCodeVerificationEvidence : hasLightweightCompletionEvidence) && runState.CodeVerificationGateRetry < executionPolicy.CodeVerificationGateMaxRetries) { runState.CodeVerificationGateRetry++; if (!string.IsNullOrEmpty(textResponse)) messages.Add(new ChatMessage { Role = "assistant", Content = textResponse }); messages.Add(new ChatMessage { Role = "user", Content = requireHighImpactCodeVerification ? "[System:CodeQualityGate] 공용/핵심 코드 변경 이후 검증 근거가 부족합니다. 종료하지 말고 file_read, grep/glob, git diff, build/test까지 확인한 뒤에만 마무리하세요." : "[System:CodeQualityGate] 마지막 코드 수정 이후 build/test/file_read/diff 근거가 부족합니다. 종료하지 말고 검증 근거를 보강한 뒤에만 마무리하세요." }); EmitEvent(AgentEventType.Thinking, "", requireHighImpactCodeVerification ? "핵심 코드 변경의 검증 근거가 부족해 추가 검증을 진행합니다..." : "코드 결과 검증 근거가 부족해 추가 검증을 진행합니다..."); return true; } if (requireHighImpactCodeVerification && executionPolicy.HighImpactBuildTestGateMaxRetries > 0 && !HasSuccessfulBuildAndTestAfterLastModification(messages) && runState.HighImpactBuildTestGateRetry < executionPolicy.HighImpactBuildTestGateMaxRetries) { runState.HighImpactBuildTestGateRetry++; if (!string.IsNullOrEmpty(textResponse)) messages.Add(new ChatMessage { Role = "assistant", Content = textResponse }); messages.Add(new ChatMessage { Role = "user", Content = "[System:HighImpactBuildTestGate] 핵심 코드 변경입니다. 종료하지 말고 build_run과 test_loop를 모두 실행해 성공 근거를 확보한 뒤에만 마무리하세요." }); EmitEvent(AgentEventType.Thinking, "", "핵심 변경이라 build+test 성공 근거를 모두 확보할 때까지 진행합니다..."); return true; } var hasBlockingCodeEvidenceGap = !(requireHighImpactCodeVerification ? hasCodeVerificationEvidence : hasLightweightCompletionEvidence) || (requireHighImpactCodeVerification && !hasSuccessfulBuildAndTestEvidence); var shouldRequestStructuredFinalReport = taskPolicy.IsReviewTask || requireHighImpactCodeVerification; if (executionPolicy.FinalReportGateMaxRetries > 0 && shouldRequestStructuredFinalReport && !hasBlockingCodeEvidenceGap && !HasSufficientFinalReportEvidence(textResponse, taskPolicy, requireHighImpactCodeVerification, messages) && runState.FinalReportGateRetry < executionPolicy.FinalReportGateMaxRetries) { runState.FinalReportGateRetry++; if (!string.IsNullOrEmpty(textResponse)) messages.Add(new ChatMessage { Role = "assistant", Content = textResponse }); messages.Add(new ChatMessage { Role = "user", Content = BuildFinalReportQualityPrompt(taskPolicy, requireHighImpactCodeVerification) }); EmitEvent(AgentEventType.Thinking, "", "최종 보고에 변경·검증·리스크 요약이 부족해 한 번 더 정리합니다..."); return true; } return false; } private bool TryApplyCodeDiffEvidenceGateTransition( List messages, string? textResponse, RunState runState, ModelExecutionProfileCatalog.ExecutionPolicy executionPolicy) { if (!string.Equals(ActiveTab, "Code", StringComparison.OrdinalIgnoreCase)) return false; if (executionPolicy.CodeDiffGateMaxRetries <= 0 || runState.CodeDiffGateRetry >= executionPolicy.CodeDiffGateMaxRetries) return false; if (HasDiffEvidenceAfterLastModification(messages) || HasBuildOrTestEvidenceAfterLastModification(messages)) return false; runState.CodeDiffGateRetry++; if (!string.IsNullOrEmpty(textResponse)) messages.Add(new ChatMessage { Role = "assistant", Content = textResponse }); messages.Add(new ChatMessage { Role = "user", Content = "[System:CodeDiffGate] 코드 변경 이후 diff 근거가 부족합니다. git_tool 도구로 변경 파일과 핵심 diff를 먼저 확인하고 요약하세요. 지금 즉시 git_tool 도구를 호출하세요." }); EmitEvent(AgentEventType.Thinking, "", "코드 diff 근거가 부족해 git diff 검증을 추가합니다..."); return true; } private bool TryApplyRecentExecutionEvidenceGateTransition( List messages, string? textResponse, TaskTypePolicy taskPolicy, RunState runState, ModelExecutionProfileCatalog.ExecutionPolicy executionPolicy) { if (!string.Equals(ActiveTab, "Code", StringComparison.OrdinalIgnoreCase)) return false; if (executionPolicy.RecentExecutionGateMaxRetries <= 0 || runState.RecentExecutionGateRetry >= executionPolicy.RecentExecutionGateMaxRetries) return false; if (HasDiffEvidenceAfterLastModification(messages)) return false; if (!HasAnyBuildOrTestEvidence(messages)) return false; if (HasBuildOrTestEvidenceAfterLastModification(messages)) return false; runState.RecentExecutionGateRetry++; if (!string.IsNullOrEmpty(textResponse)) messages.Add(new ChatMessage { Role = "assistant", Content = textResponse }); messages.Add(new ChatMessage { Role = "user", Content = BuildRecentExecutionEvidencePrompt(taskPolicy) }); EmitEvent(AgentEventType.Thinking, "", "최근 수정 이후 실행 근거가 부족해 build/test 재검증을 수행합니다..."); return true; } private bool TryApplyExecutionSuccessGateTransition( List messages, string? textResponse, TaskTypePolicy taskPolicy, RunState runState, ModelExecutionProfileCatalog.ExecutionPolicy executionPolicy) { if (!string.Equals(ActiveTab, "Code", StringComparison.OrdinalIgnoreCase)) return false; if (executionPolicy.ExecutionSuccessGateMaxRetries <= 0 || runState.ExecutionSuccessGateRetry >= executionPolicy.ExecutionSuccessGateMaxRetries) return false; if (HasDiffEvidenceAfterLastModification(messages)) return false; if (!HasAnyBuildOrTestAttempt(messages)) return false; if (HasAnyBuildOrTestEvidence(messages)) return false; runState.ExecutionSuccessGateRetry++; if (!string.IsNullOrEmpty(textResponse)) messages.Add(new ChatMessage { Role = "assistant", Content = textResponse }); messages.Add(new ChatMessage { Role = "user", Content = BuildExecutionSuccessGatePrompt(taskPolicy) }); EmitEvent(AgentEventType.Thinking, "", "실패한 실행 근거만 있어 build/test 성공 결과를 다시 검증합니다..."); return true; } private bool TryApplyTerminalEvidenceGateTransition( List messages, string? textResponse, TaskTypePolicy taskPolicy, string userQuery, int totalToolCalls, string? lastArtifactFilePath, RunState runState, int retryMax) { if (totalToolCalls <= 0) return false; if (ShouldSkipTerminalEvidenceGateForAnalysisQuery(userQuery, taskPolicy)) return false; if (runState.TerminalEvidenceGateRetry >= retryMax) return false; if (HasTerminalProgressEvidence(taskPolicy, messages, lastArtifactFilePath)) return false; runState.TerminalEvidenceGateRetry++; if (!string.IsNullOrEmpty(textResponse)) messages.Add(new ChatMessage { Role = "assistant", Content = textResponse }); messages.Add(new ChatMessage { Role = "user", Content = BuildTerminalEvidenceGatePrompt(taskPolicy, lastArtifactFilePath) }); EmitEvent(AgentEventType.Thinking, "", $"종료 전 실행 증거가 부족해 보강 단계를 진행합니다 ({runState.TerminalEvidenceGateRetry}/{retryMax})"); return true; } }