- 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
556 lines
24 KiB
C#
556 lines
24 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace AxCopilot.Services;
|
|
|
|
public sealed record CodeLanguageCapability(
|
|
string Key,
|
|
string DisplayName,
|
|
IReadOnlyList<string> Extensions,
|
|
IReadOnlyList<string> Guidance,
|
|
string? LspLanguageId = null,
|
|
bool ShowInQuickSelect = false,
|
|
string? QuickSelectKey = null,
|
|
string? QuickSelectLabel = null,
|
|
string? QuickSelectIcon = null);
|
|
|
|
/// <summary>
|
|
/// 코드 탭과 에이전트가 공통으로 참조하는 언어 지원 카탈로그.
|
|
/// 파일 분류, 시스템 프롬프트 가이드, build/test/lint 힌트, LSP 가능 여부를 한 곳에서 관리합니다.
|
|
/// </summary>
|
|
public static class CodeLanguageCatalog
|
|
{
|
|
private static readonly IReadOnlyDictionary<string, string[]> s_manifestHints =
|
|
new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["csharp"] = ["*.sln", "*.csproj", "Directory.Build.props", "Directory.Build.targets"],
|
|
["python"] = ["pyproject.toml", "requirements.txt", "setup.py", "environment.yml", "Pipfile"],
|
|
["java"] = ["pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"],
|
|
["cpp"] = ["CMakeLists.txt", "*.vcxproj", "compile_commands.json", "Makefile"],
|
|
["typescript"] = ["package.json", "tsconfig.json", "pnpm-lock.yaml", "yarn.lock", "package-lock.json"],
|
|
["javascript"] = ["package.json", "vite.config.*", "nuxt.config.*", "pnpm-lock.yaml", "yarn.lock", "package-lock.json"],
|
|
["go"] = ["go.mod", "go.sum"],
|
|
["rust"] = ["Cargo.toml", "Cargo.lock"],
|
|
["php"] = ["composer.json", "composer.lock"],
|
|
["ruby"] = ["Gemfile", "Gemfile.lock", "*.gemspec"],
|
|
["kotlin"] = ["build.gradle.kts", "settings.gradle.kts", "gradle.properties"],
|
|
["swift"] = ["Package.swift", "*.xcodeproj", "*.xcworkspace"],
|
|
["sql"] = ["migrations/*.sql", "schema.sql", "seed.sql", "*.sqlproj"],
|
|
};
|
|
|
|
private static readonly IReadOnlyDictionary<string, string[]> s_buildHints =
|
|
new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["csharp"] = ["dotnet build", "dotnet msbuild"],
|
|
["python"] = ["python -m py_compile", "python -m build"],
|
|
["java"] = ["mvn compile", "gradle build"],
|
|
["cpp"] = ["cmake --build .", "msbuild", "make"],
|
|
["typescript"] = ["npm run build", "pnpm build", "tsc -p ."],
|
|
["javascript"] = ["npm run build", "pnpm build", "vite build"],
|
|
["go"] = ["go build ./..."],
|
|
["rust"] = ["cargo build"],
|
|
["php"] = ["composer install --dry-run", "php -l"],
|
|
["ruby"] = ["bundle exec rake", "ruby -c"],
|
|
["kotlin"] = ["gradle build", "./gradlew build"],
|
|
["swift"] = ["swift build", "xcodebuild build"],
|
|
["sql"] = ["apply in a disposable database first", "review migration order and dependency impact"],
|
|
};
|
|
|
|
private static readonly IReadOnlyDictionary<string, string[]> s_testHints =
|
|
new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["csharp"] = ["dotnet test"],
|
|
["python"] = ["pytest", "python -m unittest"],
|
|
["java"] = ["mvn test", "gradle test"],
|
|
["cpp"] = ["ctest", "cmake --build . --target test"],
|
|
["typescript"] = ["npm test", "pnpm test", "vitest", "jest"],
|
|
["javascript"] = ["npm test", "pnpm test", "vitest", "jest"],
|
|
["go"] = ["go test ./..."],
|
|
["rust"] = ["cargo test"],
|
|
["php"] = ["phpunit", "composer test"],
|
|
["ruby"] = ["bundle exec rspec", "bundle exec rake test"],
|
|
["kotlin"] = ["gradle test", "./gradlew test"],
|
|
["swift"] = ["swift test", "xcodebuild test"],
|
|
["sql"] = ["run the script against a disposable database", "verify affected row counts and dependent queries"],
|
|
};
|
|
|
|
private static readonly IReadOnlyDictionary<string, string[]> s_lintHints =
|
|
new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["csharp"] = ["dotnet format", "dotnet build /warnaserror"],
|
|
["python"] = ["ruff check", "black --check", "flake8"],
|
|
["cpp"] = ["clang-format --dry-run", "clang-tidy"],
|
|
["typescript"] = ["npm run lint", "pnpm lint", "eslint ."],
|
|
["javascript"] = ["npm run lint", "pnpm lint", "eslint ."],
|
|
["go"] = ["gofmt -w", "golangci-lint run"],
|
|
["rust"] = ["cargo fmt --check", "cargo clippy"],
|
|
["php"] = ["php -l", "phpcs"],
|
|
["ruby"] = ["rubocop", "standardrb"],
|
|
["kotlin"] = ["./gradlew ktlintCheck", "./gradlew detekt"],
|
|
["swift"] = ["swiftformat --lint", "swiftlint"],
|
|
["sql"] = ["check destructive statements and transaction boundaries", "review index, constraint, and view impact"],
|
|
};
|
|
|
|
private static readonly ReadOnlyCollection<CodeLanguageCapability> s_all =
|
|
new(new List<CodeLanguageCapability>
|
|
{
|
|
new(
|
|
"csharp",
|
|
"C# (.NET)",
|
|
[".cs", ".csx", ".csproj", ".sln"],
|
|
[
|
|
"Use dotnet CLI, solution/project files, and NuGet package conventions.",
|
|
"Follow Microsoft naming conventions and prefer targeted edits over broad rewrites.",
|
|
"Verify impact on callers, DI registration, nullable flow, and build configuration."
|
|
],
|
|
LspLanguageId: "csharp",
|
|
ShowInQuickSelect: true,
|
|
QuickSelectKey: "csharp",
|
|
QuickSelectLabel: "C# (.NET)",
|
|
QuickSelectIcon: "\uD83D\uDD39"),
|
|
new(
|
|
"python",
|
|
"Python",
|
|
[".py", ".pyi", ".ipynb"],
|
|
[
|
|
"Use pip/venv or conda only if already available in the environment.",
|
|
"Follow PEP 8, type hints, and module/package boundaries.",
|
|
"Prefer small focused functions and verify import/runtime errors after edits."
|
|
],
|
|
LspLanguageId: "python",
|
|
ShowInQuickSelect: true,
|
|
QuickSelectKey: "python",
|
|
QuickSelectLabel: "Python",
|
|
QuickSelectIcon: "\uD83D\uDC0D"),
|
|
new(
|
|
"java",
|
|
"Java",
|
|
[".java", ".gradle", ".pom"],
|
|
[
|
|
"Use Maven or Gradle conventions already present in the repository.",
|
|
"Follow package structure, visibility rules, and style consistent with the existing codebase.",
|
|
"Check interfaces, implementations, and test fixtures together when modifying shared behavior."
|
|
],
|
|
LspLanguageId: "java",
|
|
ShowInQuickSelect: true,
|
|
QuickSelectKey: "java",
|
|
QuickSelectLabel: "Java",
|
|
QuickSelectIcon: "\u2615"),
|
|
new(
|
|
"cpp",
|
|
"C / C++",
|
|
[".c", ".cc", ".cxx", ".cpp", ".h", ".hh", ".hpp", ".inl"],
|
|
[
|
|
"Respect the repository's existing build system, usually CMake, MSBuild, or compiler-specific scripts.",
|
|
"Be careful with headers, include order, ownership, ABI-sensitive changes, and platform guards.",
|
|
"Validate both declaration and implementation impact when editing shared types."
|
|
],
|
|
LspLanguageId: "cpp",
|
|
ShowInQuickSelect: true,
|
|
QuickSelectKey: "cpp",
|
|
QuickSelectLabel: "C/C++",
|
|
QuickSelectIcon: "\u2699"),
|
|
new(
|
|
"typescript",
|
|
"TypeScript",
|
|
[".ts", ".tsx", ".mts", ".cts"],
|
|
[
|
|
"Use the existing package manager and tsconfig structure.",
|
|
"Prefer explicit types on public boundaries and check build/lint config before changing module format.",
|
|
"Preserve framework conventions already used by the project."
|
|
],
|
|
LspLanguageId: "typescript"),
|
|
new(
|
|
"javascript",
|
|
"JavaScript / Vue",
|
|
[".js", ".jsx", ".mjs", ".cjs", ".vue"],
|
|
[
|
|
"Use the existing Node package manager and lint/format rules.",
|
|
"For Vue, preserve the current component style and API pattern used by the project.",
|
|
"Check module boundaries, imports, and runtime side effects after edits."
|
|
],
|
|
LspLanguageId: "javascript",
|
|
ShowInQuickSelect: true,
|
|
QuickSelectKey: "javascript",
|
|
QuickSelectLabel: "JavaScript / Vue",
|
|
QuickSelectIcon: "\uD83C\uDF10"),
|
|
new(
|
|
"go",
|
|
"Go",
|
|
[".go", ".mod", ".sum"],
|
|
[
|
|
"Preserve package boundaries, error-first flow, and gofmt-style formatting.",
|
|
"Check interfaces, exported identifiers, and concurrency-sensitive changes together."
|
|
],
|
|
LspLanguageId: "go"),
|
|
new(
|
|
"rust",
|
|
"Rust",
|
|
[".rs", ".toml"],
|
|
[
|
|
"Respect Cargo workspace structure, ownership/borrowing rules, and crate boundaries.",
|
|
"Prefer explicit enums/results and verify compiler diagnostics after edits."
|
|
],
|
|
LspLanguageId: "rust"),
|
|
new(
|
|
"php",
|
|
"PHP",
|
|
[".php", ".phtml"],
|
|
[
|
|
"Follow the framework and autoloading structure already present in the project.",
|
|
"Be careful with runtime includes, container wiring, and mixed template/application files."
|
|
],
|
|
LspLanguageId: "php"),
|
|
new(
|
|
"ruby",
|
|
"Ruby",
|
|
[".rb", ".rake", ".gemspec"],
|
|
[
|
|
"Preserve gem structure, Rails or plain Ruby conventions already used in the repository.",
|
|
"Check dynamic dispatch, concerns/modules, and tests together after edits."
|
|
],
|
|
LspLanguageId: "ruby"),
|
|
new(
|
|
"kotlin",
|
|
"Kotlin",
|
|
[".kt", ".kts"],
|
|
[
|
|
"Preserve Gradle structure, package layout, and nullability intent.",
|
|
"Be careful with JVM interop boundaries and Android-specific module structure when present."
|
|
],
|
|
LspLanguageId: "kotlin"),
|
|
new(
|
|
"swift",
|
|
"Swift",
|
|
[".swift"],
|
|
[
|
|
"Preserve target structure, Apple framework imports, and protocol-oriented design already in use.",
|
|
"Check app lifecycle and platform-specific behavior after edits."
|
|
],
|
|
LspLanguageId: "swift"),
|
|
new(
|
|
"scala",
|
|
"Scala",
|
|
[".scala", ".sc"],
|
|
[
|
|
"Respect sbt/module structure, functional style, and existing typeclass or Akka patterns if present.",
|
|
"Keep public APIs simple and avoid unnecessary type-level churn."
|
|
]),
|
|
new(
|
|
"shell",
|
|
"Shell",
|
|
[".sh", ".bash", ".zsh"],
|
|
[
|
|
"Prefer safe quoting, explicit exit handling, and repository-local scripts over one-off inline shell.",
|
|
"Check portability assumptions and environment-specific commands."
|
|
]),
|
|
new(
|
|
"powershell",
|
|
"PowerShell",
|
|
[".ps1", ".psm1", ".psd1", ".bat", ".cmd"],
|
|
[
|
|
"Prefer native PowerShell cmdlets and safe path handling.",
|
|
"Be careful with Windows-specific side effects, quoting, and admin-sensitive operations."
|
|
]),
|
|
new(
|
|
"sql",
|
|
"SQL",
|
|
[".sql"],
|
|
[
|
|
"Preserve migration ordering, dialect assumptions, and transaction boundaries.",
|
|
"Call out destructive DDL, broad DML, and index or constraint impact explicitly."
|
|
]),
|
|
new(
|
|
"web",
|
|
"HTML / CSS / SCSS",
|
|
[".html", ".htm", ".css", ".scss", ".sass", ".less", ".xaml"],
|
|
[
|
|
"Preserve the existing design system, layout structure, and accessibility semantics.",
|
|
"Prefer incremental visual changes and keep selectors/components scoped."
|
|
]),
|
|
new(
|
|
"markup",
|
|
"JSON / YAML / XML / Markdown",
|
|
[".json", ".jsonc", ".xml", ".yaml", ".yml", ".md", ".txt"],
|
|
[
|
|
"Preserve schema shape, indentation style, and comment/document conventions already used in the repository.",
|
|
"Validate references, keys, and generated consumer impact after edits."
|
|
]),
|
|
});
|
|
|
|
private static readonly ReadOnlyDictionary<string, CodeLanguageCapability> s_byKey =
|
|
new(s_all.ToDictionary(x => x.Key, StringComparer.OrdinalIgnoreCase));
|
|
|
|
private static readonly ReadOnlyDictionary<string, CodeLanguageCapability> s_byExtension =
|
|
new(s_all
|
|
.SelectMany(cap => cap.Extensions.Select(ext => new KeyValuePair<string, CodeLanguageCapability>(ext, cap)))
|
|
.ToDictionary(x => x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase));
|
|
|
|
private static readonly HashSet<string> s_codeExtensions = new(
|
|
s_all.SelectMany(cap => cap.Extensions),
|
|
StringComparer.OrdinalIgnoreCase);
|
|
|
|
public static IReadOnlyList<CodeLanguageCapability> All => s_all;
|
|
|
|
public static IReadOnlyCollection<string> CodeExtensions => s_codeExtensions;
|
|
|
|
public static IReadOnlyList<CodeLanguageCapability> QuickSelectLanguages =>
|
|
s_all.Where(x => x.ShowInQuickSelect).ToList();
|
|
|
|
public static IReadOnlyList<CodeLanguageCapability> LspBackedLanguages =>
|
|
s_all.Where(x => !string.IsNullOrWhiteSpace(x.LspLanguageId)).ToList();
|
|
|
|
public static bool IsCodeLikeFile(string? extension)
|
|
=> !string.IsNullOrWhiteSpace(extension) && s_codeExtensions.Contains(extension);
|
|
|
|
public static CodeLanguageCapability? FindByKey(string? key)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(key))
|
|
return null;
|
|
|
|
return s_byKey.TryGetValue(key.Trim(), out var found) ? found : null;
|
|
}
|
|
|
|
public static CodeLanguageCapability? FindByExtension(string? extension)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(extension))
|
|
return null;
|
|
|
|
return s_byExtension.TryGetValue(extension.Trim(), out var found) ? found : null;
|
|
}
|
|
|
|
public static string? DetectLspLanguageId(string? filePath)
|
|
=> FindByExtension(Path.GetExtension(filePath ?? string.Empty))?.LspLanguageId;
|
|
|
|
public static string GetQuickSelectLabel(string? key)
|
|
=> FindByKey(key)?.DisplayName ?? key ?? "Auto";
|
|
|
|
public static string BuildQuickSelectSupportDescription()
|
|
=> string.Join(", ", QuickSelectLanguages.Select(x => x.DisplayName));
|
|
|
|
public static string BuildSelectedLanguagePrompt(string? key)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(key) || string.Equals(key, "auto", StringComparison.OrdinalIgnoreCase))
|
|
return string.Empty;
|
|
|
|
var capability = FindByKey(key);
|
|
if (capability == null)
|
|
return string.Empty;
|
|
|
|
return $"IMPORTANT: User selected language: {capability.DisplayName}. Prioritize this language for code analysis and generation.";
|
|
}
|
|
|
|
public static IEnumerable<string> GetGuidanceLines(string? selectedKey)
|
|
{
|
|
var selected = FindByKey(selectedKey);
|
|
if (selected != null)
|
|
{
|
|
foreach (var line in selected.Guidance)
|
|
yield return $"- {selected.DisplayName}: {line}";
|
|
yield break;
|
|
}
|
|
|
|
foreach (var capability in s_all.Where(x =>
|
|
x.Key is "csharp" or "python" or "java" or "cpp" or "typescript" or "javascript" or "go" or "rust" or "kotlin" or "swift" or "sql"))
|
|
{
|
|
var summary = capability.Guidance.FirstOrDefault();
|
|
if (!string.IsNullOrWhiteSpace(summary))
|
|
yield return $"- {capability.DisplayName}: {summary}";
|
|
}
|
|
}
|
|
|
|
public static string BuildLspSupportDescription()
|
|
=> string.Join(", ", LspBackedLanguages.Select(x => x.DisplayName));
|
|
|
|
public static string BuildStaticSupportDescription()
|
|
=> string.Join(", ", s_all.Select(x => x.DisplayName));
|
|
|
|
public static string BuildSupportSummaryDescription()
|
|
=> $"빠른 선택: {BuildQuickSelectSupportDescription()} | 내장 분석: {BuildStaticSupportDescription()} | LSP 심화 분석: {BuildLspSupportDescription()}";
|
|
|
|
public static string BuildCodeTabSupportDescription()
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.Append("정적 분류/검색/프롬프트 지원: ");
|
|
sb.Append(BuildStaticSupportDescription());
|
|
sb.Append(" | LSP 심화 분석: ");
|
|
sb.Append(BuildLspSupportDescription());
|
|
return sb.ToString();
|
|
}
|
|
|
|
public static IReadOnlyList<string> 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<string> 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<string> 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<string> 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<string>();
|
|
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 (string.Equals(capability.Key, "sql", StringComparison.OrdinalIgnoreCase))
|
|
parts.Add("analysis: detect dialect, script intent, destructive risk, migration order, and object dependencies");
|
|
|
|
if (parts.Count == 0)
|
|
return capability.DisplayName;
|
|
|
|
return $"{capability.DisplayName}: {string.Join(" | ", parts)}";
|
|
}
|
|
|
|
public static IReadOnlyList<string> BuildWorkspaceWorkflowSummaries(
|
|
IEnumerable<string?> extensionsOrKeys,
|
|
string? preferredKey = null,
|
|
int maxLanguages = 3,
|
|
int maxHintsPerKind = 2)
|
|
{
|
|
var results = new List<string>();
|
|
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
void TryAppend(CodeLanguageCapability? capability)
|
|
{
|
|
if (capability == null || !seen.Add(capability.Key))
|
|
return;
|
|
|
|
var summary = BuildWorkflowSummary(capability.Key, maxHintsPerKind);
|
|
if (!string.IsNullOrWhiteSpace(summary))
|
|
results.Add(summary);
|
|
}
|
|
|
|
TryAppend(ResolveCapabilityFromKeyOrExtension(preferredKey));
|
|
|
|
foreach (var value in extensionsOrKeys ?? [])
|
|
{
|
|
TryAppend(ResolveCapabilityFromKeyOrExtension(value));
|
|
if (results.Count >= Math.Max(1, maxLanguages))
|
|
break;
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
public static string BuildFallbackSupportDescription()
|
|
=> "LSP 서버가 없거나 연결되지 않아도 확장자, 매니페스트, build/test/lint 힌트 기반의 정적 fallback 분석을 계속 제공합니다.";
|
|
|
|
public static string BuildFallbackSummary(string? filePathOrExtension)
|
|
{
|
|
var capability = ResolveCapabilityFromKeyOrExtension(filePathOrExtension);
|
|
if (capability != null && string.Equals(capability.Key, "sql", StringComparison.OrdinalIgnoreCase))
|
|
return SqlAnalysisService.BuildFallbackSummary(filePathOrExtension);
|
|
if (capability == null)
|
|
return "정적 fallback: 확장자와 프로젝트 매니페스트를 먼저 확인하고 관련 build/test/lint 힌트를 따라 수동 검증하세요.";
|
|
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine($"정적 fallback 분석: {capability.DisplayName}");
|
|
|
|
if (s_manifestHints.TryGetValue(capability.Key, out var manifests) && manifests.Length > 0)
|
|
sb.AppendLine("주요 매니페스트: " + string.Join(", ", manifests));
|
|
|
|
if (s_buildHints.TryGetValue(capability.Key, out var buildHints) && buildHints.Length > 0)
|
|
sb.AppendLine("권장 build 힌트: " + string.Join(" | ", buildHints));
|
|
|
|
if (s_testHints.TryGetValue(capability.Key, out var testHints) && testHints.Length > 0)
|
|
sb.AppendLine("권장 test 힌트: " + string.Join(" | ", testHints));
|
|
|
|
if (s_lintHints.TryGetValue(capability.Key, out var lintHints) && lintHints.Length > 0)
|
|
sb.AppendLine("권장 lint/format 힌트: " + string.Join(" | ", lintHints));
|
|
|
|
var primaryGuidance = capability.Guidance.FirstOrDefault();
|
|
if (!string.IsNullOrWhiteSpace(primaryGuidance))
|
|
sb.Append("분석 포인트: ").Append(primaryGuidance);
|
|
|
|
return sb.ToString().TrimEnd();
|
|
}
|
|
|
|
private static CodeLanguageCapability? ResolveCapabilityFromKeyOrExtension(string? value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
return null;
|
|
|
|
var normalized = value.Trim();
|
|
var capability = FindByKey(normalized);
|
|
if (capability != null)
|
|
return capability;
|
|
|
|
if (normalized.StartsWith('.'))
|
|
return FindByExtension(normalized);
|
|
|
|
capability = QuickSelectLanguages
|
|
.FirstOrDefault(x => string.Equals(x.QuickSelectKey, normalized, StringComparison.OrdinalIgnoreCase));
|
|
if (capability != null)
|
|
return capability;
|
|
|
|
var extension = Path.GetExtension(normalized);
|
|
return string.IsNullOrWhiteSpace(extension)
|
|
? null
|
|
: FindByExtension(extension);
|
|
}
|
|
|
|
private static void AppendHintBlock(List<string> parts, string label, IReadOnlyList<string> 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)}");
|
|
}
|
|
}
|