SQL 정적 분석과 PPT·HTML critic을 고도화하고 코드 탭 fallback 문맥을 보강
- SqlAnalysisService에 script intent, dependency, review focus 계산을 추가해 migration/seed/reporting SQL의 위험도와 검토 포인트를 더 정확히 안내하도록 개선했습니다. - HtmlSkill에 decision_matrix, metric_strip 섹션을 추가하고 ArtifactQualityReviewService/ArtifactRepairGuideService에서 board·strategy 문서의 의사결정 구조와 KPI 연결 부족을 더 정밀하게 진단하도록 강화했습니다. - DeckQualityReviewService와 DeckRepairGuideService를 확장해 executive summary headline, comparison trade-off, roadmap milestone, chart takeaway, KPI context 부족을 추가로 감지하고 보정 가이드를 반환하도록 정리했습니다. - WorkspaceContextGenerator와 CodeLanguageCatalog를 업데이트해 SQL 저장소에서 SQL Review Focus와 확장된 workflow summary를 제공하도록 맞췄고, README/DEVELOPMENT/NEXT_ROADMAP에 2026-04-15 11:36 (KST) 기준 이력을 반영했습니다. 검증 결과 - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_code_sql_doc_final\\ -p:IntermediateOutputPath=obj\\verify_code_sql_doc_final\\ : 경고 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_final_tests\\ -p:IntermediateOutputPath=obj\\verify_code_sql_doc_final_tests\\ : 통과 62
This commit is contained in:
@@ -16,7 +16,9 @@ public class ArtifactQualityReviewServiceTests
|
||||
<div class="callout-info">Important message</div>
|
||||
<table><tr><th>Metric</th><th>Value</th></tr><tr><td>NPS</td><td>61</td></tr></table>
|
||||
<div class="kpi-panel"></div>
|
||||
<div class="metric-strip"></div>
|
||||
<div class="comparison-grid"></div>
|
||||
<div class="decision-matrix"></div>
|
||||
<div class="roadmap-block"></div>
|
||||
<div class="decision-summary"></div>
|
||||
<section class="board-report-panel"></section>
|
||||
@@ -32,6 +34,7 @@ public class ArtifactQualityReviewServiceTests
|
||||
strength.Contains("cover", StringComparison.OrdinalIgnoreCase) ||
|
||||
strength.Contains("contents", StringComparison.OrdinalIgnoreCase));
|
||||
review.Strengths.Should().Contain(strength => strength.Contains("board-ready", StringComparison.OrdinalIgnoreCase));
|
||||
review.Strengths.Should().Contain(strength => strength.Contains("decision matrix", StringComparison.OrdinalIgnoreCase));
|
||||
review.Issues.Should().NotContain(issue => issue.Severity == ArtifactReviewSeverity.Critical);
|
||||
}
|
||||
|
||||
@@ -183,4 +186,24 @@ public class ArtifactQualityReviewServiceTests
|
||||
review.Issues.Should().Contain(issue => issue.Message.Contains("evidence table", StringComparison.OrdinalIgnoreCase));
|
||||
review.Issues.Should().Contain(issue => issue.Message.Contains("callout or highlight blocks", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReviewHtml_ShouldRecommendDecisionMatrix_WhenTradeoffViewIsMissing()
|
||||
{
|
||||
var html =
|
||||
"""
|
||||
<h2>Executive Summary</h2><p>Summary text.</p><p>Supporting paragraph.</p>
|
||||
<section class="board-report-panel"></section>
|
||||
<section class="strategy-brief-panel"></section>
|
||||
<div class="decision-summary"></div>
|
||||
<div class="metric-strip"></div>
|
||||
<h2>Recommendation</h2><p>Recommendation text.</p><p>More detail.</p>
|
||||
<h2>Appendix</h2><p>Reference detail.</p><p>More detail.</p>
|
||||
""";
|
||||
|
||||
var review = ArtifactQualityReviewService.ReviewHtml("Tradeoff Gap", html, hasCover: true, hasTableOfContents: true, printReady: true);
|
||||
|
||||
review.Issues.Should().Contain(issue => issue.Message.Contains("comparison or decision matrix", StringComparison.OrdinalIgnoreCase));
|
||||
review.Issues.Should().Contain(issue => issue.Message.Contains("decision matrix", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ public class ArtifactRepairGuideServiceTests
|
||||
[
|
||||
new ArtifactReviewIssue("Board-ready report should include a decision summary block.", ArtifactReviewSeverity.Warning),
|
||||
new ArtifactReviewIssue("Board-ready report would be stronger with a KPI panel or metric strip.", ArtifactReviewSeverity.Info),
|
||||
new ArtifactReviewIssue("Strategy brief should include explicit decisions or a decision summary block.", ArtifactReviewSeverity.Warning)
|
||||
new ArtifactReviewIssue("Strategy brief should include explicit decisions or a decision summary block.", ArtifactReviewSeverity.Warning),
|
||||
new ArtifactReviewIssue("Board-ready report would benefit from a comparison or decision matrix block.", ArtifactReviewSeverity.Info)
|
||||
]);
|
||||
|
||||
var guide = ArtifactRepairGuideService.BuildGuide(review);
|
||||
@@ -45,6 +46,7 @@ public class ArtifactRepairGuideServiceTests
|
||||
guide.Should().Contain("decision summary");
|
||||
guide.Should().Contain("KPI");
|
||||
guide.Should().Contain("strategy brief");
|
||||
guide.Should().Contain("decision matrix");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -103,5 +103,7 @@ public class CodeLanguageCatalogTests
|
||||
summary.Should().Contain("schema.sql");
|
||||
summary.Should().Contain("disposable database");
|
||||
summary.Should().Contain("dialect");
|
||||
summary.Should().Contain("migration order");
|
||||
summary.Should().Contain("dependencies");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,4 +125,46 @@ public class DeckQualityReviewServiceTests
|
||||
|
||||
review.Issues.Should().Contain(issue => issue.Message.Contains("supporting rationale or next steps", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReviewDeck_ShouldFlagComparisonTradeoffsAndKpiContext()
|
||||
{
|
||||
using var slides = JsonDocument.Parse(
|
||||
"""
|
||||
[
|
||||
{ "layout": "title", "title": "Board Review" },
|
||||
{
|
||||
"layout": "executive_summary",
|
||||
"title": "Executive Summary",
|
||||
"summary_points": ["A", "B"],
|
||||
"recommendation": "Proceed"
|
||||
},
|
||||
{
|
||||
"layout": "comparison",
|
||||
"title": "Options",
|
||||
"headline": "Choose a path",
|
||||
"options": [
|
||||
{ "name": "A", "verdict": "Recommended" },
|
||||
{ "name": "B", "verdict": "Fallback" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "kpi_dashboard",
|
||||
"title": "KPIs",
|
||||
"headline": "Performance snapshot",
|
||||
"kpis": [
|
||||
{ "label": "Margin", "value": "+2.4pt" },
|
||||
{ "label": "Lead Time", "value": "-12%" },
|
||||
{ "label": "Quality", "value": "96%" }
|
||||
]
|
||||
}
|
||||
]
|
||||
""");
|
||||
|
||||
var review = DeckQualityReviewService.ReviewDeck("Thin Signals", slides.RootElement, hasTemplate: false, autoRepairCount: 0);
|
||||
|
||||
review.Issues.Should().Contain(issue => issue.Message.Contains("sharper single-message headline", StringComparison.OrdinalIgnoreCase));
|
||||
review.Issues.Should().Contain(issue => issue.Message.Contains("trade-offs behind the verdict", StringComparison.OrdinalIgnoreCase));
|
||||
review.Issues.Should().Contain(issue => issue.Message.Contains("trend or note context", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,23 @@ public class HtmlSkillConsultingSectionsTests
|
||||
{ "name": "Option A", "summary": "Fast rollout", "pros": "Low setup effort", "cons": "Limited scale", "verdict": "Fast" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "decision_matrix",
|
||||
"title": "Decision Matrix",
|
||||
"criteria": ["Speed", "Risk", "Margin"],
|
||||
"options": [
|
||||
{ "name": "Option A", "verdict": "Fast", "notes": "Low setup effort", "scores": { "Speed": 5, "Risk": 2, "Margin": 3 } },
|
||||
{ "name": "Option B", "verdict": "Recommended", "notes": "Balanced path", "scores": { "Speed": 4, "Risk": 4, "Margin": 4 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "metric_strip",
|
||||
"title": "Top-Line Metrics",
|
||||
"items": [
|
||||
{ "label": "Margin", "value": "+4.2pt", "trend": "up", "note": "pilot mix" },
|
||||
{ "label": "Lead Time", "value": "-18%", "trend": "down", "note": "handoff reduction" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "roadmap",
|
||||
"title": "Delivery Roadmap",
|
||||
@@ -108,6 +125,8 @@ public class HtmlSkillConsultingSectionsTests
|
||||
result.Output.Should().Contain("Repair guide:");
|
||||
var html = File.ReadAllText(Path.Combine(workDir, "consulting.html"));
|
||||
html.Should().Contain("comparison-grid");
|
||||
html.Should().Contain("decision-matrix");
|
||||
html.Should().Contain("metric-strip");
|
||||
html.Should().Contain("roadmap-block");
|
||||
html.Should().Contain("matrix-grid");
|
||||
html.Should().Contain("decision-summary");
|
||||
|
||||
@@ -14,6 +14,7 @@ public class SqlAnalysisServiceTests
|
||||
"""
|
||||
BEGIN TRANSACTION;
|
||||
CREATE TABLE orders (id SERIAL PRIMARY KEY, customer_id INT);
|
||||
CREATE VIEW order_summary AS SELECT customer_id FROM customer_dim;
|
||||
UPDATE orders SET customer_id = 0;
|
||||
DROP TABLE legacy_orders;
|
||||
COMMIT;
|
||||
@@ -22,14 +23,17 @@ public class SqlAnalysisServiceTests
|
||||
var report = SqlAnalysisService.Analyze(sql, "20260415_orders_migration.sql");
|
||||
|
||||
report.Dialect.Should().Be("PostgreSQL");
|
||||
report.ScriptIntent.Should().Be("schema migration");
|
||||
report.StatementKinds.Should().Contain("CREATE TABLE");
|
||||
report.StatementKinds.Should().Contain("UPDATE");
|
||||
report.StatementKinds.Should().Contain("DROP TABLE");
|
||||
report.Objects.Should().Contain("orders");
|
||||
report.Objects.Should().Contain("legacy_orders");
|
||||
report.Dependencies.Should().Contain("customer_dim");
|
||||
report.Risks.Should().Contain(risk => risk.Contains("UPDATE statement without WHERE", StringComparison.OrdinalIgnoreCase));
|
||||
report.Risks.Should().Contain(risk => risk.Contains("Destructive DROP", StringComparison.OrdinalIgnoreCase));
|
||||
report.SuggestedChecks.Should().Contain(check => check.Contains("rollback", StringComparison.OrdinalIgnoreCase));
|
||||
report.ReviewNotes.Should().Contain(note => note.Contains("rollback", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -48,6 +52,8 @@ public class SqlAnalysisServiceTests
|
||||
summary.Should().Contain("CREATE TABLE");
|
||||
summary.Should().Contain("DELETE");
|
||||
summary.Should().Contain("users");
|
||||
summary.Should().Contain("script:");
|
||||
summary.Should().Contain("review focus:");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -55,4 +61,24 @@ public class SqlAnalysisServiceTests
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Analyze_ShouldDetectSeedIntentDependenciesAndTransactionRisk()
|
||||
{
|
||||
const string sql =
|
||||
"""
|
||||
INSERT INTO seed_status (code, label) VALUES ('ACTIVE', 'Active');
|
||||
UPDATE users
|
||||
SET status_code = map.code
|
||||
FROM account_status_map map
|
||||
WHERE users.status_code IS NULL;
|
||||
""";
|
||||
|
||||
var report = SqlAnalysisService.Analyze(sql, "20260415_seed_status.sql");
|
||||
|
||||
report.ScriptIntent.Should().Be("seed / reference data");
|
||||
report.Dependencies.Should().Contain("account_status_map");
|
||||
report.ReviewNotes.Should().Contain(note => note.Contains("idempotent", StringComparison.OrdinalIgnoreCase));
|
||||
report.Risks.Should().Contain(risk => risk.Contains("transaction", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +339,29 @@ public class WorkspaceContextGeneratorTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateAsync_IncludesSqlReviewFocus_WhenWorkspaceContainsSqlScripts()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
try
|
||||
{
|
||||
File.WriteAllText(Path.Combine(tempDir, "001_init.sql"), "CREATE TABLE users (id INT);");
|
||||
File.WriteAllText(Path.Combine(tempDir, "002_seed.sql"), "INSERT INTO users (id) VALUES (1);");
|
||||
File.WriteAllText(Path.Combine(tempDir, "003_view.sql"), "CREATE VIEW active_users AS SELECT * FROM users;");
|
||||
|
||||
var result = await WorkspaceContextGenerator.GenerateAsync(tempDir);
|
||||
|
||||
result.Should().Contain("## SQL Review Focus");
|
||||
result.Should().Contain("migration, seed, reporting query");
|
||||
result.Should().Contain("rollback");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetectLanguageWorkflowHints_ShouldPreferSelectedLanguage()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user