문서 품질 critic와 golden 회귀를 고도화해 PPTX·HTML·DOCX·XLSX 마감 품질을 강화한다

- ArtifactQualityReviewService를 확장해 HTML의 board/strategy decision gap, DOCX evidence table·callout 부족, XLSX dashboard trend·variance·sheet summary·headline tile 부족을 개별 이슈로 판정하도록 보강

- ArtifactRepairGuideService와 DeckRepairGuideService를 보강해 decision summary, next steps, evidence table, trend/variance, dashboard tile 등 실제 보강 행동을 바로 제안하는 repair guide를 반환하도록 정리

- ExcelSkill review 입력을 세분화하고 Html/PPT golden 테스트를 strategy brief, PMO steering 시나리오까지 확대해 문서군 golden 회귀 범위를 넓힘

- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_finish_batch\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch\\ / dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal (ArtifactQualityReviewServiceTests, ArtifactRepairGuideServiceTests, DeckQualityReviewServiceTests, DeckRepairGuideServiceTests, HtmlSkillGoldenReportTests, PptxSkillGoldenDeckTests, DocxSkillGoldenDocumentTests, ExcelSkillGoldenWorkbookTests) -p:OutputPath=bin\\verify_doc_finish_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch_tests\
This commit is contained in:
2026-04-15 10:25:44 +09:00
parent 06540a0e71
commit 2c1926356a
13 changed files with 476 additions and 21 deletions

View File

@@ -1962,3 +1962,10 @@ MIT License
- 남은 고도화를 한 번에 끝내기 위한 통합 마감 계획을 확정했습니다. 다음 배치는 `문서 포맷 최종 마감 -> 에이전틱 루프 iteration pipeline 분리 -> 개발언어 no-LSP fallback 심화 -> 명령/스킬 합성 및 릴리즈 게이트` 순서로 진행합니다.
- 기준 레퍼런스는 `claude-code`의 [query.ts](/E:/AX%20Copilot%20-%20Codex/claw-code/claw-code-f5a40b86dede580f6543bf8926c9af017eea9409/src/query.ts), [QueryEngine.ts](/E:/AX%20Copilot%20-%20Codex/claw-code/claw-code-f5a40b86dede580f6543bf8926c9af017eea9409/src/QueryEngine.ts), [toolResultStorage.ts](/E:/AX%20Copilot%20-%20Codex/claw-code/claw-code-f5a40b86dede580f6543bf8926c9af017eea9409/src/utils/toolResultStorage.ts), [messageQueueManager.ts](/E:/AX%20Copilot%20-%20Codex/claw-code/claw-code-f5a40b86dede580f6543bf8926c9af017eea9409/src/utils/messageQueueManager.ts), [commands.ts](/E:/AX%20Copilot%20-%20Codex/claw-code/claw-code-f5a40b86dede580f6543bf8926c9af017eea9409/src/commands.ts)이며, AX 쪽에서는 `AgentLoopService`, `AgentCommandQueue`, `AgentQueryContextBuilder`, `ContextCondenser`, `CodeLanguageCatalog`, `SlashCommandCatalog`, `SkillService`, 각 문서 생성 스킬을 마감 대상으로 잡았습니다.
- 완료 기준은 `dotnet build` 경고 0 / 오류 0, `PPTX/XLSX/DOCX/HTML golden`, `중단/재개/권한/branch/replay`, `언어 fallback` 회귀까지 모두 통과하는 상태입니다.
업데이트: 2026-04-15 10:24 (KST)
- 문서 품질 critic를 한 단계 더 구체화했습니다. [ArtifactQualityReviewService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ArtifactQualityReviewService.cs)는 이제 HTML의 `board_report`/`strategy_brief` 결정 블록 누락, DOCX 장문 문서의 evidence table·callout 부족, XLSX dashboard workbook의 trend/variance framing·sheet summary·headline tile 부족을 각각 별도 이슈로 판정합니다.
- 보정 가이드도 포맷별로 더 실행 가능하게 정리했습니다. [ArtifactRepairGuideService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ArtifactRepairGuideService.cs)와 [DeckRepairGuideService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/DeckRepairGuideService.cs)는 decision summary, trend/variance block, evidence table, next steps 같은 실제 보강 항목을 바로 제안하도록 보강했습니다.
- golden 회귀를 확대했습니다. [HtmlSkillGoldenReportTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/HtmlSkillGoldenReportTests.cs)에 strategy brief 시나리오를 추가했고, [PptxSkillGoldenDeckTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/PptxSkillGoldenDeckTests.cs)는 PMO steering deck까지 고정했습니다. [ArtifactQualityReviewServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ArtifactQualityReviewServiceTests.cs), [ArtifactRepairGuideServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ArtifactRepairGuideServiceTests.cs), [DeckQualityReviewServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/DeckQualityReviewServiceTests.cs), [DeckRepairGuideServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/DeckRepairGuideServiceTests.cs)도 함께 확장했습니다.
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_finish_batch\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch\\` 경고 0 / 오류 0
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|ArtifactRepairGuideServiceTests|DeckQualityReviewServiceTests|DeckRepairGuideServiceTests|HtmlSkillGoldenReportTests|PptxSkillGoldenDeckTests|DocxSkillGoldenDocumentTests|ExcelSkillGoldenWorkbookTests" -p:OutputPath=bin\\verify_doc_finish_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch_tests\\` 통과 26

View File

@@ -1078,3 +1078,33 @@ UI ?붿옄???€洹쒕え 由ы뙥?좊쭅 ???꾪뿕 ?묒뾽 ??湲곕줉???덉쟾
- 문서 golden: `PPTX/XLSX/DOCX/HTML`
- 루프/큐/컨텍스트: `중단/재개/권한/branch/replay`
- 언어 fallback: `CodeLanguageCatalogTests`, `WorkspaceContextGeneratorTests`, 관련 fallback 회귀
업데이트: 2026-04-15 10:24 (KST)
- 문서 critic 세부화:
- `ArtifactQualityReviewService.cs`
- HTML: `board_report`는 `decision_summary` 누락 시 별도 경고, `strategy_brief`는 explicit decision block 누락 시 별도 경고
- HTML 품질 계산 시 `h2`만이 아니라 `board_report`, `strategy_brief`, `comparison`, `roadmap`, `decision_summary`, `evidence_cards`, `kpi`를 `major section`/`supporting block` 추정에 반영
- DOCX: 장문 비즈니스 문서에서 evidence table, callout/highlight 부족을 별도 이슈로 판정
- XLSX: dashboard workbook의 `trend_series`, `variance_series`, `sheet_summaries`, `dashboard_tiles` 부재를 각각 추가 진단
- 보정 가이드 강화:
- `ArtifactRepairGuideService.cs`
- HTML: board decision summary, strategy brief decision block, comparison/roadmap, evidence-card 보강 가이드 추가
- DOCX: evidence table, callout/highlight 보강 가이드 추가
- XLSX: trend/variance framing, supporting sheet summary, headline tile, follow-up action 보강 가이드 추가
- `DeckRepairGuideService.cs`
- Executive Summary의 decision ask, recommendation slide의 rationale/next steps 누락을 별도 액션으로 변환
- Workbook review 입력 확장:
- `WorkbookReviewInput`에 `HasTrendSection`, `HasVarianceSection`, `HasDashboardTileSection` 추가
- `ExcelSkill.cs`의 single-summary/multi-sheet review 경로가 위 신호를 실제 review에 전달
- Deck 품질 기준 강화:
- `DeckQualityReviewService.cs`
- Executive Summary 내 recommendation/decision ask 누락 감지
- Recommendation slide의 rationale/next steps 누락 감지
- Roadmap slide가 1개 phase만 가진 경우 경고
- Golden/회귀 테스트 확장:
- `HtmlSkillGoldenReportTests.cs`: `strategy-golden.html` 추가
- `PptxSkillGoldenDeckTests.cs`: `pmo-golden.pptx` 추가
- `ArtifactQualityReviewServiceTests.cs`, `ArtifactRepairGuideServiceTests.cs`, `DeckQualityReviewServiceTests.cs`, `DeckRepairGuideServiceTests.cs` 회귀 확장
- 검증:
- `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_finish_batch\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch\\`
- `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|ArtifactRepairGuideServiceTests|DeckQualityReviewServiceTests|DeckRepairGuideServiceTests|HtmlSkillGoldenReportTests|PptxSkillGoldenDeckTests|DocxSkillGoldenDocumentTests|ExcelSkillGoldenWorkbookTests" -p:OutputPath=bin\\verify_doc_finish_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch_tests\\`

View File

@@ -17,6 +17,7 @@ public class ArtifactQualityReviewServiceTests
<table><tr><th>Metric</th><th>Value</th></tr><tr><td>NPS</td><td>61</td></tr></table>
<div class="comparison-grid"></div>
<div class="roadmap-block"></div>
<div class="decision-summary"></div>
<section class="board-report-panel"></section>
<section class="strategy-brief-panel"></section>
<h2>Recommendation</h2><p>Recommendation text.</p><p>Action support text.</p>
@@ -77,6 +78,9 @@ public class ArtifactQualityReviewServiceTests
false,
false,
false,
false,
false,
false,
false));
review.Issues.Should().Contain(issue => issue.Message.Contains("summary sheet", StringComparison.OrdinalIgnoreCase));
@@ -101,11 +105,60 @@ public class ArtifactQualityReviewServiceTests
false,
false,
false,
true));
false,
false,
false,
false));
review.Issues.Should().Contain(issue => issue.Message.Contains("highlights or actions", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("supporting detail sheets", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("trend or variance formulas", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("KPI, trend, or decision content", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("trend or variance framing", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("summarize each supporting detail sheet", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void ReviewHtml_ShouldFlagDecisionGap_ForBoardAndStrategyPanels()
{
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="comparison-grid"></div>
<div class="roadmap-block"></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("Decision Gap", html, hasCover: false, hasTableOfContents: false, printReady: true);
review.Issues.Should().Contain(issue => issue.Message.Contains("Board-ready report should include a decision summary block", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("Strategy brief should include explicit decisions", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void ReviewStructuredDocument_ShouldRecommendEvidenceTableAndCallouts_ForLongBusinessDoc()
{
var review = ArtifactQualityReviewService.ReviewStructuredDocument(new StructuredDocumentReviewInput(
"Executive Brief",
6,
2400,
0,
1,
0,
0,
0,
true,
true,
true,
true,
true,
true,
true));
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));
}
}

View File

@@ -15,15 +15,16 @@ public class ArtifactRepairGuideServiceTests
["Includes summary sheet"],
[
new ArtifactReviewIssue("Dashboard sheet lacks KPI, trend, or decision content.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Workbook could benefit from a dashboard sheet to summarize multi-sheet trends.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Summary sheet does not link to detail sheets.", ArtifactReviewSeverity.Warning)
new ArtifactReviewIssue("Dashboard sheet lacks trend or variance framing for the supporting sheets.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Dashboard workbook should summarize each supporting detail sheet.", ArtifactReviewSeverity.Info)
]);
var guide = ArtifactRepairGuideService.BuildGuide(review);
guide.Should().Contain("dashboard sheet");
guide.Should().Contain("dashboard");
guide.Should().Contain("core story");
guide.Should().Contain("detail sheets");
guide.Should().Contain("trend");
guide.Should().Contain("sheet summaries");
}
[Fact]
@@ -34,14 +35,14 @@ public class ArtifactRepairGuideServiceTests
70,
["Includes print-ready CSS"],
[
new ArtifactReviewIssue("Print-ready business report would benefit from decision summary or evidence cards.", ArtifactReviewSeverity.Warning),
new ArtifactReviewIssue("Strategy brief would be stronger with a comparison or roadmap block.", ArtifactReviewSeverity.Info)
new ArtifactReviewIssue("Board-ready report should include a decision summary block.", ArtifactReviewSeverity.Warning),
new ArtifactReviewIssue("Strategy brief should include explicit decisions or a decision summary block.", ArtifactReviewSeverity.Warning)
]);
var guide = ArtifactRepairGuideService.BuildGuide(review);
guide.Should().Contain("decision summary");
guide.Should().Contain("comparison or roadmap");
guide.Should().Contain("strategy brief");
}
[Fact]
@@ -52,15 +53,15 @@ public class ArtifactRepairGuideServiceTests
66,
["Includes cover page"],
[
new ArtifactReviewIssue("Long document could benefit from a table of contents.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Template-based styling could improve consistency for a longer document.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Supporting evidence table is limited for a business document.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Key messages would benefit from callout or highlight blocks.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Header or footer metadata is limited for a business document.", ArtifactReviewSeverity.Info)
]);
var guide = ArtifactRepairGuideService.BuildGuide(review);
guide.Should().Contain("table of contents");
guide.Should().Contain("template");
guide.Should().Contain("evidence table");
guide.Should().Contain("callout");
guide.Should().Contain("header and footer");
}
}

View File

@@ -42,7 +42,7 @@ public class DeckQualityReviewServiceTests
{ "layout": "title", "title": "Growth Strategy" },
{ "layout": "executive_summary", "title": "Executive Summary", "headline": "Headline", "summary_points": ["A","B","C"], "recommendation": "Do B" },
{ "layout": "comparison", "title": "Options", "headline": "Compare", "options": [{ "name": "A", "pros": "Fast", "cons": "Shallow", "verdict": "Fastest" }, { "name": "B", "pros": "Balanced", "cons": "Needs alignment", "verdict": "Recommended" }] },
{ "layout": "roadmap", "title": "Roadmap", "headline": "Execute in three phases" },
{ "layout": "roadmap", "title": "Roadmap", "headline": "Execute in three phases", "phases": [{ "title": "Design", "detail": "Lock scope" }, { "title": "Launch", "detail": "Go live" }] },
{ "layout": "recommendation", "title": "Recommendation", "recommendation": "Approve phase-1", "summary_points": ["Reason 1", "Reason 2"] },
{ "layout": "table", "title": "Appendix & Evidence", "headers": ["Evidence","Detail"], "rows": [["Metric","Value"]] }
]
@@ -81,6 +81,7 @@ public class DeckQualityReviewServiceTests
review.Issues.Should().Contain(issue => issue.Message.Contains("Slide 2"));
review.Issues.Should().Contain(issue => issue.Message.Contains("headline is too long"));
review.Issues.Should().Contain(issue => issue.Message.Contains("decision ask", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("comparison slide needs at least two options"));
}
@@ -107,4 +108,21 @@ public class DeckQualityReviewServiceTests
review.Issues.Should().Contain(issue => issue.Message.Contains("Storyline expects a roadmap slide", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("Storyline expects appendix or evidence support", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void ReviewDeck_ShouldFlagRecommendationSlideWithoutRationaleOrNextSteps()
{
using var slides = JsonDocument.Parse(
"""
[
{ "layout": "title", "title": "PMO Steering" },
{ "layout": "executive_summary", "title": "Executive Summary", "headline": "Delivery is stable", "summary_points": ["A", "B"], "recommendation": "Proceed" },
{ "layout": "recommendation", "title": "Recommendation", "recommendation": "Proceed" }
]
""");
var review = DeckQualityReviewService.ReviewDeck("Thin Recommendation", slides.RootElement, hasTemplate: false, autoRepairCount: 0);
review.Issues.Should().Contain(issue => issue.Message.Contains("supporting rationale or next steps", StringComparison.OrdinalIgnoreCase));
}
}

View File

@@ -70,4 +70,22 @@ public class DeckRepairGuideServiceTests
guide.Should().Contain("comparison slide");
guide.Should().Contain("storyline");
}
[Fact]
public void BuildGuide_ShouldTranslateDecisionAskAndNextStepGaps()
{
var report = new DeckQualityReport(
60,
[],
[
new DeckReviewIssue("Slide 2: Executive Summary should include a recommendation or decision ask.", DeckReviewSeverity.Info),
new DeckReviewIssue("Slide 3: recommendation slide needs supporting rationale or next steps.", DeckReviewSeverity.Info)
],
[]);
var guide = DeckRepairGuideService.BuildGuide(report);
guide.Should().Contain("decision ask");
guide.Should().Contain("next steps");
}
}

View File

@@ -142,4 +142,121 @@ public class HtmlSkillGoldenReportTests
}
}
}
[Fact]
public async Task ExecuteAsync_WithStrongStrategyBrief_ShouldReturnStableQualitySummary()
{
var workDir = Path.Combine(Path.GetTempPath(), "ax-html-golden-strategy-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(workDir);
try
{
var tool = new HtmlSkill();
var context = new AgentContext
{
WorkFolder = workDir,
Permission = "Auto",
OperationMode = "external",
};
var args = JsonDocument.Parse(
"""
{
"path": "strategy-golden.html",
"title": "Growth Strategy Brief",
"body": "",
"toc": true,
"print": true,
"cover": {
"title": "Growth Strategy Brief",
"subtitle": "Decision-ready strategy pack",
"author": "AX Copilot",
"date": "2026-04-15"
},
"sections": [
{ "type": "heading", "level": 2, "text": "Executive Summary" },
{ "type": "paragraph", "text": "The strongest near-term growth path is an SMB-first expansion strategy that protects margin while improving sales velocity. The evidence is consistent across retention, commercial efficiency, and onboarding load." },
{ "type": "paragraph", "text": "Leadership should approve the SMB-first sequencing now, then unlock enterprise investment after onboarding capacity stabilizes. This is a sequencing choice, not a strategic reversal." },
{
"type": "strategy_brief",
"title": "Strategic Question",
"strategic_question": "Which segment should be prioritized over the next two quarters?",
"thesis": "SMB-first expansion offers the best balance of growth, margin, and execution risk.",
"implications": [
"Commercial resources should shift toward faster-cycle opportunities.",
"Enterprise capacity should be staged behind onboarding readiness."
],
"decisions": [
"Approve SMB-first sequencing",
"Stage enterprise hiring after week 8"
]
},
{
"type": "comparison",
"title": "Strategic Options",
"items": [
{ "name": "SMB first", "summary": "Fastest near-term growth", "pros": "Strong retention and shorter sales cycle", "cons": "Smaller average deal size", "verdict": "Recommended" },
{ "name": "Balanced mix", "summary": "Diversified growth path", "pros": "Lower concentration risk", "cons": "More operating complexity", "verdict": "Balanced" },
{ "name": "Enterprise first", "summary": "Highest headline upside", "pros": "Largest deal size", "cons": "Longest ramp and heaviest onboarding load", "verdict": "Long-term option" }
]
},
{
"type": "evidence_cards",
"title": "Evidence",
"items": [
{ "title": "Retention", "detail": "SMB retention remains above plan across the last three cohorts.", "source": "Revenue analytics", "tag": "KPI" },
{ "title": "Onboarding load", "detail": "Enterprise onboarding is the main delivery bottleneck.", "source": "Operations review", "tag": "Ops" }
]
},
{
"type": "roadmap",
"title": "Execution Roadmap",
"phases": [
{ "title": "Refocus", "detail": "Reset segment priorities and revenue targets", "timeline": "Weeks 1-2", "owner": "Strategy" },
{ "title": "Enable", "detail": "Align messaging, pricing, and onboarding", "timeline": "Weeks 3-6", "owner": "Sales + Ops" },
{ "title": "Scale", "detail": "Expand channel coverage and monitor retention", "timeline": "Weeks 7-12", "owner": "GM" }
]
},
{
"type": "decision_summary",
"title": "Decision Summary",
"decision": "Approve the SMB-first growth plan and stage enterprise investment after onboarding capacity stabilizes.",
"rationale": "The SMB-first path captures faster growth with lower delivery complexity.",
"actions": [
"Reset next-quarter targets",
"Lock the SMB enablement backlog",
"Review enterprise readiness at week 8"
]
},
{ "type": "heading", "level": 2, "text": "Appendix" },
{ "type": "paragraph", "text": "The appendix contains source notes and supporting assumptions behind the growth model. It preserves traceability from the recommended decision to the underlying evidence." }
]
}
""").RootElement;
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
result.Success.Should().BeTrue();
result.Output.Should().Contain("Quality score");
result.Output.Should().Contain("Needs work: none");
result.Output.Should().Contain("Repair guide: none");
var html = File.ReadAllText(Path.Combine(workDir, "strategy-golden.html"));
html.Should().Contain("strategy-brief-panel");
html.Should().Contain("decision-summary");
html.Should().Contain("comparison-grid");
html.Should().Contain("evidence-cards");
}
finally
{
try
{
if (Directory.Exists(workDir))
Directory.Delete(workDir, true);
}
catch
{
}
}
}
}

View File

@@ -237,4 +237,131 @@ public class PptxSkillGoldenDeckTests
}
}
}
[Fact]
public async Task ExecuteAsync_WithStrongPmoSteeringDeck_ShouldRemainFreeOfSlideAlerts()
{
var workDir = Path.Combine(Path.GetTempPath(), "ax-pptx-golden-pmo-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(workDir);
try
{
var tool = new PptxSkill();
var context = new AgentContext
{
WorkFolder = workDir,
Permission = "Auto",
OperationMode = "external",
};
var args = JsonDocument.Parse(
"""
{
"path": "pmo-golden.pptx",
"theme": "professional",
"template_pack": "pmo",
"audience": "Transformation steering committee",
"objective": "Confirm the phase-2 PMO steering decision",
"decision_ask": "Approve the next wave and keep the governance cadence",
"storyline": ["Executive Summary", "Issue Tree", "Risk Overview", "Roadmap", "Recommendation", "Appendix"],
"slides": [
{
"layout": "title",
"title": "PMO Steering Pack"
},
{
"layout": "executive_summary",
"title": "Executive Summary",
"headline": "Phase-2 can proceed with controlled delivery risk and a stable governance model.",
"summary_points": [
"The current PMO cadence already highlights the few issues that matter most for the next wave.",
"The major delivery risk is onboarding bottlenecks, not design ambiguity.",
"The decision is now about release timing and governance discipline."
],
"recommendation": "Approve the next wave and keep the PMO steering cadence intact.",
"kpis": [
{ "label": "Milestone Health", "value": "92%", "trend": "on track", "note": "core plan stable" },
{ "label": "Risk Closure", "value": "78%", "trend": "improving", "note": "weekly review" },
{ "label": "Budget", "value": "98%", "trend": "plan", "note": "within guardrails" }
]
},
{
"layout": "issue_tree",
"title": "Issue Tree",
"issues": [
"Onboarding capacity is the main delivery bottleneck.",
"Regional readiness varies by manager capability.",
"Decision latency increases when risk owners are unclear."
],
"implications": [
"Protect the launch window with tighter staffing rules.",
"Keep manager enablement inside the PMO workplan.",
"Confirm named owners for the top-three risk items."
]
},
{
"layout": "risk_heatmap",
"title": "Risk Overview",
"risks": [
{ "title": "Onboarding bottleneck", "impact": "High", "likelihood": "Medium", "mitigation": "Stage wave-2 staffing" },
{ "title": "Regional readiness gap", "impact": "Medium", "likelihood": "Medium", "mitigation": "Run manager enablement" }
]
},
{
"layout": "roadmap",
"title": "Roadmap",
"headline": "Run the next wave through three controlled PMO checkpoints.",
"phases": [
{ "title": "Approve", "detail": "Lock funding, risk owners, and wave scope", "timeline": "Week 1", "owner": "SteerCo" },
{ "title": "Launch", "detail": "Start the next wave and monitor onboarding load", "timeline": "Weeks 2-6", "owner": "PMO" },
{ "title": "Review", "detail": "Confirm milestone health and risk closure", "timeline": "Week 8", "owner": "Exec sponsors" }
]
},
{
"layout": "recommendation",
"title": "Recommendation",
"headline": "Approve the next wave and keep governance cadence intact.",
"recommendation": "Approve the next wave, keep weekly PMO reviews, and assign named owners to the top-three risks.",
"summary_points": [
"The current evidence is sufficient to proceed without delaying the launch window.",
"Governance continuity is the main control lever for delivery quality."
],
"next_steps": [
"Approve funding and scope",
"Confirm risk owners",
"Start weekly PMO review for wave 2"
]
},
{
"layout": "appendix_evidence",
"title": "Appendix & Evidence",
"evidence": [
{ "title": "Milestone trend", "detail": "Core milestones remain above 90% health", "source": "PMO tracker" },
{ "title": "Budget status", "detail": "Program remains inside the approved guardrails", "source": "Finance review" }
]
}
]
}
""").RootElement;
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
result.Success.Should().BeTrue();
File.Exists(Path.Combine(workDir, "pmo-golden.pptx")).Should().BeTrue();
result.Output.Should().Contain("PPT quality");
result.Output.Should().NotContain("Slide alerts:");
result.Output.Should().Contain("Needs work: none");
}
finally
{
try
{
if (Directory.Exists(workDir))
Directory.Delete(workDir, true);
}
catch
{
}
}
}
}

View File

@@ -73,7 +73,10 @@ public sealed record WorkbookReviewInput(
bool HasActionSection,
bool HasScorecardSection,
bool HasDecisionSection,
bool HasSheetSummarySection);
bool HasSheetSummarySection,
bool HasTrendSection,
bool HasVarianceSection,
bool HasDashboardTileSection);
public static class ArtifactQualityReviewService
{
@@ -131,13 +134,33 @@ public static class ArtifactQualityReviewService
var boardPanelCount = Regex.Matches(html, @"board-report-panel", RegexOptions.IgnoreCase).Count;
var strategyBriefCount = Regex.Matches(html, @"strategy-brief-panel", RegexOptions.IgnoreCase).Count;
var kpiCount = Regex.Matches(html, @"kpi-card|kpi-grid|metric-card", RegexOptions.IgnoreCase).Count;
var majorSectionEstimate = sectionCount
+ (boardPanelCount > 0 ? 1 : 0)
+ (strategyBriefCount > 0 ? 1 : 0)
+ (comparisonCount > 0 ? 1 : 0)
+ (roadmapCount > 0 ? 1 : 0)
+ (matrixCount > 0 ? 1 : 0)
+ (decisionCount > 0 ? 1 : 0)
+ (evidenceCardCount > 0 ? 1 : 0)
+ (kpiCount > 0 ? 1 : 0);
var supportingBlockEstimate = paragraphCount
+ tableCount
+ calloutCount
+ comparisonCount
+ roadmapCount
+ matrixCount
+ decisionCount
+ evidenceCardCount
+ boardPanelCount
+ strategyBriefCount
+ (kpiCount > 0 ? 1 : 0);
var placeholderCount = CountPlaceholders(html);
if (hasCover) strengths.Add("Includes cover page");
if (hasTableOfContents) strengths.Add("Includes table of contents");
if (printReady) strengths.Add("Includes print-ready CSS");
if (hasPrintFrame) strengths.Add("Includes print header/footer frame");
if (sectionCount >= 5) strengths.Add($"Contains {sectionCount} major sections");
if (majorSectionEstimate >= 5) strengths.Add($"Contains {majorSectionEstimate} major sections");
if (tableCount > 0 || comparisonCount > 0 || roadmapCount > 0 || matrixCount > 0 || decisionCount > 0 || evidenceCardCount > 0 || boardPanelCount > 0 || strategyBriefCount > 0 || kpiCount > 0)
strengths.Add("Uses structured business blocks");
if (calloutCount > 0) strengths.Add("Uses callout blocks for emphasis");
@@ -146,9 +169,9 @@ public static class ArtifactQualityReviewService
if (html.Length < 1800)
issues.Add(new("Body content may be too short for an executive-quality document.", ArtifactReviewSeverity.Warning));
if (sectionCount < 4)
if (majorSectionEstimate < 4)
issues.Add(new("Major section count is low for a business report.", ArtifactReviewSeverity.Warning));
if (paragraphCount < sectionCount * 2)
if (supportingBlockEstimate < Math.Max(4, majorSectionEstimate * 2))
issues.Add(new("Several sections may need more supporting paragraphs.", ArtifactReviewSeverity.Warning));
if (tableCount + comparisonCount + roadmapCount + matrixCount + decisionCount + evidenceCardCount + boardPanelCount + strategyBriefCount + kpiCount == 0)
issues.Add(new("Structured visual blocks are limited.", ArtifactReviewSeverity.Warning));
@@ -158,12 +181,16 @@ public static class ArtifactQualityReviewService
issues.Add(new("Executive summary or summary section is missing.", ArtifactReviewSeverity.Warning));
if (printReady && !hasPrintFrame)
issues.Add(new("Print-ready export is missing a print header/footer frame.", ArtifactReviewSeverity.Info));
if (printReady && sectionCount >= 4 && decisionCount + evidenceCardCount == 0)
if (printReady && majorSectionEstimate >= 4 && decisionCount + evidenceCardCount == 0)
issues.Add(new("Print-ready business report would benefit from decision summary or evidence cards.", ArtifactReviewSeverity.Warning));
if (printReady && !hasCover && sectionCount >= 5)
if (printReady && !hasCover && majorSectionEstimate >= 5)
issues.Add(new("Print-ready report could benefit from a cover page.", ArtifactReviewSeverity.Info));
if (boardPanelCount > 0 && decisionCount == 0)
issues.Add(new("Board-ready report should include a decision summary block.", ArtifactReviewSeverity.Warning));
if (boardPanelCount > 0 && evidenceCardCount == 0 && tableCount == 0)
issues.Add(new("Board-ready report would benefit from evidence cards or a supporting table.", ArtifactReviewSeverity.Info));
if (strategyBriefCount > 0 && decisionCount == 0)
issues.Add(new("Strategy brief should include explicit decisions or a decision summary block.", ArtifactReviewSeverity.Warning));
if (strategyBriefCount > 0 && comparisonCount + roadmapCount == 0)
issues.Add(new("Strategy brief would be stronger with a comparison or roadmap block.", ArtifactReviewSeverity.Info));
@@ -202,6 +229,10 @@ public static class ArtifactQualityReviewService
issues.Add(new("Recommendation or next-step section is missing.", ArtifactReviewSeverity.Warning));
if (input.SectionCount >= 6 && !input.HasAppendixSection)
issues.Add(new("Appendix or reference section is limited for a long document.", ArtifactReviewSeverity.Info));
if (input.SectionCount >= 5 && input.TableCount == 0)
issues.Add(new("Supporting evidence table is limited for a business document.", ArtifactReviewSeverity.Info));
if (input.SectionCount >= 5 && input.CalloutCount + input.HighlightCount == 0)
issues.Add(new("Key messages would benefit from callout or highlight blocks.", ArtifactReviewSeverity.Info));
if (input.TableCount + input.ListCount + input.CalloutCount + input.HighlightCount == 0)
issues.Add(new("Document structure is mostly plain paragraphs.", ArtifactReviewSeverity.Warning));
@@ -248,6 +279,14 @@ public static class ArtifactQualityReviewService
issues.Add(new("Dashboard sheet should link to supporting detail sheets.", ArtifactReviewSeverity.Info));
if (input.HasDashboardSheet && input.DetailSheetCount >= 2 && input.FormulaCount < 3)
issues.Add(new("Dashboard sheet would benefit from more calculated trend or variance formulas.", ArtifactReviewSeverity.Info));
if (input.HasDashboardSheet && input.DetailSheetCount >= 2 && !input.HasTrendSection && !input.HasVarianceSection)
issues.Add(new("Dashboard sheet lacks trend or variance framing for the supporting sheets.", ArtifactReviewSeverity.Info));
if (input.HasDashboardSheet && input.DetailSheetCount >= 2 && !input.HasSheetSummarySection)
issues.Add(new("Dashboard workbook should summarize each supporting detail sheet.", ArtifactReviewSeverity.Info));
if (input.HasDecisionSection && !input.HasActionSection)
issues.Add(new("Decision summary is present but follow-up actions are limited.", ArtifactReviewSeverity.Info));
if (input.HasDashboardSheet && !input.HasDashboardTileSection && !input.HasHighlightSection)
issues.Add(new("Dashboard sheet would benefit from headline tiles or highlight callouts.", ArtifactReviewSeverity.Info));
return BuildReport("xlsx", strengths, issues);
}

View File

@@ -34,8 +34,12 @@ public static class ArtifactRepairGuideService
private static string? BuildHtmlAction(string message)
{
if (message.Contains("Board-ready report should include a decision summary block", StringComparison.OrdinalIgnoreCase))
return "Add a decision summary block directly after the board ask so the approval point is explicit";
if (message.Contains("decision summary or evidence cards", StringComparison.OrdinalIgnoreCase))
return "Add a decision summary and evidence cards near the recommendation section";
if (message.Contains("Strategy brief should include explicit decisions", StringComparison.OrdinalIgnoreCase))
return "Add an explicit decisions block or decision summary to the strategy brief";
if (message.Contains("cover page", StringComparison.OrdinalIgnoreCase))
return "Add a cover page for print-ready or board-facing reports";
if (message.Contains("table of contents", StringComparison.OrdinalIgnoreCase))
@@ -57,14 +61,22 @@ public static class ArtifactRepairGuideService
return "Add KPI, trend, or decision blocks so the dashboard communicates the core story at a glance";
if (message.Contains("dashboard sheet", StringComparison.OrdinalIgnoreCase))
return "Add a dashboard sheet with KPI tiles, trends, and links to detail sheets";
if (message.Contains("trend or variance framing", StringComparison.OrdinalIgnoreCase))
return "Add trend and variance blocks so the dashboard explains what changed across the detail sheets";
if (message.Contains("summarize each supporting detail sheet", StringComparison.OrdinalIgnoreCase))
return "Add sheet summaries so each detail tab rolls up into the dashboard story";
if (message.Contains("Summary sheet could better surface", StringComparison.OrdinalIgnoreCase))
return "Promote KPIs, decisions, and highlights into the summary or dashboard sheet";
if (message.Contains("Dashboard sheet could better call out highlights or actions", StringComparison.OrdinalIgnoreCase))
return "Add highlight and action callout blocks to the dashboard sheet";
if (message.Contains("headline tiles or highlight callouts", StringComparison.OrdinalIgnoreCase))
return "Add dashboard tiles or highlight callouts to surface the top-line business message";
if (message.Contains("Dashboard sheet should link", StringComparison.OrdinalIgnoreCase))
return "Add hyperlinks from the dashboard to each supporting detail sheet";
if (message.Contains("trend or variance formulas", StringComparison.OrdinalIgnoreCase))
return "Add trend, variance, or rollup formulas so the dashboard summarizes detail sheets";
if (message.Contains("follow-up actions are limited", StringComparison.OrdinalIgnoreCase))
return "Add explicit follow-up actions so each dashboard decision has a named next step";
if (message.Contains("data validation", StringComparison.OrdinalIgnoreCase))
return "Add data validation rules for editable status, owner, or category columns";
if (message.Contains("Conditional formatting", StringComparison.OrdinalIgnoreCase))
@@ -92,6 +104,10 @@ public static class ArtifactRepairGuideService
return "Add a recommendation or next-step section with a clear decision ask";
if (message.Contains("Appendix", StringComparison.OrdinalIgnoreCase))
return "Add an appendix or reference section for longer documents";
if (message.Contains("evidence table", StringComparison.OrdinalIgnoreCase))
return "Add at least one supporting evidence table so key claims are traceable";
if (message.Contains("callout or highlight blocks", StringComparison.OrdinalIgnoreCase))
return "Add callout or highlight blocks to surface the most important messages";
if (message.Contains("mostly plain paragraphs", StringComparison.OrdinalIgnoreCase))
return "Introduce tables, lists, or callout blocks to break up dense prose";
if (message.Contains("too short", StringComparison.OrdinalIgnoreCase))

View File

@@ -207,6 +207,11 @@ public static class DeckQualityReviewService
issues.Add(new($"Slide {slideNumber}: Executive Summary needs more supporting points.", DeckReviewSeverity.Warning));
added++;
}
if (string.IsNullOrWhiteSpace(ReadString(slide, "recommendation")))
{
issues.Add(new($"Slide {slideNumber}: Executive Summary should include a recommendation or decision ask.", DeckReviewSeverity.Info));
added++;
}
break;
case "recommendation":
if (string.IsNullOrWhiteSpace(ReadString(slide, "recommendation")))
@@ -214,6 +219,12 @@ public static class DeckQualityReviewService
issues.Add(new($"Slide {slideNumber}: recommendation text is missing.", DeckReviewSeverity.Warning));
added++;
}
if (ReadStringList(slide, "summary_points").Count == 0 &&
ReadStringList(slide, "next_steps").Count == 0)
{
issues.Add(new($"Slide {slideNumber}: recommendation slide needs supporting rationale or next steps.", DeckReviewSeverity.Info));
added++;
}
break;
case "comparison":
if (ReadJsonArrayCount(slide, "options") < 2)
@@ -228,6 +239,11 @@ public static class DeckQualityReviewService
issues.Add(new($"Slide {slideNumber}: roadmap slide is missing phases.", DeckReviewSeverity.Warning));
added++;
}
else if (ReadJsonArrayCount(slide, "phases") < 2)
{
issues.Add(new($"Slide {slideNumber}: roadmap slide should show at least two phases.", DeckReviewSeverity.Info));
added++;
}
break;
case "chart":
if (ReadJsonArrayCount(slide, "chart_labels") == 0 || ReadJsonArrayCount(slide, "chart_values") == 0)

View File

@@ -12,8 +12,12 @@ public static class DeckRepairGuideService
{
var action = issue.Message switch
{
var message when message.Contains("decision ask", StringComparison.OrdinalIgnoreCase)
=> "Add a recommendation or decision ask directly to the Executive Summary",
var message when message.Contains("Executive Summary", StringComparison.OrdinalIgnoreCase)
=> "Add or strengthen the Executive Summary with 2-3 evidence-backed takeaways",
var message when message.Contains("supporting rationale or next steps", StringComparison.OrdinalIgnoreCase)
=> "Add supporting rationale or next steps so the recommendation turns into action",
var message when message.Contains("Recommendation", StringComparison.OrdinalIgnoreCase)
=> "Add a clear recommendation or decision request slide near the end of the deck",
var message when message.Contains("roadmap", StringComparison.OrdinalIgnoreCase)

View File

@@ -221,6 +221,9 @@ public class ExcelSkill : IAgentTool
false,
false,
false,
false,
false,
false,
false));
foreach (var line in ArtifactQualityOutputFormatter.BuildLines(review))
features += $"\n{line}";
@@ -332,7 +335,10 @@ public class ExcelSkill : IAgentTool
HasSummaryItems(summarySheet, "actions"),
HasSummaryItems(summarySheet, "scorecards") || HasSummaryItems(summarySheet, "cards") || HasSummaryItems(summarySheet, "kpis") || HasSummaryItems(summarySheet, "trend_series") || HasSummaryItems(summarySheet, "dashboard_tiles") || HasSummaryItems(summarySheet, "variance_series"),
HasStructuredSummaryContent(summarySheet, "decision_summary"),
HasSummaryItems(summarySheet, "sheet_summaries")));
HasSummaryItems(summarySheet, "sheet_summaries"),
HasSummaryItems(summarySheet, "trend_series"),
HasSummaryItems(summarySheet, "variance_series"),
HasSummaryItems(summarySheet, "dashboard_tiles")));
foreach (var line in ArtifactQualityOutputFormatter.BuildLines(review))
features += $"\n{line}";
@@ -477,7 +483,10 @@ public class ExcelSkill : IAgentTool
HasSummaryItems(summarySheet, "actions"),
HasSummaryItems(summarySheet, "scorecards") || HasSummaryItems(summarySheet, "cards") || HasSummaryItems(summarySheet, "kpis") || HasSummaryItems(summarySheet, "trend_series") || HasSummaryItems(summarySheet, "dashboard_tiles") || HasSummaryItems(summarySheet, "variance_series"),
HasStructuredSummaryContent(summarySheet, "decision_summary"),
HasSummaryItems(summarySheet, "sheet_summaries")));
HasSummaryItems(summarySheet, "sheet_summaries"),
HasSummaryItems(summarySheet, "trend_series"),
HasSummaryItems(summarySheet, "variance_series"),
HasSummaryItems(summarySheet, "dashboard_tiles")));
var outputLines = new List<string>
{
$"Excel 파일 생성 완료: {fullPath}",