SQL 리뷰 계층과 AgentLoop 응답 분해 helper를 추가해 코드 탭 마감 품질을 높임

- SqlReviewService를 추가해 SQL fallback 결과에 review severity, key findings, review checklist를 붙이고 schema migration, seed/reference data, reporting query마다 다른 검토 포인트를 안내하도록 확장했습니다.
- SqlAnalysisService와 CodeLanguageCatalog를 업데이트해 SQL fallback summary와 workflow summary가 rollback notes, dependency order, row-count guard 같은 리뷰 힌트를 직접 포함하도록 보강했습니다.
- AgentLoopResponseClassificationService를 추가해 LLM 응답에서 text/tool_use 분리, no-tool 연속 카운트 계산, thinking summary 생성을 helper로 분리했고 AgentLoopService 본체는 해당 helper를 사용하도록 정리했습니다.
- README, docs/DEVELOPMENT.md, docs/NEXT_ROADMAP.md에 2026-04-15 11:50 (KST) 기준 이력을 반영했습니다.

검증 결과
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_loop_sql_finalize\\ -p:IntermediateOutputPath=obj\\verify_loop_sql_finalize\\ : 경고 0 / 오류 0
- dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AgentLoopResponseClassificationServiceTests|AgentLoopLlmRequestPreparationServiceTests|AgentLoopIterationPreparationServiceTests|SqlAnalysisServiceTests|SqlReviewServiceTests|CodeLanguageCatalogTests|WorkspaceContextGeneratorTests" -p:OutputPath=bin\\verify_loop_sql_finalize_tests\\ -p:IntermediateOutputPath=obj\\verify_loop_sql_finalize_tests\\ : 통과 48
This commit is contained in:
2026-04-15 11:51:42 +09:00
parent 2a49b1da24
commit 717d0f2143
12 changed files with 354 additions and 36 deletions

View File

@@ -0,0 +1,42 @@
using AxCopilot.Services;
using AxCopilot.Services.Agent;
using FluentAssertions;
using Xunit;
namespace AxCopilot.Tests.Services;
public class AgentLoopResponseClassificationServiceTests
{
[Fact]
public void Classify_ShouldSplitTextAndToolCalls_AndResetNoToolCounter()
{
var blocks = new List<ContentBlock>
{
new() { Type = "text", Text = "I will inspect the repository." },
new() { Type = "tool_use", ToolName = "file_read", ToolId = "tool-1" }
};
var result = AgentLoopResponseClassificationService.Classify(blocks, consecutiveNoToolResponses: 2);
result.TextResponse.Should().Contain("inspect the repository");
result.ToolCalls.Should().HaveCount(1);
result.NextConsecutiveNoToolResponses.Should().Be(0);
result.BuildThinkingSummary().Should().Contain("inspect the repository");
}
[Fact]
public void Classify_ShouldIncrementNoToolCounter_WhenOnlyTextBlocksArePresent()
{
var blocks = new List<ContentBlock>
{
new() { Type = "text", Text = "Plan step one." },
new() { Type = "text", Text = "Plan step two." }
};
var result = AgentLoopResponseClassificationService.Classify(blocks, consecutiveNoToolResponses: 1);
result.ToolCalls.Should().BeEmpty();
result.TextParts.Should().HaveCount(2);
result.NextConsecutiveNoToolResponses.Should().Be(2);
}
}

View File

@@ -105,5 +105,6 @@ public class CodeLanguageCatalogTests
summary.Should().Contain("dialect");
summary.Should().Contain("migration order");
summary.Should().Contain("dependencies");
summary.Should().Contain("rollback");
}
}

View File

@@ -54,6 +54,8 @@ public class SqlAnalysisServiceTests
summary.Should().Contain("users");
summary.Should().Contain("script:");
summary.Should().Contain("review focus:");
summary.Should().Contain("review severity:");
summary.Should().Contain("review checklist:");
}
finally
{

View File

@@ -0,0 +1,50 @@
using AxCopilot.Services;
using FluentAssertions;
using Xunit;
namespace AxCopilot.Tests.Services;
public class SqlReviewServiceTests
{
[Fact]
public void Review_ShouldMarkDestructiveMigrationAsHighSeverity()
{
var report = new SqlAnalysisReport(
"PostgreSQL",
"schema migration",
["ALTER TABLE", "DROP TABLE"],
["orders", "legacy_orders"],
["customer_dim"],
["Destructive DROP statement is present."],
["Run the script in a disposable database and confirm rollback strategy first."],
["Review forward migration order and any missing rollback note."]);
var review = SqlReviewService.Review(report);
review.Severity.Should().Be("high");
review.Findings.Should().Contain(f => f.Contains("schema change", StringComparison.OrdinalIgnoreCase)
|| f.Contains("rollback", StringComparison.OrdinalIgnoreCase));
review.Checklist.Should().Contain(c => c.Contains("rollback", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void Review_ShouldRecommendIdempotencyChecksForSeedScript()
{
var report = new SqlAnalysisReport(
"Generic SQL",
"seed / reference data",
["INSERT", "UPDATE"],
["seed_status", "users"],
["account_status_map"],
["Explicit transaction boundaries are not visible in the script."],
["Confirm the script is safe to rerun and that seed data remains idempotent."],
["Verify the script is idempotent before rerunning seed data."]);
var review = SqlReviewService.Review(report);
review.Severity.Should().Be("medium");
review.Findings.Should().Contain(f => f.Contains("idempotent", StringComparison.OrdinalIgnoreCase));
review.Checklist.Should().Contain(c => c.Contains("rerun", StringComparison.OrdinalIgnoreCase)
|| c.Contains("idempotent", StringComparison.OrdinalIgnoreCase));
}
}