diff --git a/README.md b/README.md index 5460825..281e306 100644 --- a/README.md +++ b/README.md @@ -1940,3 +1940,11 @@ MIT License - 저장 경로 회귀도 추가했습니다. 새 [ChatStorageServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ChatStorageServiceTests.cs)는 legacy `.axchat` 파일을 직접 만든 뒤 로드시 synthetic preview가 실제로 채워지는지 검증합니다. - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_loop_storage_golden\\ -p:IntermediateOutputPath=obj\\verify_loop_storage_golden\\` 경고 0 / 오류 0 - 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AgentLoopDiagnosticsFormatterTests|ChatStorageServiceTests|HtmlSkillGoldenReportTests|DocxSkillGoldenDocumentTests|AgentMessageInvariantHelperTests|PptxSkillGoldenDeckTests|ExcelSkillGoldenWorkbookTests" -p:OutputPath=bin\\verify_loop_storage_golden_tests\\ -p:IntermediateOutputPath=obj\\verify_loop_storage_golden_tests\\` 통과 10 + +업데이트: 2026-04-15 09:49 (KST) +- 개발언어 카탈로그를 실제 워크플로 힌트 소스로 확장했습니다. [CodeLanguageCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/CodeLanguageCatalog.cs)는 이제 언어별 `manifest/build/test/lint` 힌트를 직접 조회하는 API와 `workflow summary` 조합기를 제공해, 단순 지원 언어 표시에 그치지 않고 실제 분석/복구 지침 생성에 재사용할 수 있습니다. +- [WorkspaceContextGenerator.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs)는 새 `## Language Workflow` 섹션을 생성합니다. 상위 언어 3개까지의 주요 manifest, build/test/lint 힌트를 `.ax-context.md`에 함께 기록해, no-LSP 환경에서도 워크스페이스 문맥 안에 바로 실행 가능한 언어별 힌트를 남깁니다. +- 문서 포맷 품질 출력도 공통 helper로 정리하기 시작했습니다. 새 [ArtifactQualityOutputFormatter.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ArtifactQualityOutputFormatter.cs)를 추가했고, [HtmlSkill.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/HtmlSkill.cs)와 [ExcelSkill.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ExcelSkill.cs)는 동일한 quality summary + repair guide 조합을 사용하도록 맞췄습니다. +- 테스트는 [CodeLanguageCatalogTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/CodeLanguageCatalogTests.cs)와 [WorkspaceContextGeneratorTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/WorkspaceContextGeneratorTests.cs)를 확장해 workflow summary와 workspace context 내 language workflow 섹션을 회귀 검증합니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_language_workflow\\ -p:IntermediateOutputPath=obj\\verify_language_workflow\\` 경고 0 / 오류 0 +- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "CodeLanguageCatalogTests|WorkspaceContextGeneratorTests" -p:OutputPath=bin\\verify_language_workflow_tests\\ -p:IntermediateOutputPath=obj\\verify_language_workflow_tests\\` 통과 33 diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index bbfc1b6..f6db739 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1011,3 +1011,14 @@ UI ?붿옄???€洹쒕え 由ы뙥?좊쭅 ???꾪뿕 ?묒뾽 ??湲곕줉???덉쟾 - golden 범위는 이제 `PPTX + XLSX + HTML + DOCX`까지 확장되었습니다. HTML golden은 board-grade 보고서의 print frame/evidence/decision 구성을, DOCX golden은 template/TOC/header-footer/appendix가 포함된 business pack 조립을 기준 fixture로 삼습니다. - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_loop_storage_golden\\ -p:IntermediateOutputPath=obj\\verify_loop_storage_golden\\` 경고 0 / 오류 0 - 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AgentLoopDiagnosticsFormatterTests|ChatStorageServiceTests|HtmlSkillGoldenReportTests|DocxSkillGoldenDocumentTests|AgentMessageInvariantHelperTests|PptxSkillGoldenDeckTests|ExcelSkillGoldenWorkbookTests" -p:OutputPath=bin\\verify_loop_storage_golden_tests\\ -p:IntermediateOutputPath=obj\\verify_loop_storage_golden_tests\\` 통과 10 + +업데이트: 2026-04-15 09:49 (KST) +- `CodeLanguageCatalog`를 단순 표시용 카탈로그에서 실행 힌트 카탈로그로 확장했다. 언어별 `manifest/build/test/lint` 조회 메서드와 `BuildWorkflowSummary()`를 추가해, 지원 언어 목록과 no-LSP fallback 설명이 같은 소스에서 나오도록 정리했다. +- `WorkspaceContextGenerator`는 `.ax-context.md` 생성 시 `## Language Workflow` 섹션을 추가한다. 상위 언어 3개까지의 manifest, build/test/lint 힌트를 함께 기록해 장기 세션과 서브에이전트 문맥에서 바로 재사용할 수 있게 했다. +- 문서 품질 출력 포맷의 공통화를 시작했다. 새 `ArtifactQualityOutputFormatter`를 추가했고, 현재는 `HtmlSkill`, `ExcelSkill`이 동일한 quality summary + repair guide 조합기를 사용한다. 이후 DOCX/PPTX까지 같은 helper로 맞추는 기반이다. +- 테스트 보강: + - `CodeLanguageCatalogTests`: workflow summary, hint lookup 회귀 추가 + - `WorkspaceContextGeneratorTests`: generated context 내 `Language Workflow` 섹션 회귀 추가 +- 검증: + - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_language_workflow\\ -p:IntermediateOutputPath=obj\\verify_language_workflow\\` + - `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "CodeLanguageCatalogTests|WorkspaceContextGeneratorTests" -p:OutputPath=bin\\verify_language_workflow_tests\\ -p:IntermediateOutputPath=obj\\verify_language_workflow_tests\\` diff --git a/docs/NEXT_ROADMAP.md b/docs/NEXT_ROADMAP.md index 1ba3f1f..0140a68 100644 --- a/docs/NEXT_ROADMAP.md +++ b/docs/NEXT_ROADMAP.md @@ -143,3 +143,10 @@ 2. PPTX/XLSX/DOCX/HTML critic/repair loop 최종 마감 3. 언어별 build/test/lint 힌트의 실제 도구 활용 경로 확대 4. 릴리즈 게이트와 로드맵 문서 최종 정합화 + +업데이트: 2026-04-15 09:49 (KST) + +### 추가 진행 메모 +1. 개발언어 고도화는 `지원 언어 표시`를 넘어서 `Language Workflow`를 실제 컨텍스트에 주입하는 단계로 넘어갔습니다. +2. 다음 구현 배치는 `AgentLoopService` 세분화, `tool_result replacement state` 장기 세션 고정, 문서 포맷 공통 quality formatter 확장 순으로 진행합니다. +3. 문서 포맷은 PPTX가 가장 앞서 있고, DOCX/XLSX/HTML은 공통 critic/repair와 golden 회귀를 같은 수준으로 끌어올리는 마감 단계에 들어갑니다. diff --git a/src/AxCopilot.Tests/Services/CodeLanguageCatalogTests.cs b/src/AxCopilot.Tests/Services/CodeLanguageCatalogTests.cs index 6f5363a..7b0f9da 100644 --- a/src/AxCopilot.Tests/Services/CodeLanguageCatalogTests.cs +++ b/src/AxCopilot.Tests/Services/CodeLanguageCatalogTests.cs @@ -57,4 +57,24 @@ public class CodeLanguageCatalogTests summary.Should().Contain("go build ./..."); summary.Should().Contain("go test ./..."); } + [Fact] + public void BuildWorkflowSummary_ShouldExposeActionableWorkflowHints() + { + var summary = CodeLanguageCatalog.BuildWorkflowSummary("rust"); + + summary.Should().Contain("Rust"); + summary.Should().Contain("Cargo.toml"); + summary.Should().Contain("cargo build"); + summary.Should().Contain("cargo test"); + summary.Should().Contain("cargo clippy"); + } + + [Fact] + public void HintLookups_ShouldReturnLanguageSpecificEntries() + { + CodeLanguageCatalog.GetManifestHints("python").Should().Contain("pyproject.toml"); + CodeLanguageCatalog.GetBuildHints("go").Should().Contain("go build ./..."); + CodeLanguageCatalog.GetTestHints("kotlin").Should().Contain("./gradlew test"); + CodeLanguageCatalog.GetLintHints("javascript").Should().Contain("eslint ."); + } } diff --git a/src/AxCopilot.Tests/Services/WorkspaceContextGeneratorTests.cs b/src/AxCopilot.Tests/Services/WorkspaceContextGeneratorTests.cs index 4af4fc5..234fa0d 100644 --- a/src/AxCopilot.Tests/Services/WorkspaceContextGeneratorTests.cs +++ b/src/AxCopilot.Tests/Services/WorkspaceContextGeneratorTests.cs @@ -315,4 +315,27 @@ public class WorkspaceContextGeneratorTests Directory.Delete(tempDir, recursive: true); } } + + [Fact] + public async Task GenerateAsync_IncludesLanguageWorkflowHints() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(tempDir); + try + { + File.WriteAllText(Path.Combine(tempDir, "Cargo.toml"), "[package]\nname='sample'"); + File.WriteAllText(Path.Combine(tempDir, "main.rs"), "fn main() {}"); + + var result = await WorkspaceContextGenerator.GenerateAsync(tempDir); + + result.Should().Contain("## Language Workflow"); + result.Should().Contain("Rust:"); + result.Should().Contain("Cargo.toml"); + result.Should().Contain("cargo build"); + } + finally + { + Directory.Delete(tempDir, recursive: true); + } + } } diff --git a/src/AxCopilot/Services/Agent/ArtifactQualityOutputFormatter.cs b/src/AxCopilot/Services/Agent/ArtifactQualityOutputFormatter.cs new file mode 100644 index 0000000..9f983fd --- /dev/null +++ b/src/AxCopilot/Services/Agent/ArtifactQualityOutputFormatter.cs @@ -0,0 +1,10 @@ +namespace AxCopilot.Services.Agent; + +public static class ArtifactQualityOutputFormatter +{ + public static IReadOnlyList BuildLines(ArtifactQualityReport review) + => [review.ToToolSummary(), ArtifactRepairGuideService.BuildGuide(review)]; + + public static IReadOnlyList BuildLines(DeckQualityReport review) + => [review.ToToolSummary(), DeckRepairGuideService.BuildGuide(review)]; +} diff --git a/src/AxCopilot/Services/Agent/ExcelSkill.cs b/src/AxCopilot/Services/Agent/ExcelSkill.cs index f2c57d8..40f2c1a 100644 --- a/src/AxCopilot/Services/Agent/ExcelSkill.cs +++ b/src/AxCopilot/Services/Agent/ExcelSkill.cs @@ -222,8 +222,8 @@ public class ExcelSkill : IAgentTool false, false, false)); - features += $"\n{review.ToToolSummary()}"; - features += $"\n{ArtifactRepairGuideService.BuildGuide(review)}"; + foreach (var line in ArtifactQualityOutputFormatter.BuildLines(review)) + features += $"\n{line}"; return ToolResult.Ok( $"Excel 파일 생성 완료: {fullPath}\n시트: {sheetName}, 열: {colCount}, 행: {rowCount}{features}", @@ -333,8 +333,8 @@ public class ExcelSkill : IAgentTool 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"))); - features += $"\n{review.ToToolSummary()}"; - features += $"\n{ArtifactRepairGuideService.BuildGuide(review)}"; + foreach (var line in ArtifactQualityOutputFormatter.BuildLines(review)) + features += $"\n{line}"; return ToolResult.Ok( $"Excel ?뚯씪 ?앹꽦 ?꾨즺: {fullPath}\n?쒗듃: {summaryName}, {sheetName}, ?? {colCount}, ?? {rowCount}{features} [?붿빟 ?쒗듃]", diff --git a/src/AxCopilot/Services/Agent/HtmlSkill.cs b/src/AxCopilot/Services/Agent/HtmlSkill.cs index ba83970..49daced 100644 --- a/src/AxCopilot/Services/Agent/HtmlSkill.cs +++ b/src/AxCopilot/Services/Agent/HtmlSkill.cs @@ -308,8 +308,8 @@ public class HtmlSkill : IAgentTool useToc, usePrint, !string.IsNullOrWhiteSpace(printHeader) || !string.IsNullOrWhiteSpace(printFooter)); - featureStr += $"\n{review.ToToolSummary()}"; - featureStr += $"\n{ArtifactRepairGuideService.BuildGuide(review)}"; + foreach (var line in ArtifactQualityOutputFormatter.BuildLines(review)) + featureStr += $"\n{line}"; return ToolResult.Ok( $"HTML 문서 생성 완료: {fullPath} (디자인: {mood}{featureStr})", diff --git a/src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs b/src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs index 9404ea9..26ec742 100644 --- a/src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs +++ b/src/AxCopilot/Services/Agent/WorkspaceContextGenerator.cs @@ -94,6 +94,15 @@ internal static class WorkspaceContextGenerator } // 4. 기존 컨텍스트 파일 감지 + var workflowGuidance = BuildLanguageWorkflow(extDist); + if (workflowGuidance.Count > 0) + { + sb.AppendLine("## Language Workflow"); + foreach (var line in workflowGuidance) + sb.AppendLine($"- {line}"); + sb.AppendLine(); + } + var contextFiles = DetectContextFiles(workFolder); if (contextFiles.Count > 0) { @@ -440,6 +449,28 @@ internal static class WorkspaceContextGenerator .Select(x => $"{x.Language}: {x.Count} file(s)") .ToList(); + private static List BuildLanguageWorkflow(List> extDist) + { + var workflow = new List(); + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var (extension, _) in extDist) + { + var capability = Services.CodeLanguageCatalog.FindByExtension(extension); + if (capability == null || !seen.Add(capability.Key)) + continue; + + var summary = Services.CodeLanguageCatalog.BuildWorkflowSummary(capability.Key); + if (!string.IsNullOrWhiteSpace(summary)) + workflow.Add(summary); + + if (workflow.Count >= 3) + break; + } + + return workflow; + } + private static async Task<(string? Branch, string? Remote)> GetGitInfoAsync( string folder, CancellationToken ct) { diff --git a/src/AxCopilot/Services/CodeLanguageCatalog.cs b/src/AxCopilot/Services/CodeLanguageCatalog.cs index 87e051b..b74d84f 100644 --- a/src/AxCopilot/Services/CodeLanguageCatalog.cs +++ b/src/AxCopilot/Services/CodeLanguageCatalog.cs @@ -380,6 +380,72 @@ public static class CodeLanguageCatalog sb.Append(BuildLspSupportDescription()); return sb.ToString(); } + public static IReadOnlyList GetManifestHints(string? key) + { + var normalizedKey = FindByKey(key)?.Key; + if (string.IsNullOrWhiteSpace(normalizedKey)) + return []; + + return s_manifestHints.TryGetValue(normalizedKey, out var hints) + ? hints + : []; + } + + public static IReadOnlyList GetBuildHints(string? key) + { + var normalizedKey = FindByKey(key)?.Key; + if (string.IsNullOrWhiteSpace(normalizedKey)) + return []; + + return s_buildHints.TryGetValue(normalizedKey, out var hints) + ? hints + : []; + } + + public static IReadOnlyList GetTestHints(string? key) + { + var normalizedKey = FindByKey(key)?.Key; + if (string.IsNullOrWhiteSpace(normalizedKey)) + return []; + + return s_testHints.TryGetValue(normalizedKey, out var hints) + ? hints + : []; + } + + public static IReadOnlyList GetLintHints(string? key) + { + var normalizedKey = FindByKey(key)?.Key; + if (string.IsNullOrWhiteSpace(normalizedKey)) + return []; + + return s_lintHints.TryGetValue(normalizedKey, out var hints) + ? hints + : []; + } + + public static string BuildWorkflowSummary(string? key, int maxHintsPerKind = 2) + { + var capability = FindByKey(key); + if (capability == null) + return string.Empty; + + var parts = new List(); + AppendHintBlock(parts, "manifests", GetManifestHints(capability.Key), maxHintsPerKind); + AppendHintBlock(parts, "build", GetBuildHints(capability.Key), maxHintsPerKind); + AppendHintBlock(parts, "test", GetTestHints(capability.Key), maxHintsPerKind); + AppendHintBlock(parts, "lint", GetLintHints(capability.Key), maxHintsPerKind); + + var primaryGuidance = capability.Guidance.FirstOrDefault(); + if (!string.IsNullOrWhiteSpace(primaryGuidance)) + parts.Add("focus: " + primaryGuidance); + + if (parts.Count == 0) + return capability.DisplayName; + + return $"{capability.DisplayName}: {string.Join(" | ", parts)}"; + } + public static string BuildFallbackSupportDescription() => "LSP 서버가 없거나 연결되지 않아도 확장자, 매니페스트, build/test/lint 힌트 기반의 정적 분석을 계속 제공합니다."; @@ -417,4 +483,19 @@ public static class CodeLanguageCatalog return sb.ToString().TrimEnd(); } + + private static void AppendHintBlock(List parts, string label, IReadOnlyList hints, int maxHintsPerKind) + { + if (hints.Count == 0) + return; + + var limited = hints + .Where(hint => !string.IsNullOrWhiteSpace(hint)) + .Take(Math.Max(1, maxHintsPerKind)) + .ToList(); + if (limited.Count == 0) + return; + + parts.Add($"{label}: {string.Join(", ", limited)}"); + } }