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

@@ -2022,3 +2022,16 @@ MIT License
- 검증:
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_code_sql_doc\\ -p:IntermediateOutputPath=obj\\verify_code_sql_doc\\` 경고 0 / 오류 0
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "SqlDialectDetectorTests|SqlAnalysisServiceTests|CodeLanguageCatalogTests|WorkspaceContextGeneratorTests|ArtifactQualityReviewServiceTests|ArtifactRepairGuideServiceTests|DeckQualityReviewServiceTests|HtmlSkillConsultingSectionsTests" -p:OutputPath=bin\\verify_code_sql_doc_tests\\ -p:IntermediateOutputPath=obj\\verify_code_sql_doc_tests\\` 통과 62
업데이트: 2026-04-15 11:50 (KST)
- SQL을 단순 정적 요약에서 `전용 리뷰` 단계로 더 끌어올렸습니다. 새 [SqlReviewService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/SqlReviewService.cs)는 `severity`, `key findings`, `review checklist`를 계산해 schema migration, seed/reference data, reporting query마다 다른 검토 포인트와 rollback·row-count·dependency 체크리스트를 fallback 결과에 붙입니다.
- [SqlAnalysisService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/SqlAnalysisService.cs)는 SQL fallback summary 끝에 위 리뷰 결과를 함께 출력하도록 확장됐고, [CodeLanguageCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/CodeLanguageCatalog.cs)는 SQL workflow summary에 `review` 힌트를 추가해 rollback notes, dependency order, row-count guard를 더 명시적으로 안내합니다.
- 에이전틱 루프 본체도 한 단계 더 가벼워졌습니다. 새 [AgentLoopResponseClassificationService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentLoopResponseClassificationService.cs)는 LLM 응답 블록에서 `text/tool_use` 분리, no-tool 연속 카운트 계산, thinking summary 생성을 담당하고, [AgentLoopService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentLoopService.cs)는 이 helper를 통해 응답 해석 책임을 줄였습니다.
- 테스트:
- [SqlReviewServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/SqlReviewServiceTests.cs)
- [AgentLoopResponseClassificationServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/AgentLoopResponseClassificationServiceTests.cs)
- [SqlAnalysisServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/SqlAnalysisServiceTests.cs)
- [CodeLanguageCatalogTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/CodeLanguageCatalogTests.cs)
- 검증:
- `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

View File

@@ -1247,3 +1247,38 @@ UI ?붿옄???€洹쒕え 由ы뙥?좊쭅 ???꾪뿕 ?묒뾽 ??湲곕줉???덉쟾
### 검증
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_code_sql_doc\\ -p:IntermediateOutputPath=obj\\verify_code_sql_doc\\` 경고 0 / 오류 0
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "SqlDialectDetectorTests|SqlAnalysisServiceTests|CodeLanguageCatalogTests|WorkspaceContextGeneratorTests|ArtifactQualityReviewServiceTests|ArtifactRepairGuideServiceTests|DeckQualityReviewServiceTests|HtmlSkillConsultingSectionsTests" -p:OutputPath=bin\\verify_code_sql_doc_tests\\ -p:IntermediateOutputPath=obj\\verify_code_sql_doc_tests\\` 통과 62
업데이트: 2026-04-15 11:50 (KST)
### SQL review 계층 추가
- 새 `SqlReviewService.cs`
- `SqlReviewResult`를 도입해 `review severity`, `key findings`, `review checklist`를 구조화했습니다.
- schema migration/schema change는 migration sequencing, dependent object readiness를 우선 체크합니다.
- seed/reference data는 rerun safety와 idempotent upsert 관점을 별도 체크합니다.
- query/reporting SQL은 join width와 downstream consumer 영향 검토 포인트를 추가합니다.
- destructive DDL, broad DML, unclear transaction scope, wildcard projection을 findings/checklist로 변환합니다.
- `SqlAnalysisService.cs`
- `BuildFallbackSummary()`가 `SqlReviewService.Review(report)` 결과를 이어붙여 SQL fallback을 `analysis + review` 2단 구조로 반환하도록 변경했습니다.
- `CodeLanguageCatalog.cs`
- SQL workflow summary에 `review: confirm rollback notes, dependency order, and row-count guards before apply` 힌트를 추가했습니다.
### AgentLoop 응답 분해 helper 추가
- 새 `AgentLoopResponseClassificationService.cs`
- LLM 응답 블록을 `TextResponse`, `TextParts`, `ToolCalls`, `NextConsecutiveNoToolResponses`로 분류합니다.
- `BuildThinkingSummary()`를 제공해 thinking preview 길이 제한도 helper에서 처리합니다.
- `AgentLoopService.cs`
- 수동 `text/tool_use` 분리 루프를 제거하고 `AgentLoopResponseClassificationService.Classify()`를 사용하도록 정리했습니다.
- no-tool 응답 누적 카운트와 thinking summary 생성 책임을 helper 호출로 대체했습니다.
### 테스트
- 새 `SqlReviewServiceTests.cs`
- destructive migration은 `high` severity와 rollback checklist를 반환하는지 검증
- seed/reference data는 idempotency와 rerun safety 체크리스트를 반환하는지 검증
- 새 `AgentLoopResponseClassificationServiceTests.cs`
- text/tool_use 분리와 no-tool counter reset/increment 동작 검증
- 기존 `SqlAnalysisServiceTests.cs`, `CodeLanguageCatalogTests.cs`
- SQL fallback summary에 `review severity`, `review checklist`, rollback review 힌트가 들어가는지 회귀 검증
### 검증
- `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

View File

@@ -210,3 +210,10 @@
1. SQL fallback은 `dialect/statement/risk` 수준을 넘어 `script intent/dependency/review focus`까지 확장됐습니다. 다음 선택지는 dialect별 migration lint, schema dependency graph, rollback 시뮬레이션처럼 더 깊은 전용 리뷰 계층입니다.
2. HTML 문서는 `decision_matrix``metric_strip`이 들어오면서 board/strategy 보고서의 의사결정 구조를 더 직접적으로 표현할 수 있게 됐습니다. 남은 작업은 bundled skill을 목적형으로 더 쪼개고 print/export polish를 마감하는 수준입니다.
3. PPT critic은 headline, trade-off, phase milestone, chart takeaway, KPI context를 세밀하게 보기 시작했습니다. 이후 남는 작업은 finance/sales/board fixture 확대와 slide-level auto-repair 정교화처럼 golden 마감 중심입니다.
업데이트: 2026-04-15 11:50 (KST)
### 추가 진행 메모
1. SQL은 이제 fallback 요약에 별도 `review severity / findings / checklist`가 붙는 수준까지 올라왔습니다. 남은 고도화는 dialect별 migration lint, schema dependency graph, rollback simulation 같은 더 깊은 검증 계층입니다.
2. Agent loop는 응답 분해 helper가 추가되어 `RunAsync` 본체가 한 단계 더 얇아졌습니다. 이후 분리 후보는 tool dispatch와 finalize 쪽이라, 구조적 마감은 거의 끝나가고 있습니다.
3. 문서 쪽은 기능 확장보다 `golden fixture 확대`, `목적형 bundled skill`, `print/export polish`처럼 완성도 마감 항목이 중심으로 남았습니다.

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));
}
}

View File

@@ -0,0 +1,52 @@
using AxCopilot.Services;
namespace AxCopilot.Services.Agent;
internal sealed record AgentLoopResponseClassificationResult(
string TextResponse,
IReadOnlyList<string> TextParts,
List<ContentBlock> ToolCalls,
int NextConsecutiveNoToolResponses)
{
public string BuildThinkingSummary(int maxLength = 150)
{
if (string.IsNullOrEmpty(TextResponse))
return string.Empty;
return TextResponse.Length > maxLength
? TextResponse[..maxLength] + "…"
: TextResponse;
}
}
/// <summary>
/// LLM 응답 블록을 텍스트와 tool_use로 분리하고, 무도구 응답 연속 횟수를 계산한다.
/// </summary>
internal static class AgentLoopResponseClassificationService
{
public static AgentLoopResponseClassificationResult Classify(
IReadOnlyList<ContentBlock> blocks,
int consecutiveNoToolResponses)
{
var textParts = new List<string>();
var toolCalls = new List<ContentBlock>();
foreach (var block in blocks)
{
if (block.Type == "text" && !string.IsNullOrWhiteSpace(block.Text))
textParts.Add(block.Text);
else if (block.Type == "tool_use")
toolCalls.Add(block);
}
var nextConsecutiveNoToolResponses = toolCalls.Count == 0
? consecutiveNoToolResponses + 1
: 0;
return new AgentLoopResponseClassificationResult(
string.Join("\n", textParts),
textParts,
toolCalls,
nextConsecutiveNoToolResponses);
}
}

View File

@@ -726,21 +726,12 @@ public partial class AgentLoopService
return $"⚠ LLM 오류: {ex.Message}";
}
// 응답에서 텍스트와 도구 호출 분리
var textParts = new List<string>();
var toolCalls = new List<ContentBlock>();
foreach (var block in blocks)
{
if (block.Type == "text" && !string.IsNullOrWhiteSpace(block.Text))
textParts.Add(block.Text);
else if (block.Type == "tool_use")
toolCalls.Add(block);
}
// 텍스트 부분
var textResponse = string.Join("\n", textParts);
consecutiveNoToolResponses = toolCalls.Count == 0 ? consecutiveNoToolResponses + 1 : 0;
var responseClassification = AgentLoopResponseClassificationService.Classify(
blocks,
consecutiveNoToolResponses);
var textResponse = responseClassification.TextResponse;
var toolCalls = responseClassification.ToolCalls;
consecutiveNoToolResponses = responseClassification.NextConsecutiveNoToolResponses;
// 워크플로우 상세 로그: LLM 응답
WorkflowLogService.LogLlmResponse(_conversationId, _currentRunId, iteration,
@@ -804,9 +795,7 @@ public partial class AgentLoopService
// Thinking UI: 텍스트 응답 중 도구 호출이 있으면 "사고 과정"으로 표시
if (!string.IsNullOrEmpty(textResponse) && toolCalls.Count > 0)
{
var thinkingSummary = textResponse.Length > 150
? textResponse[..150] + "…"
: textResponse;
var thinkingSummary = responseClassification.BuildThinkingSummary();
EmitEvent(AgentEventType.Thinking, "", thinkingSummary);
}

View File

@@ -442,7 +442,10 @@ public static class CodeLanguageCatalog
if (!string.IsNullOrWhiteSpace(primaryGuidance))
parts.Add("focus: " + primaryGuidance);
if (string.Equals(capability.Key, "sql", StringComparison.OrdinalIgnoreCase))
{
parts.Add("analysis: detect dialect, script intent, destructive risk, migration order, and object dependencies");
parts.Add("review: confirm rollback notes, dependency order, and row-count guards before apply");
}
if (parts.Count == 0)
return capability.DisplayName;

View File

@@ -57,29 +57,34 @@ public static class SqlAnalysisService
public static string BuildFallbackSummary(string? filePathOrExtension)
{
SqlAnalysisReport report;
if (!string.IsNullOrWhiteSpace(filePathOrExtension) && File.Exists(filePathOrExtension))
{
var sql = File.ReadAllText(filePathOrExtension);
return Analyze(sql, filePathOrExtension).ToFallbackSummary();
report = Analyze(sql, filePathOrExtension);
}
else
{
report = new SqlAnalysisReport(
"Generic SQL",
"generic script",
["DDL/DML review"],
[],
[],
["Review destructive statements and transaction boundaries before execution."],
[
"Confirm migration order, object dependencies, and rollback strategy",
"Run the script in a disposable database before applying it to shared environments",
"Review broad UPDATE/DELETE predicates, indexes, and constraint impact"
],
[
"Identify whether the script is a migration, seed, or ad-hoc query before execution",
"Check dependency order for tables, views, indexes, and procedures",
"Prepare a rollback or recovery note for destructive or high-impact changes"
]);
}
return new SqlAnalysisReport(
"Generic SQL",
"generic script",
["DDL/DML review"],
[],
[],
["Review destructive statements and transaction boundaries before execution."],
[
"Confirm migration order, object dependencies, and rollback strategy",
"Run the script in a disposable database before applying it to shared environments",
"Review broad UPDATE/DELETE predicates, indexes, and constraint impact"
],
[
"Identify whether the script is a migration, seed, or ad-hoc query before execution",
"Check dependency order for tables, views, indexes, and procedures",
"Prepare a rollback or recovery note for destructive or high-impact changes"
]).ToFallbackSummary();
return report.ToFallbackSummary() + Environment.NewLine + SqlReviewService.Review(report).ToFallbackSummary();
}
private static IReadOnlyList<string> DetectStatementKinds(string sql)

View File

@@ -0,0 +1,119 @@
namespace AxCopilot.Services;
public sealed record SqlReviewResult(
string Severity,
IReadOnlyList<string> Findings,
IReadOnlyList<string> Checklist)
{
public string ToFallbackSummary()
{
var lines = new List<string> { $"review severity: {Severity}" };
if (Findings.Count > 0)
lines.Add("key findings: " + string.Join(" | ", Findings.Take(3)));
if (Checklist.Count > 0)
lines.Add("review checklist: " + string.Join(" | ", Checklist.Take(4)));
return string.Join(Environment.NewLine, lines);
}
}
public static class SqlReviewService
{
public static SqlReviewResult Review(SqlAnalysisReport report)
{
var findings = new List<string>();
var checklist = new List<string>();
if (string.Equals(report.ScriptIntent, "schema migration", StringComparison.OrdinalIgnoreCase) ||
string.Equals(report.ScriptIntent, "schema change", StringComparison.OrdinalIgnoreCase))
{
findings.Add("Treat the script as an ordered schema change and verify migration sequencing.");
checklist.Add("Apply the script in migration order and confirm dependent views, indexes, and procedures are ready.");
}
if (string.Equals(report.ScriptIntent, "seed / reference data", StringComparison.OrdinalIgnoreCase))
{
findings.Add("Seed/reference data should remain idempotent across reruns.");
checklist.Add("Confirm rerun safety and expected upsert behavior for reference rows.");
}
if (string.Equals(report.ScriptIntent, "query / reporting", StringComparison.OrdinalIgnoreCase))
{
findings.Add("Reporting/query SQL should be validated for read scope, join width, and downstream consumers.");
checklist.Add("Check result-set width, join selectivity, and whether the query feeds dashboards or extracts.");
}
foreach (var risk in report.Risks)
{
if (risk.Contains("DROP", StringComparison.OrdinalIgnoreCase) ||
risk.Contains("TRUNCATE", StringComparison.OrdinalIgnoreCase))
{
findings.Add("Destructive DDL is present and should be paired with rollback or recovery notes.");
checklist.Add("Capture backup or rollback evidence before running destructive DDL.");
}
else if (risk.Contains("without WHERE", StringComparison.OrdinalIgnoreCase))
{
findings.Add("Broad data change risk is present and row-count expectations must be reviewed.");
checklist.Add("Validate affected row counts and predicates before executing broad UPDATE/DELETE statements.");
}
else if (risk.Contains("transaction", StringComparison.OrdinalIgnoreCase))
{
findings.Add("Transaction scope is unclear for a mutating script.");
checklist.Add("Define explicit transaction boundaries or document why the script is safe without them.");
}
else if (risk.Contains("SELECT *", StringComparison.OrdinalIgnoreCase))
{
findings.Add("Wildcard projection may widen downstream contract unexpectedly.");
checklist.Add("Replace SELECT * with explicit columns when the query feeds stable consumers.");
}
}
if (report.Dependencies.Count > 0)
{
findings.Add("Referenced dependencies should be checked before rollout: " +
string.Join(", ", report.Dependencies.Take(4)));
checklist.Add("Validate dependency order for referenced tables, views, functions, and lookup sources.");
}
foreach (var note in report.ReviewNotes)
{
if (note.Contains("rollback", StringComparison.OrdinalIgnoreCase))
checklist.Add("Document rollback or recovery notes alongside the change.");
if (note.Contains("lock", StringComparison.OrdinalIgnoreCase))
checklist.Add("Review locking, constraint, and index impact during rollout.");
if (note.Contains("derived database objects", StringComparison.OrdinalIgnoreCase))
checklist.Add("Revalidate derived objects and deployment order for procedures, views, and triggers.");
}
if (findings.Count == 0)
findings.Add("No high-risk SQL pattern was detected, but dependency and execution-order review is still recommended.");
if (checklist.Count == 0)
checklist.Add("Run the script in a disposable database and capture verification notes before shared rollout.");
var severity = DetermineSeverity(report);
return new SqlReviewResult(
severity,
findings.Distinct(StringComparer.OrdinalIgnoreCase).Take(4).ToList(),
checklist.Distinct(StringComparer.OrdinalIgnoreCase).Take(5).ToList());
}
private static string DetermineSeverity(SqlAnalysisReport report)
{
if (report.Risks.Any(risk =>
risk.Contains("DROP", StringComparison.OrdinalIgnoreCase) ||
risk.Contains("TRUNCATE", StringComparison.OrdinalIgnoreCase) ||
risk.Contains("without WHERE", StringComparison.OrdinalIgnoreCase)))
{
return "high";
}
if (string.Equals(report.ScriptIntent, "schema migration", StringComparison.OrdinalIgnoreCase) ||
string.Equals(report.ScriptIntent, "schema change", StringComparison.OrdinalIgnoreCase) ||
report.Dependencies.Count > 0 ||
report.Risks.Any(risk => risk.Contains("transaction", StringComparison.OrdinalIgnoreCase)))
{
return "medium";
}
return "low";
}
}