3671 lines
120 KiB
C#
Executable File
3671 lines
120 KiB
C#
Executable File
using MarketAlly.AIPlugin;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using Microsoft.CodeAnalysis.MSBuild;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
using System.Xml.Linq;
|
|
using Microsoft.Extensions.Logging;
|
|
using System.Text;
|
|
|
|
namespace MarketAlly.AIPlugin.Analysis.Plugins
|
|
{
|
|
[AIPlugin("ModularMap", "Generates visual dependency maps and analyzes modular architecture patterns")]
|
|
public class ModularMapPlugin : IAIPlugin
|
|
{
|
|
private readonly ILogger<ModularMapPlugin>? _logger;
|
|
|
|
public ModularMapPlugin(ILogger<ModularMapPlugin>? logger = null)
|
|
{
|
|
_logger = logger;
|
|
}
|
|
|
|
[AIParameter("Full path to the project or solution directory", required: true)]
|
|
public string ProjectPath { get; set; } = string.Empty;
|
|
|
|
[AIParameter("Map output format: json, mermaid, cytoscape, graphviz", required: false)]
|
|
public string OutputFormat { get; set; } = "json";
|
|
|
|
[AIParameter("Include external dependencies (NuGet packages)", required: false)]
|
|
public bool IncludeExternalDependencies { get; set; } = false;
|
|
|
|
[AIParameter("Include internal project references only", required: false)]
|
|
public bool InternalOnly { get; set; } = true;
|
|
|
|
[AIParameter("Analyze coupling and cohesion metrics", required: false)]
|
|
public bool AnalyzeCoupling { get; set; } = true;
|
|
|
|
[AIParameter("Generate architectural insights and recommendations", required: false)]
|
|
public bool GenerateInsights { get; set; } = true;
|
|
|
|
[AIParameter("Maximum dependency depth to analyze", required: false)]
|
|
public int MaxDepth { get; set; } = 5;
|
|
|
|
[AIParameter("Filter by namespace pattern (e.g., 'MyCompany.*')", required: false)]
|
|
public string NamespaceFilter { get; set; } = string.Empty;
|
|
|
|
[AIParameter("Include method-level dependencies", required: false)]
|
|
public bool IncludeMethodLevel { get; set; } = false;
|
|
|
|
[AIParameter("Output file path for the map", required: false)]
|
|
public string OutputPath { get; set; } = string.Empty;
|
|
|
|
[AIParameter("Enable advanced module identification and grouping", required: false)]
|
|
public bool EnableModuleGrouping { get; set; } = true;
|
|
|
|
[AIParameter("Module grouping strategy: namespace, folder, feature, auto", required: false)]
|
|
public string ModuleGroupingStrategy { get; set; } = "auto";
|
|
|
|
[AIParameter("Detect platform-specific modules", required: false)]
|
|
public bool DetectPlatformModules { get; set; } = true;
|
|
|
|
[AIParameter("Include module entry points and public interfaces", required: false)]
|
|
public bool IncludeEntryPoints { get; set; } = true;
|
|
|
|
[AIParameter("Generate reusable module definitions", required: false)]
|
|
public bool GenerateModuleDefinitions { get; set; } = true;
|
|
|
|
public IReadOnlyDictionary<string, Type> SupportedParameters => new Dictionary<string, Type>
|
|
{
|
|
["projectPath"] = typeof(string),
|
|
["outputFormat"] = typeof(string),
|
|
["includeExternalDependencies"] = typeof(bool),
|
|
["internalOnly"] = typeof(bool),
|
|
["analyzeCoupling"] = typeof(bool),
|
|
["generateInsights"] = typeof(bool),
|
|
["maxDepth"] = typeof(int),
|
|
["namespaceFilter"] = typeof(string),
|
|
["includeMethodLevel"] = typeof(bool),
|
|
["outputPath"] = typeof(string),
|
|
["detectPatterns"] = typeof(bool),
|
|
["enableModuleGrouping"] = typeof(bool),
|
|
["moduleGroupingStrategy"] = typeof(string),
|
|
["detectPlatformModules"] = typeof(bool),
|
|
["includeEntryPoints"] = typeof(bool),
|
|
["generateModuleDefinitions"] = typeof(bool),
|
|
["generateScaffoldingMetadata"] = typeof(bool),
|
|
["includeLlmDescriptions"] = typeof(bool),
|
|
["analyzeModuleTags"] = typeof(bool),
|
|
["includeModuleFlags"] = typeof(bool)
|
|
};
|
|
|
|
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
|
|
{
|
|
try
|
|
{
|
|
// Extract parameters
|
|
string projectPath = parameters["projectPath"].ToString() ?? string.Empty;
|
|
string outputFormat = parameters.GetValueOrDefault("outputFormat", "json")?.ToString() ?? "json";
|
|
bool includeExternal = Convert.ToBoolean(parameters.GetValueOrDefault("includeExternalDependencies", false));
|
|
bool internalOnly = Convert.ToBoolean(parameters.GetValueOrDefault("internalOnly", true));
|
|
bool analyzeCoupling = Convert.ToBoolean(parameters.GetValueOrDefault("analyzeCoupling", true));
|
|
bool generateInsights = Convert.ToBoolean(parameters.GetValueOrDefault("generateInsights", true));
|
|
int maxDepth = Convert.ToInt32(parameters.GetValueOrDefault("maxDepth", 5));
|
|
string? namespaceFilter = parameters.GetValueOrDefault("namespaceFilter", string.Empty)?.ToString();
|
|
bool includeMethodLevel = Convert.ToBoolean(parameters.GetValueOrDefault("includeMethodLevel", false));
|
|
string? outputPath = parameters.GetValueOrDefault("outputPath", string.Empty)?.ToString();
|
|
bool detectPatterns = Convert.ToBoolean(parameters.GetValueOrDefault("detectPatterns", true));
|
|
bool enableModuleGrouping = Convert.ToBoolean(parameters.GetValueOrDefault("enableModuleGrouping", true));
|
|
string moduleGroupingStrategy = parameters.GetValueOrDefault("moduleGroupingStrategy", "auto")?.ToString() ?? "auto";
|
|
bool detectPlatformModules = Convert.ToBoolean(parameters.GetValueOrDefault("detectPlatformModules", true));
|
|
bool includeEntryPoints = Convert.ToBoolean(parameters.GetValueOrDefault("includeEntryPoints", true));
|
|
bool generateModuleDefinitions = Convert.ToBoolean(parameters.GetValueOrDefault("generateModuleDefinitions", true));
|
|
bool generateScaffoldingMetadata = Convert.ToBoolean(parameters.GetValueOrDefault("generateScaffoldingMetadata", true));
|
|
bool includeLlmDescriptions = Convert.ToBoolean(parameters.GetValueOrDefault("includeLlmDescriptions", true));
|
|
bool analyzeModuleTags = Convert.ToBoolean(parameters.GetValueOrDefault("analyzeModuleTags", true));
|
|
bool includeModuleFlags = Convert.ToBoolean(parameters.GetValueOrDefault("includeModuleFlags", true));
|
|
|
|
// Validate project path
|
|
if (!Directory.Exists(projectPath) && !File.Exists(projectPath))
|
|
{
|
|
return new AIPluginResult(
|
|
new DirectoryNotFoundException($"Project path not found: {projectPath}"),
|
|
"Invalid project path"
|
|
);
|
|
}
|
|
|
|
_logger?.LogInformation("Starting modular map analysis for {ProjectPath}", projectPath);
|
|
|
|
// Analyze project structure
|
|
var analysisResult = await AnalyzeProjectStructureAsync(projectPath, new AnalysisOptions
|
|
{
|
|
IncludeExternalDependencies = includeExternal,
|
|
InternalOnly = internalOnly,
|
|
MaxDepth = maxDepth,
|
|
NamespaceFilter = namespaceFilter,
|
|
IncludeMethodLevel = includeMethodLevel,
|
|
EnableModuleGrouping = enableModuleGrouping,
|
|
ModuleGroupingStrategy = moduleGroupingStrategy,
|
|
DetectPlatformModules = detectPlatformModules,
|
|
IncludeEntryPoints = includeEntryPoints,
|
|
GenerateModuleDefinitions = generateModuleDefinitions,
|
|
GenerateScaffoldingMetadata = generateScaffoldingMetadata,
|
|
IncludeLlmDescriptions = includeLlmDescriptions,
|
|
AnalyzeModuleTags = analyzeModuleTags,
|
|
IncludeModuleFlags = includeModuleFlags
|
|
});
|
|
|
|
// Generate modular structure if requested
|
|
var modularStructure = enableModuleGrouping ?
|
|
await GenerateModularStructureAsync(analysisResult, options: new ModularOptions
|
|
{
|
|
GroupingStrategy = moduleGroupingStrategy,
|
|
DetectPlatformModules = detectPlatformModules,
|
|
IncludeEntryPoints = includeEntryPoints,
|
|
GenerateScaffoldingMetadata = generateScaffoldingMetadata,
|
|
IncludeLlmDescriptions = includeLlmDescriptions,
|
|
AnalyzeModuleTags = analyzeModuleTags,
|
|
IncludeModuleFlags = includeModuleFlags
|
|
}) : null;
|
|
|
|
// Generate dependency map (enhanced with modular structure if available)
|
|
var dependencyMap = await GenerateDependencyMapAsync(analysisResult, outputFormat, modularStructure);
|
|
|
|
// Analyze coupling metrics if requested
|
|
var couplingMetrics = analyzeCoupling ? await AnalyzeCouplingMetricsAsync(analysisResult) : null;
|
|
|
|
// Detect architectural patterns if requested
|
|
var architecturalPatterns = detectPatterns ? await DetectArchitecturalPatternsAsync(analysisResult) : null;
|
|
|
|
// Generate insights and recommendations
|
|
var insights = generateInsights ? await GenerateArchitecturalInsightsAsync(analysisResult, couplingMetrics, architecturalPatterns) : null;
|
|
|
|
// Save output file if path provided
|
|
string? savedPath = null;
|
|
if (!string.IsNullOrEmpty(outputPath))
|
|
{
|
|
savedPath = await SaveMapToFileAsync(dependencyMap, outputPath, outputFormat);
|
|
}
|
|
|
|
var result = new
|
|
{
|
|
ProjectPath = projectPath,
|
|
OutputFormat = outputFormat,
|
|
DependencyMap = dependencyMap,
|
|
ModularStructure = modularStructure,
|
|
CouplingMetrics = couplingMetrics,
|
|
ArchitecturalPatterns = architecturalPatterns,
|
|
Insights = insights,
|
|
Statistics = new
|
|
{
|
|
TotalModules = modularStructure?.Modules?.Count ?? analysisResult.Modules.Count,
|
|
TotalLogicalModules = modularStructure?.Modules?.Count ?? 0,
|
|
TotalDependencies = analysisResult.Dependencies.Count,
|
|
MaxDepthReached = analysisResult.MaxDepthReached,
|
|
ExternalDependencies = analysisResult.ExternalDependencies.Count,
|
|
PlatformSpecificModules = modularStructure?.Modules?.Count(m => m.PlatformSpecific) ?? 0
|
|
},
|
|
OutputPath = savedPath
|
|
};
|
|
|
|
_logger?.LogInformation("Modular map analysis completed successfully");
|
|
return new AIPluginResult(result, "Modular map generation completed successfully");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogError(ex, "Failed to generate modular map");
|
|
return new AIPluginResult(ex, $"Failed to generate modular map: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task<ProjectAnalysisResult> AnalyzeProjectStructureAsync(string projectPath, AnalysisOptions options)
|
|
{
|
|
var result = new ProjectAnalysisResult { ProjectPath = projectPath };
|
|
var processedProjects = new HashSet<string>();
|
|
var projectQueue = new Queue<(string path, int depth)>();
|
|
|
|
try
|
|
{
|
|
// Find initial projects to analyze
|
|
var initialProjects = await DiscoverProjectsAsync(projectPath);
|
|
foreach (var project in initialProjects)
|
|
{
|
|
projectQueue.Enqueue((project, 0));
|
|
}
|
|
|
|
// Process projects with depth limiting
|
|
while (projectQueue.Count > 0)
|
|
{
|
|
var (currentPath, depth) = projectQueue.Dequeue();
|
|
|
|
if (depth > options.MaxDepth || processedProjects.Contains(currentPath))
|
|
continue;
|
|
|
|
processedProjects.Add(currentPath);
|
|
result.MaxDepthReached = Math.Max(result.MaxDepthReached, depth);
|
|
|
|
var module = await AnalyzeProjectAsync(currentPath, options);
|
|
if (module != null && ShouldIncludeModule(module, options))
|
|
{
|
|
result.Modules.Add(module);
|
|
|
|
// Add project references to queue for deeper analysis
|
|
var projectRefs = await GetProjectReferencesAsync(currentPath);
|
|
foreach (var refPath in projectRefs)
|
|
{
|
|
if (!processedProjects.Contains(refPath))
|
|
{
|
|
projectQueue.Enqueue((refPath, depth + 1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build dependency relationships
|
|
result.Dependencies = await BuildDependencyGraphAsync(result.Modules, options);
|
|
|
|
// Analyze external dependencies if requested
|
|
if (options.IncludeExternalDependencies)
|
|
{
|
|
result.ExternalDependencies = await AnalyzeExternalDependenciesAsync(result.Modules);
|
|
}
|
|
|
|
_logger?.LogInformation("Analyzed {ModuleCount} modules with {DependencyCount} dependencies",
|
|
result.Modules.Count, result.Dependencies.Count);
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogError(ex, "Error analyzing project structure");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private async Task<List<string>> DiscoverProjectsAsync(string path)
|
|
{
|
|
var projects = new List<string>();
|
|
|
|
if (File.Exists(path))
|
|
{
|
|
var extension = Path.GetExtension(path).ToLower();
|
|
if (extension == ".sln")
|
|
{
|
|
// Parse solution file
|
|
projects.AddRange(await ParseSolutionFileAsync(path));
|
|
}
|
|
else if (extension == ".csproj" || extension == ".vbproj" || extension == ".fsproj")
|
|
{
|
|
projects.Add(path);
|
|
}
|
|
}
|
|
else if (Directory.Exists(path))
|
|
{
|
|
// Search for project files
|
|
var projectFiles = Directory.GetFiles(path, "*.csproj", SearchOption.AllDirectories)
|
|
.Concat(Directory.GetFiles(path, "*.vbproj", SearchOption.AllDirectories))
|
|
.Concat(Directory.GetFiles(path, "*.fsproj", SearchOption.AllDirectories));
|
|
|
|
projects.AddRange(projectFiles);
|
|
|
|
// Also check for solution files
|
|
var solutionFiles = Directory.GetFiles(path, "*.sln", SearchOption.TopDirectoryOnly);
|
|
foreach (var sln in solutionFiles)
|
|
{
|
|
projects.AddRange(await ParseSolutionFileAsync(sln));
|
|
}
|
|
}
|
|
|
|
return projects.Distinct().ToList();
|
|
}
|
|
|
|
private async Task<List<string>> ParseSolutionFileAsync(string solutionPath)
|
|
{
|
|
var projects = new List<string>();
|
|
var solutionDir = Path.GetDirectoryName(solutionPath);
|
|
|
|
var content = await File.ReadAllTextAsync(solutionPath);
|
|
var projectRegex = new Regex(@"Project\(""\{[^}]+\}""\)\s*=\s*""[^""]*"",\s*""([^""]+)"",\s*""\{[^}]+\}""");
|
|
|
|
foreach (Match match in projectRegex.Matches(content))
|
|
{
|
|
var relativePath = match.Groups[1].Value;
|
|
var absolutePath = Path.GetFullPath(Path.Combine(solutionDir ?? string.Empty, relativePath));
|
|
|
|
if (File.Exists(absolutePath) && IsProjectFile(absolutePath))
|
|
{
|
|
projects.Add(absolutePath);
|
|
}
|
|
}
|
|
|
|
return projects;
|
|
}
|
|
|
|
private bool IsProjectFile(string path)
|
|
{
|
|
var extension = Path.GetExtension(path).ToLower();
|
|
return extension == ".csproj" || extension == ".vbproj" || extension == ".fsproj";
|
|
}
|
|
|
|
private async Task<ModuleInfo?> AnalyzeProjectAsync(string projectPath, AnalysisOptions options)
|
|
{
|
|
try
|
|
{
|
|
var projectName = Path.GetFileNameWithoutExtension(projectPath);
|
|
var projectDir = Path.GetDirectoryName(projectPath) ?? string.Empty;
|
|
|
|
// Parse project file
|
|
var projectXml = await File.ReadAllTextAsync(projectPath);
|
|
var doc = XDocument.Parse(projectXml);
|
|
|
|
// Get project type and target framework
|
|
var projectType = DetermineProjectType(doc);
|
|
var targetFramework = doc.Descendants("TargetFramework").FirstOrDefault()?.Value ??
|
|
doc.Descendants("TargetFrameworks").FirstOrDefault()?.Value?.Split(';').FirstOrDefault() ??
|
|
"unknown";
|
|
|
|
// Analyze source code
|
|
var sourceFiles = Directory.GetFiles(projectDir, "*.cs", SearchOption.AllDirectories)
|
|
.Where(f => !f.Contains("bin") && !f.Contains("obj"))
|
|
.ToList();
|
|
|
|
var namespaces = new HashSet<string>();
|
|
var classes = new List<string>();
|
|
var totalLines = 0;
|
|
|
|
foreach (var file in sourceFiles)
|
|
{
|
|
try
|
|
{
|
|
var sourceCode = await File.ReadAllTextAsync(file);
|
|
totalLines += sourceCode.Split('\n').Length;
|
|
|
|
var tree = CSharpSyntaxTree.ParseText(sourceCode);
|
|
var root = await tree.GetRootAsync();
|
|
|
|
// Extract namespaces
|
|
var namespaceDeclarations = root.DescendantNodes().OfType<NamespaceDeclarationSyntax>();
|
|
foreach (var ns in namespaceDeclarations)
|
|
{
|
|
if (ns.Name != null)
|
|
namespaces.Add(ns.Name.ToString());
|
|
}
|
|
|
|
// Extract classes
|
|
var classDeclarations = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
|
|
foreach (var cls in classDeclarations)
|
|
{
|
|
classes.Add(cls.Identifier.ValueText);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning("Failed to parse file {File}: {Error}", file, ex.Message);
|
|
}
|
|
}
|
|
|
|
var primaryNamespace = namespaces.FirstOrDefault() ?? projectName;
|
|
|
|
return new ModuleInfo
|
|
{
|
|
Name = projectName,
|
|
Namespace = primaryNamespace,
|
|
Type = projectType,
|
|
LineCount = totalLines,
|
|
ClassCount = classes.Count,
|
|
TargetFramework = targetFramework,
|
|
ProjectPath = projectPath,
|
|
SourceFiles = sourceFiles,
|
|
Namespaces = namespaces.ToList(),
|
|
Classes = classes
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning("Failed to analyze project {Project}: {Error}", projectPath, ex.Message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private string DetermineProjectType(XDocument projectDoc)
|
|
{
|
|
var outputType = projectDoc.Descendants("OutputType").FirstOrDefault()?.Value?.ToLower();
|
|
var useWebSdk = projectDoc.Root?.Attribute("Sdk")?.Value?.Contains("Web") == true;
|
|
var hasPackageRefs = projectDoc.Descendants("PackageReference").Any();
|
|
|
|
return outputType switch
|
|
{
|
|
"exe" when useWebSdk => "WebAPI",
|
|
"exe" => "Console",
|
|
"library" when useWebSdk => "WebLibrary",
|
|
"library" => "Library",
|
|
"winexe" => "WinForms",
|
|
_ when useWebSdk => "Web",
|
|
_ => "Library"
|
|
};
|
|
}
|
|
|
|
private bool ShouldIncludeModule(ModuleInfo module, AnalysisOptions options)
|
|
{
|
|
if (string.IsNullOrEmpty(options.NamespaceFilter))
|
|
return true;
|
|
|
|
var pattern = options.NamespaceFilter.Replace("*", ".*");
|
|
var regex = new Regex(pattern, RegexOptions.IgnoreCase);
|
|
|
|
return module.Namespaces.Any(ns => regex.IsMatch(ns));
|
|
}
|
|
|
|
private async Task<List<string>> GetProjectReferencesAsync(string projectPath)
|
|
{
|
|
var references = new List<string>();
|
|
|
|
try
|
|
{
|
|
var projectXml = await File.ReadAllTextAsync(projectPath);
|
|
var doc = XDocument.Parse(projectXml);
|
|
var projectDir = Path.GetDirectoryName(projectPath) ?? string.Empty;
|
|
|
|
var projectRefs = doc.Descendants("ProjectReference");
|
|
foreach (var projRef in projectRefs)
|
|
{
|
|
var include = projRef.Attribute("Include")?.Value;
|
|
if (!string.IsNullOrEmpty(include))
|
|
{
|
|
var absolutePath = Path.GetFullPath(Path.Combine(projectDir, include));
|
|
if (File.Exists(absolutePath))
|
|
{
|
|
references.Add(absolutePath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning("Failed to get project references for {Project}: {Error}", projectPath, ex.Message);
|
|
}
|
|
|
|
return references;
|
|
}
|
|
|
|
private async Task<List<DependencyInfo>> BuildDependencyGraphAsync(List<ModuleInfo> modules, AnalysisOptions options)
|
|
{
|
|
var dependencies = new List<DependencyInfo>();
|
|
var modulesByPath = modules.ToDictionary(m => m.ProjectPath, m => m);
|
|
|
|
foreach (var module in modules)
|
|
{
|
|
try
|
|
{
|
|
// Get project references
|
|
var projectRefs = await GetProjectReferencesAsync(module.ProjectPath);
|
|
foreach (var refPath in projectRefs)
|
|
{
|
|
if (modulesByPath.TryGetValue(refPath, out var targetModule))
|
|
{
|
|
var strength = await CalculateDependencyStrength(module, targetModule, options);
|
|
dependencies.Add(new DependencyInfo
|
|
{
|
|
From = module.Name,
|
|
To = targetModule.Name,
|
|
Type = "ProjectReference",
|
|
Strength = strength,
|
|
ReferenceCount = await CountReferences(module, targetModule)
|
|
});
|
|
}
|
|
}
|
|
|
|
// Analyze using statements for namespace dependencies if method-level analysis is enabled
|
|
if (options.IncludeMethodLevel)
|
|
{
|
|
var namespaceDependencies = await AnalyzeNamespaceDependencies(module, modules);
|
|
dependencies.AddRange(namespaceDependencies);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning("Failed to build dependencies for {Module}: {Error}", module.Name, ex.Message);
|
|
}
|
|
}
|
|
|
|
return dependencies;
|
|
}
|
|
|
|
private async Task<string> CalculateDependencyStrength(ModuleInfo from, ModuleInfo to, AnalysisOptions options)
|
|
{
|
|
try
|
|
{
|
|
var referenceCount = await CountReferences(from, to);
|
|
|
|
return referenceCount switch
|
|
{
|
|
> 50 => "Strong",
|
|
> 10 => "Medium",
|
|
_ => "Weak"
|
|
};
|
|
}
|
|
catch
|
|
{
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
private async Task<int> CountReferences(ModuleInfo from, ModuleInfo to)
|
|
{
|
|
int count = 0;
|
|
|
|
foreach (var sourceFile in from.SourceFiles)
|
|
{
|
|
try
|
|
{
|
|
var content = await File.ReadAllTextAsync(sourceFile);
|
|
var tree = CSharpSyntaxTree.ParseText(content);
|
|
var root = await tree.GetRootAsync();
|
|
|
|
// Count using directives
|
|
var usingDirectives = root.DescendantNodes().OfType<UsingDirectiveSyntax>();
|
|
foreach (var usingDirective in usingDirectives)
|
|
{
|
|
var namespaceName = usingDirective.Name?.ToString();
|
|
if (to.Namespaces.Any(ns => namespaceName?.StartsWith(ns) == true))
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
// Count qualified name references
|
|
var identifierNames = root.DescendantNodes().OfType<IdentifierNameSyntax>();
|
|
foreach (var identifier in identifierNames)
|
|
{
|
|
if (to.Classes.Contains(identifier.Identifier.ValueText))
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning("Failed to count references in {File}: {Error}", sourceFile, ex.Message);
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
private async Task<List<DependencyInfo>> AnalyzeNamespaceDependencies(ModuleInfo module, List<ModuleInfo> allModules)
|
|
{
|
|
var dependencies = new List<DependencyInfo>();
|
|
var targetModulesByNamespace = allModules
|
|
.SelectMany(m => m.Namespaces.Select(ns => new { Namespace = ns, Module = m }))
|
|
.GroupBy(x => x.Namespace)
|
|
.ToDictionary(g => g.Key, g => g.Select(x => x.Module).ToList());
|
|
|
|
foreach (var sourceFile in module.SourceFiles)
|
|
{
|
|
try
|
|
{
|
|
var content = await File.ReadAllTextAsync(sourceFile);
|
|
var tree = CSharpSyntaxTree.ParseText(content);
|
|
var root = await tree.GetRootAsync();
|
|
|
|
var usingDirectives = root.DescendantNodes().OfType<UsingDirectiveSyntax>();
|
|
foreach (var usingDirective in usingDirectives)
|
|
{
|
|
var namespaceName = usingDirective.Name?.ToString();
|
|
if (!string.IsNullOrEmpty(namespaceName) && targetModulesByNamespace.ContainsKey(namespaceName))
|
|
{
|
|
foreach (var targetModule in targetModulesByNamespace[namespaceName])
|
|
{
|
|
if (targetModule.Name != module.Name)
|
|
{
|
|
var existing = dependencies.FirstOrDefault(d => d.From == module.Name && d.To == targetModule.Name && d.Type == "NamespaceReference");
|
|
if (existing == null)
|
|
{
|
|
dependencies.Add(new DependencyInfo
|
|
{
|
|
From = module.Name,
|
|
To = targetModule.Name,
|
|
Type = "NamespaceReference",
|
|
Strength = "Weak",
|
|
ReferenceCount = 1
|
|
});
|
|
}
|
|
else
|
|
{
|
|
existing.ReferenceCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning("Failed to analyze namespace dependencies in {File}: {Error}", sourceFile, ex.Message);
|
|
}
|
|
}
|
|
|
|
return dependencies;
|
|
}
|
|
|
|
private async Task<List<ExternalDependencyInfo>> AnalyzeExternalDependenciesAsync(List<ModuleInfo> modules)
|
|
{
|
|
var externalDeps = new List<ExternalDependencyInfo>();
|
|
var depsByName = new Dictionary<string, ExternalDependencyInfo>();
|
|
|
|
foreach (var module in modules)
|
|
{
|
|
try
|
|
{
|
|
var projectXml = await File.ReadAllTextAsync(module.ProjectPath);
|
|
var doc = XDocument.Parse(projectXml);
|
|
|
|
var packageRefs = doc.Descendants("PackageReference");
|
|
foreach (var packageRef in packageRefs)
|
|
{
|
|
var name = packageRef.Attribute("Include")?.Value;
|
|
var version = packageRef.Attribute("Version")?.Value ??
|
|
packageRef.Element("Version")?.Value;
|
|
|
|
if (!string.IsNullOrEmpty(name))
|
|
{
|
|
if (!depsByName.TryGetValue(name, out var existingDep))
|
|
{
|
|
existingDep = new ExternalDependencyInfo
|
|
{
|
|
Name = name,
|
|
Version = version ?? "unknown",
|
|
Type = "NuGet"
|
|
};
|
|
depsByName[name] = existingDep;
|
|
externalDeps.Add(existingDep);
|
|
}
|
|
existingDep.UsedBy.Add(module.Name);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning("Failed to analyze external dependencies for {Module}: {Error}", module.Name, ex.Message);
|
|
}
|
|
}
|
|
|
|
return externalDeps;
|
|
}
|
|
|
|
private async Task<CouplingMetrics> AnalyzeCouplingMetricsAsync(ProjectAnalysisResult analysis)
|
|
{
|
|
var metrics = new CouplingMetrics();
|
|
var moduleMetrics = new Dictionary<string, ModuleCouplingMetrics>();
|
|
|
|
// Calculate coupling metrics for each module
|
|
foreach (var module in analysis.Modules)
|
|
{
|
|
var afferentCoupling = analysis.Dependencies.Count(d => d.To == module.Name);
|
|
var efferentCoupling = analysis.Dependencies.Count(d => d.From == module.Name);
|
|
var totalCoupling = afferentCoupling + efferentCoupling;
|
|
var instability = totalCoupling == 0 ? 0 : (double)efferentCoupling / totalCoupling;
|
|
|
|
// Calculate abstractness (simplified - ratio of interfaces/abstract classes)
|
|
var abstractness = await CalculateAbstractnessAsync(module);
|
|
|
|
// Distance from main sequence: D = |A + I - 1|
|
|
var distance = Math.Abs(abstractness + instability - 1);
|
|
|
|
var moduleCoupling = new ModuleCouplingMetrics
|
|
{
|
|
AfferentCoupling = afferentCoupling,
|
|
EfferentCoupling = efferentCoupling,
|
|
Instability = instability,
|
|
Abstractness = abstractness,
|
|
Distance = distance
|
|
};
|
|
|
|
moduleMetrics[module.Name] = moduleCoupling;
|
|
metrics.InstabilityScores[module.Name] = instability;
|
|
}
|
|
|
|
// Calculate overall metrics
|
|
metrics.OverallCouplingScore = moduleMetrics.Values.Average(m => m.Distance);
|
|
|
|
// Identify highly coupled modules (instability > 0.7)
|
|
metrics.HighlyCoupledModules = moduleMetrics
|
|
.Where(kvp => kvp.Value.Instability > 0.7)
|
|
.Select(kvp => kvp.Key)
|
|
.ToList();
|
|
|
|
// Identify loosely coupled modules (instability < 0.3)
|
|
metrics.LooselyCoupledModules = moduleMetrics
|
|
.Where(kvp => kvp.Value.Instability < 0.3)
|
|
.Select(kvp => kvp.Key)
|
|
.ToList();
|
|
|
|
// Detect circular dependencies
|
|
metrics.CircularDependencies = await DetectCircularDependenciesAsync(analysis);
|
|
|
|
// Generate recommendations
|
|
metrics.Recommendations = GenerateCouplingRecommendations(moduleMetrics, metrics);
|
|
|
|
await Task.CompletedTask;
|
|
return metrics;
|
|
}
|
|
|
|
private async Task<double> CalculateAbstractnessAsync(ModuleInfo module)
|
|
{
|
|
int totalTypes = 0;
|
|
int abstractTypes = 0;
|
|
|
|
foreach (var sourceFile in module.SourceFiles)
|
|
{
|
|
try
|
|
{
|
|
var content = await File.ReadAllTextAsync(sourceFile);
|
|
var tree = CSharpSyntaxTree.ParseText(content);
|
|
var root = await tree.GetRootAsync();
|
|
|
|
// Count classes and interfaces
|
|
var classDeclarations = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
|
|
var interfaceDeclarations = root.DescendantNodes().OfType<InterfaceDeclarationSyntax>();
|
|
|
|
foreach (var classDecl in classDeclarations)
|
|
{
|
|
totalTypes++;
|
|
if (classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.AbstractKeyword)))
|
|
{
|
|
abstractTypes++;
|
|
}
|
|
}
|
|
|
|
foreach (var interfaceDecl in interfaceDeclarations)
|
|
{
|
|
totalTypes++;
|
|
abstractTypes++; // Interfaces are considered abstract
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning("Failed to calculate abstractness for {File}: {Error}", sourceFile, ex.Message);
|
|
}
|
|
}
|
|
|
|
return totalTypes == 0 ? 0 : (double)abstractTypes / totalTypes;
|
|
}
|
|
|
|
private async Task<List<string>> DetectCircularDependenciesAsync(ProjectAnalysisResult analysis)
|
|
{
|
|
var circularDeps = new List<string>();
|
|
var graph = new Dictionary<string, List<string>>();
|
|
|
|
// Build adjacency list
|
|
foreach (var module in analysis.Modules)
|
|
{
|
|
graph[module.Name] = new List<string>();
|
|
}
|
|
|
|
foreach (var dependency in analysis.Dependencies)
|
|
{
|
|
if (graph.ContainsKey(dependency.From))
|
|
{
|
|
graph[dependency.From].Add(dependency.To);
|
|
}
|
|
}
|
|
|
|
// Use DFS to detect cycles
|
|
var visited = new HashSet<string>();
|
|
var recursionStack = new HashSet<string>();
|
|
|
|
foreach (var module in analysis.Modules)
|
|
{
|
|
if (!visited.Contains(module.Name))
|
|
{
|
|
var cycles = await DetectCyclesAsync(module.Name, graph, visited, recursionStack, new List<string>());
|
|
circularDeps.AddRange(cycles);
|
|
}
|
|
}
|
|
|
|
return circularDeps.Distinct().ToList();
|
|
}
|
|
|
|
private async Task<List<string>> DetectCyclesAsync(string node, Dictionary<string, List<string>> graph,
|
|
HashSet<string> visited, HashSet<string> recursionStack, List<string> currentPath)
|
|
{
|
|
var cycles = new List<string>();
|
|
visited.Add(node);
|
|
recursionStack.Add(node);
|
|
currentPath.Add(node);
|
|
|
|
if (graph.ContainsKey(node))
|
|
{
|
|
foreach (var neighbor in graph[node])
|
|
{
|
|
if (!visited.Contains(neighbor))
|
|
{
|
|
var subCycles = await DetectCyclesAsync(neighbor, graph, visited, recursionStack, new List<string>(currentPath));
|
|
cycles.AddRange(subCycles);
|
|
}
|
|
else if (recursionStack.Contains(neighbor))
|
|
{
|
|
// Found a cycle
|
|
var cycleStart = currentPath.IndexOf(neighbor);
|
|
var cycle = currentPath.Skip(cycleStart).Append(neighbor);
|
|
cycles.Add(string.Join(" -> ", cycle));
|
|
}
|
|
}
|
|
}
|
|
|
|
recursionStack.Remove(node);
|
|
return cycles;
|
|
}
|
|
|
|
private List<string> GenerateCouplingRecommendations(Dictionary<string, ModuleCouplingMetrics> moduleMetrics, CouplingMetrics metrics)
|
|
{
|
|
var recommendations = new List<string>();
|
|
|
|
// High coupling recommendations
|
|
foreach (var highlyCouple in metrics.HighlyCoupledModules)
|
|
{
|
|
if (moduleMetrics.TryGetValue(highlyCouple, out var coupling))
|
|
{
|
|
if (coupling.EfferentCoupling > coupling.AfferentCoupling)
|
|
{
|
|
recommendations.Add($"Module '{highlyCouple}' has high efferent coupling ({coupling.EfferentCoupling}). Consider extracting interfaces or breaking into smaller modules.");
|
|
}
|
|
else
|
|
{
|
|
recommendations.Add($"Module '{highlyCouple}' has high afferent coupling ({coupling.AfferentCoupling}). This module is heavily depended upon - ensure it has a stable interface.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Distance from main sequence recommendations
|
|
foreach (var moduleMetric in moduleMetrics)
|
|
{
|
|
if (moduleMetric.Value.Distance > 0.7)
|
|
{
|
|
if (moduleMetric.Value.Abstractness < 0.3 && moduleMetric.Value.Instability > 0.7)
|
|
{
|
|
recommendations.Add($"Module '{moduleMetric.Key}' is in the 'Zone of Pain' (concrete and unstable). Consider increasing abstraction through interfaces.");
|
|
}
|
|
else if (moduleMetric.Value.Abstractness > 0.7 && moduleMetric.Value.Instability < 0.3)
|
|
{
|
|
recommendations.Add($"Module '{moduleMetric.Key}' is in the 'Zone of Uselessness' (abstract but not used). Consider removing unused abstractions.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Circular dependency recommendations
|
|
if (metrics.CircularDependencies.Any())
|
|
{
|
|
recommendations.Add($"Found {metrics.CircularDependencies.Count} circular dependencies. Consider dependency inversion or extracting common interfaces.");
|
|
}
|
|
|
|
// General recommendations
|
|
if (metrics.OverallCouplingScore > 0.6)
|
|
{
|
|
recommendations.Add("Overall coupling is high. Consider applying SOLID principles and dependency injection patterns.");
|
|
}
|
|
|
|
return recommendations;
|
|
}
|
|
|
|
private async Task<ArchitecturalPatterns> DetectArchitecturalPatternsAsync(ProjectAnalysisResult analysis)
|
|
{
|
|
var patterns = new ArchitecturalPatterns();
|
|
var layerScores = new Dictionary<string, double>();
|
|
|
|
// Detect layered architecture
|
|
var layeredScore = await DetectLayeredArchitectureAsync(analysis);
|
|
layerScores["Layered"] = layeredScore;
|
|
|
|
// Detect clean architecture
|
|
var cleanScore = await DetectCleanArchitectureAsync(analysis);
|
|
layerScores["Clean"] = cleanScore;
|
|
|
|
// Detect hexagonal architecture
|
|
var hexagonalScore = await DetectHexagonalArchitectureAsync(analysis);
|
|
layerScores["Hexagonal"] = hexagonalScore;
|
|
|
|
// Detect microservices patterns
|
|
var microservicesScore = await DetectMicroservicesPatternAsync(analysis);
|
|
layerScores["Microservices"] = microservicesScore;
|
|
|
|
// Find the pattern with highest confidence
|
|
var bestPattern = layerScores.OrderByDescending(kvp => kvp.Value).First();
|
|
patterns.DetectedPattern = bestPattern.Key;
|
|
patterns.Confidence = bestPattern.Value;
|
|
|
|
// Generate layer definitions based on detected pattern
|
|
patterns.LayerDefinitions = await GenerateLayerDefinitionsAsync(analysis, bestPattern.Key);
|
|
|
|
// Detect pattern violations
|
|
patterns.PatternViolations = await DetectPatternViolationsAsync(analysis, bestPattern.Key, patterns.LayerDefinitions);
|
|
|
|
// Generate suggestions
|
|
patterns.Suggestions = GeneratePatternSuggestions(analysis, bestPattern.Key, patterns.PatternViolations);
|
|
|
|
return patterns;
|
|
}
|
|
|
|
private async Task<double> DetectLayeredArchitectureAsync(ProjectAnalysisResult analysis)
|
|
{
|
|
double score = 0;
|
|
var moduleNames = analysis.Modules.Select(m => m.Name.ToLower()).ToList();
|
|
|
|
// Check for common layer naming patterns
|
|
var layerPatterns = new Dictionary<string, string[]>
|
|
{
|
|
["presentation"] = new[] { "web", "api", "mvc", "ui", "frontend", "presentation" },
|
|
["business"] = new[] { "business", "service", "logic", "application", "app" },
|
|
["data"] = new[] { "data", "dal", "repository", "persistence", "infrastructure" }
|
|
};
|
|
|
|
var foundLayers = new HashSet<string>();
|
|
foreach (var module in analysis.Modules)
|
|
{
|
|
var moduleName = module.Name.ToLower();
|
|
var namespaces = module.Namespaces.Select(ns => ns.ToLower());
|
|
|
|
foreach (var layer in layerPatterns)
|
|
{
|
|
if (layer.Value.Any(pattern => moduleName.Contains(pattern) ||
|
|
namespaces.Any(ns => ns.Contains(pattern))))
|
|
{
|
|
foundLayers.Add(layer.Key);
|
|
score += 0.3;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check dependency flow (should be top-down in layered architecture)
|
|
var dependencyViolations = 0;
|
|
var layerOrder = new[] { "presentation", "business", "data" };
|
|
|
|
foreach (var dependency in analysis.Dependencies)
|
|
{
|
|
var fromLayer = DetermineLayer(dependency.From, analysis.Modules);
|
|
var toLayer = DetermineLayer(dependency.To, analysis.Modules);
|
|
|
|
var fromIndex = Array.IndexOf(layerOrder, fromLayer);
|
|
var toIndex = Array.IndexOf(layerOrder, toLayer);
|
|
|
|
if (fromIndex >= 0 && toIndex >= 0 && fromIndex < toIndex)
|
|
{
|
|
dependencyViolations++;
|
|
}
|
|
}
|
|
|
|
// Penalize for dependency violations
|
|
if (analysis.Dependencies.Count > 0)
|
|
{
|
|
var violationRatio = (double)dependencyViolations / analysis.Dependencies.Count;
|
|
score -= violationRatio * 0.4;
|
|
}
|
|
|
|
// Bonus for having all three layers
|
|
if (foundLayers.Count >= 3)
|
|
{
|
|
score += 0.3;
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return Math.Max(0, Math.Min(1, score));
|
|
}
|
|
|
|
private async Task<double> DetectCleanArchitectureAsync(ProjectAnalysisResult analysis)
|
|
{
|
|
double score = 0;
|
|
var moduleNames = analysis.Modules.Select(m => m.Name.ToLower()).ToList();
|
|
|
|
// Check for clean architecture naming patterns
|
|
var cleanPatterns = new Dictionary<string, string[]>
|
|
{
|
|
["domain"] = new[] { "domain", "entities", "core" },
|
|
["application"] = new[] { "application", "usecases", "services" },
|
|
["infrastructure"] = new[] { "infrastructure", "persistence", "external" },
|
|
["presentation"] = new[] { "web", "api", "ui", "controllers" }
|
|
};
|
|
|
|
var foundLayers = new HashSet<string>();
|
|
foreach (var module in analysis.Modules)
|
|
{
|
|
var moduleName = module.Name.ToLower();
|
|
var namespaces = module.Namespaces.Select(ns => ns.ToLower());
|
|
|
|
foreach (var layer in cleanPatterns)
|
|
{
|
|
if (layer.Value.Any(pattern => moduleName.Contains(pattern) ||
|
|
namespaces.Any(ns => ns.Contains(pattern))))
|
|
{
|
|
foundLayers.Add(layer.Key);
|
|
score += 0.25;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for dependency rule compliance (dependencies point inward)
|
|
var violationCount = 0;
|
|
var layerDependencyRules = new Dictionary<string, string[]>
|
|
{
|
|
["domain"] = new string[0], // Domain should have no dependencies
|
|
["application"] = new[] { "domain" },
|
|
["infrastructure"] = new[] { "domain", "application" },
|
|
["presentation"] = new[] { "application", "domain" }
|
|
};
|
|
|
|
foreach (var dependency in analysis.Dependencies)
|
|
{
|
|
var fromLayer = DetermineCleanLayer(dependency.From, analysis.Modules);
|
|
var toLayer = DetermineCleanLayer(dependency.To, analysis.Modules);
|
|
|
|
if (!string.IsNullOrEmpty(fromLayer) && !string.IsNullOrEmpty(toLayer))
|
|
{
|
|
var allowedDependencies = layerDependencyRules.GetValueOrDefault(fromLayer, new string[0]);
|
|
if (!allowedDependencies.Contains(toLayer))
|
|
{
|
|
violationCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Penalize violations
|
|
if (analysis.Dependencies.Count > 0)
|
|
{
|
|
var violationRatio = (double)violationCount / analysis.Dependencies.Count;
|
|
score -= violationRatio * 0.5;
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return Math.Max(0, Math.Min(1, score));
|
|
}
|
|
|
|
private async Task<double> DetectHexagonalArchitectureAsync(ProjectAnalysisResult analysis)
|
|
{
|
|
double score = 0;
|
|
|
|
// Check for hexagonal architecture patterns
|
|
var hexPatterns = new Dictionary<string, string[]>
|
|
{
|
|
["core"] = new[] { "core", "domain", "business" },
|
|
["ports"] = new[] { "ports", "interfaces", "contracts" },
|
|
["adapters"] = new[] { "adapters", "infrastructure", "external" }
|
|
};
|
|
|
|
var foundComponents = new HashSet<string>();
|
|
foreach (var module in analysis.Modules)
|
|
{
|
|
var moduleName = module.Name.ToLower();
|
|
var namespaces = module.Namespaces.Select(ns => ns.ToLower());
|
|
|
|
foreach (var component in hexPatterns)
|
|
{
|
|
if (component.Value.Any(pattern => moduleName.Contains(pattern) ||
|
|
namespaces.Any(ns => ns.Contains(pattern))))
|
|
{
|
|
foundComponents.Add(component.Key);
|
|
score += 0.33;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look for interface segregation (ports pattern)
|
|
var interfaceCount = 0;
|
|
var totalClasses = 0;
|
|
|
|
foreach (var module in analysis.Modules)
|
|
{
|
|
foreach (var sourceFile in module.SourceFiles)
|
|
{
|
|
try
|
|
{
|
|
var content = await File.ReadAllTextAsync(sourceFile);
|
|
var tree = CSharpSyntaxTree.ParseText(content);
|
|
var root = await tree.GetRootAsync();
|
|
|
|
interfaceCount += root.DescendantNodes().OfType<InterfaceDeclarationSyntax>().Count();
|
|
totalClasses += root.DescendantNodes().OfType<ClassDeclarationSyntax>().Count();
|
|
}
|
|
catch
|
|
{
|
|
// Ignore parsing errors
|
|
}
|
|
}
|
|
}
|
|
|
|
// High interface-to-class ratio suggests ports and adapters
|
|
if (totalClasses > 0)
|
|
{
|
|
var interfaceRatio = (double)interfaceCount / totalClasses;
|
|
if (interfaceRatio > 0.3)
|
|
{
|
|
score += 0.2;
|
|
}
|
|
}
|
|
|
|
return Math.Max(0, Math.Min(1, score));
|
|
}
|
|
|
|
private async Task<double> DetectMicroservicesPatternAsync(ProjectAnalysisResult analysis)
|
|
{
|
|
double score = 0;
|
|
|
|
// Check for microservices indicators
|
|
var servicePatterns = new[] { "service", "api", "microservice", "ms" };
|
|
var serviceCount = 0;
|
|
|
|
foreach (var module in analysis.Modules)
|
|
{
|
|
var moduleName = module.Name.ToLower();
|
|
if (servicePatterns.Any(pattern => moduleName.Contains(pattern)) &&
|
|
(module.Type == "WebAPI" || module.Type == "Console"))
|
|
{
|
|
serviceCount++;
|
|
score += 0.2;
|
|
}
|
|
}
|
|
|
|
// Multiple independent services suggest microservices
|
|
if (serviceCount >= 3)
|
|
{
|
|
score += 0.3;
|
|
}
|
|
|
|
// Low coupling between services is good for microservices
|
|
var serviceDependencies = analysis.Dependencies.Where(d =>
|
|
IsService(d.From, analysis.Modules) && IsService(d.To, analysis.Modules)).Count();
|
|
|
|
if (serviceCount > 0)
|
|
{
|
|
var serviceCouplingRatio = (double)serviceDependencies / (serviceCount * serviceCount);
|
|
if (serviceCouplingRatio < 0.2)
|
|
{
|
|
score += 0.3;
|
|
}
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return Math.Max(0, Math.Min(1, score));
|
|
}
|
|
|
|
private bool IsService(string moduleName, List<ModuleInfo> modules)
|
|
{
|
|
var module = modules.FirstOrDefault(m => m.Name == moduleName);
|
|
return module != null && (module.Type == "WebAPI" || module.Type == "Console") &&
|
|
new[] { "service", "api", "microservice", "ms" }.Any(pattern =>
|
|
module.Name.ToLower().Contains(pattern));
|
|
}
|
|
|
|
private string DetermineLayer(string moduleName, List<ModuleInfo> modules)
|
|
{
|
|
var module = modules.FirstOrDefault(m => m.Name == moduleName);
|
|
if (module == null) return "unknown";
|
|
|
|
var name = module.Name.ToLower();
|
|
var namespaces = module.Namespaces.Select(ns => ns.ToLower());
|
|
|
|
if (new[] { "web", "api", "mvc", "ui", "presentation" }.Any(p => name.Contains(p) || namespaces.Any(ns => ns.Contains(p))))
|
|
return "presentation";
|
|
if (new[] { "business", "service", "logic", "application" }.Any(p => name.Contains(p) || namespaces.Any(ns => ns.Contains(p))))
|
|
return "business";
|
|
if (new[] { "data", "dal", "repository", "persistence" }.Any(p => name.Contains(p) || namespaces.Any(ns => ns.Contains(p))))
|
|
return "data";
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
private string? DetermineCleanLayer(string moduleName, List<ModuleInfo> modules)
|
|
{
|
|
var module = modules.FirstOrDefault(m => m.Name == moduleName);
|
|
if (module == null) return null;
|
|
|
|
var name = module.Name.ToLower();
|
|
var namespaces = module.Namespaces.Select(ns => ns.ToLower());
|
|
|
|
if (new[] { "domain", "entities", "core" }.Any(p => name.Contains(p) || namespaces.Any(ns => ns.Contains(p))))
|
|
return "domain";
|
|
if (new[] { "application", "usecases", "services" }.Any(p => name.Contains(p) || namespaces.Any(ns => ns.Contains(p))))
|
|
return "application";
|
|
if (new[] { "infrastructure", "persistence", "external" }.Any(p => name.Contains(p) || namespaces.Any(ns => ns.Contains(p))))
|
|
return "infrastructure";
|
|
if (new[] { "web", "api", "ui", "controllers" }.Any(p => name.Contains(p) || namespaces.Any(ns => ns.Contains(p))))
|
|
return "presentation";
|
|
|
|
return null;
|
|
}
|
|
|
|
private async Task<Dictionary<string, List<string>>> GenerateLayerDefinitionsAsync(ProjectAnalysisResult analysis, string patternType)
|
|
{
|
|
var layers = new Dictionary<string, List<string>>();
|
|
|
|
switch (patternType.ToLower())
|
|
{
|
|
case "layered":
|
|
layers["Presentation"] = analysis.Modules.Where(m => DetermineLayer(m.Name, analysis.Modules) == "presentation").Select(m => m.Name).ToList();
|
|
layers["Business"] = analysis.Modules.Where(m => DetermineLayer(m.Name, analysis.Modules) == "business").Select(m => m.Name).ToList();
|
|
layers["Data"] = analysis.Modules.Where(m => DetermineLayer(m.Name, analysis.Modules) == "data").Select(m => m.Name).ToList();
|
|
break;
|
|
|
|
case "clean":
|
|
layers["Domain"] = analysis.Modules.Where(m => DetermineCleanLayer(m.Name, analysis.Modules) == "domain").Select(m => m.Name).ToList();
|
|
layers["Application"] = analysis.Modules.Where(m => DetermineCleanLayer(m.Name, analysis.Modules) == "application").Select(m => m.Name).ToList();
|
|
layers["Infrastructure"] = analysis.Modules.Where(m => DetermineCleanLayer(m.Name, analysis.Modules) == "infrastructure").Select(m => m.Name).ToList();
|
|
layers["Presentation"] = analysis.Modules.Where(m => DetermineCleanLayer(m.Name, analysis.Modules) == "presentation").Select(m => m.Name).ToList();
|
|
break;
|
|
|
|
case "hexagonal":
|
|
layers["Core"] = analysis.Modules.Where(m => m.Name.ToLower().Contains("core") || m.Name.ToLower().Contains("domain")).Select(m => m.Name).ToList();
|
|
layers["Ports"] = analysis.Modules.Where(m => m.Name.ToLower().Contains("interface") || m.Name.ToLower().Contains("contract")).Select(m => m.Name).ToList();
|
|
layers["Adapters"] = analysis.Modules.Where(m => m.Name.ToLower().Contains("adapter") || m.Name.ToLower().Contains("infrastructure")).Select(m => m.Name).ToList();
|
|
break;
|
|
|
|
case "microservices":
|
|
layers["Services"] = analysis.Modules.Where(m => IsService(m.Name, analysis.Modules)).Select(m => m.Name).ToList();
|
|
layers["Shared"] = analysis.Modules.Where(m => !IsService(m.Name, analysis.Modules)).Select(m => m.Name).ToList();
|
|
break;
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return layers;
|
|
}
|
|
|
|
private async Task<List<string>> DetectPatternViolationsAsync(ProjectAnalysisResult analysis, string patternType, Dictionary<string, List<string>> layers)
|
|
{
|
|
var violations = new List<string>();
|
|
|
|
switch (patternType.ToLower())
|
|
{
|
|
case "layered":
|
|
violations.AddRange(await DetectLayeredViolationsAsync(analysis, layers));
|
|
break;
|
|
case "clean":
|
|
violations.AddRange(await DetectCleanViolationsAsync(analysis, layers));
|
|
break;
|
|
case "hexagonal":
|
|
violations.AddRange(await DetectHexagonalViolationsAsync(analysis, layers));
|
|
break;
|
|
case "microservices":
|
|
violations.AddRange(await DetectMicroservicesViolationsAsync(analysis, layers));
|
|
break;
|
|
}
|
|
|
|
return violations;
|
|
}
|
|
|
|
private async Task<List<string>> DetectLayeredViolationsAsync(ProjectAnalysisResult analysis, Dictionary<string, List<string>> layers)
|
|
{
|
|
var violations = new List<string>();
|
|
var layerOrder = new[] { "Presentation", "Business", "Data" };
|
|
|
|
foreach (var dependency in analysis.Dependencies)
|
|
{
|
|
var fromLayer = GetModuleLayer(dependency.From, layers);
|
|
var toLayer = GetModuleLayer(dependency.To, layers);
|
|
|
|
if (!string.IsNullOrEmpty(fromLayer) && !string.IsNullOrEmpty(toLayer))
|
|
{
|
|
var fromIndex = Array.IndexOf(layerOrder, fromLayer);
|
|
var toIndex = Array.IndexOf(layerOrder, toLayer);
|
|
|
|
if (fromIndex >= 0 && toIndex >= 0 && fromIndex < toIndex)
|
|
{
|
|
violations.Add($"{fromLayer} layer module '{dependency.From}' should not depend on {toLayer} layer module '{dependency.To}'");
|
|
}
|
|
}
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return violations;
|
|
}
|
|
|
|
private async Task<List<string>> DetectCleanViolationsAsync(ProjectAnalysisResult analysis, Dictionary<string, List<string>> layers)
|
|
{
|
|
var violations = new List<string>();
|
|
var dependencyRules = new Dictionary<string, string[]>
|
|
{
|
|
["Domain"] = new string[0],
|
|
["Application"] = new[] { "Domain" },
|
|
["Infrastructure"] = new[] { "Domain", "Application" },
|
|
["Presentation"] = new[] { "Application", "Domain" }
|
|
};
|
|
|
|
foreach (var dependency in analysis.Dependencies)
|
|
{
|
|
var fromLayer = GetModuleLayer(dependency.From, layers);
|
|
var toLayer = GetModuleLayer(dependency.To, layers);
|
|
|
|
if (!string.IsNullOrEmpty(fromLayer) && !string.IsNullOrEmpty(toLayer))
|
|
{
|
|
var allowedDependencies = dependencyRules.GetValueOrDefault(fromLayer, new string[0]);
|
|
if (!allowedDependencies.Contains(toLayer))
|
|
{
|
|
violations.Add($"Clean Architecture violation: {fromLayer} module '{dependency.From}' should not depend on {toLayer} module '{dependency.To}'");
|
|
}
|
|
}
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return violations;
|
|
}
|
|
|
|
private async Task<List<string>> DetectHexagonalViolationsAsync(ProjectAnalysisResult analysis, Dictionary<string, List<string>> layers)
|
|
{
|
|
var violations = new List<string>();
|
|
|
|
// Core should not depend on Adapters
|
|
foreach (var dependency in analysis.Dependencies)
|
|
{
|
|
var fromLayer = GetModuleLayer(dependency.From, layers);
|
|
var toLayer = GetModuleLayer(dependency.To, layers);
|
|
|
|
if (fromLayer == "Core" && toLayer == "Adapters")
|
|
{
|
|
violations.Add($"Hexagonal Architecture violation: Core module '{dependency.From}' should not depend on Adapter module '{dependency.To}'");
|
|
}
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return violations;
|
|
}
|
|
|
|
private async Task<List<string>> DetectMicroservicesViolationsAsync(ProjectAnalysisResult analysis, Dictionary<string, List<string>> layers)
|
|
{
|
|
var violations = new List<string>();
|
|
|
|
// Services should have minimal dependencies on other services
|
|
var serviceToServiceDeps = analysis.Dependencies.Where(d =>
|
|
layers["Services"].Contains(d.From) && layers["Services"].Contains(d.To)).ToList();
|
|
|
|
if (serviceToServiceDeps.Count > layers["Services"].Count * 0.3)
|
|
{
|
|
violations.Add($"High coupling between microservices detected: {serviceToServiceDeps.Count} inter-service dependencies");
|
|
}
|
|
|
|
foreach (var serviceDep in serviceToServiceDeps)
|
|
{
|
|
violations.Add($"Service coupling: '{serviceDep.From}' depends on '{serviceDep.To}' - consider async messaging or shared data contracts");
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return violations;
|
|
}
|
|
|
|
private string GetModuleLayer(string moduleName, Dictionary<string, List<string>> layers)
|
|
{
|
|
return layers.FirstOrDefault(kvp => kvp.Value.Contains(moduleName)).Key;
|
|
}
|
|
|
|
private List<string> GeneratePatternSuggestions(ProjectAnalysisResult analysis, string patternType, List<string> violations)
|
|
{
|
|
var suggestions = new List<string>();
|
|
|
|
switch (patternType.ToLower())
|
|
{
|
|
case "layered":
|
|
suggestions.Add("Ensure dependencies flow downward: Presentation -> Business -> Data");
|
|
suggestions.Add("Consider using dependency injection to invert control between layers");
|
|
if (violations.Any())
|
|
suggestions.Add("Extract interfaces to break inappropriate layer dependencies");
|
|
break;
|
|
|
|
case "clean":
|
|
suggestions.Add("Keep the Domain layer free of external dependencies");
|
|
suggestions.Add("Use dependency inversion principle for Infrastructure dependencies");
|
|
suggestions.Add("Consider using MediatR pattern for Application layer orchestration");
|
|
break;
|
|
|
|
case "hexagonal":
|
|
suggestions.Add("Define clear port interfaces for external integrations");
|
|
suggestions.Add("Keep business logic in the core, isolated from infrastructure concerns");
|
|
suggestions.Add("Use adapter pattern for external service integrations");
|
|
break;
|
|
|
|
case "microservices":
|
|
suggestions.Add("Minimize direct dependencies between services");
|
|
suggestions.Add("Consider using message queues or event-driven communication");
|
|
suggestions.Add("Implement proper service boundaries based on business domains");
|
|
break;
|
|
}
|
|
|
|
return suggestions;
|
|
}
|
|
|
|
private async Task<ArchitecturalInsights> GenerateArchitecturalInsightsAsync(
|
|
ProjectAnalysisResult analysis,
|
|
CouplingMetrics? coupling,
|
|
ArchitecturalPatterns? patterns)
|
|
{
|
|
var insights = new ArchitecturalInsights();
|
|
|
|
// Calculate overall architecture score
|
|
var patternScore = patterns?.Confidence ?? 0;
|
|
var couplingScore = coupling != null ? (1 - coupling.OverallCouplingScore) : 0;
|
|
var complexityScore = CalculateComplexityScore(analysis);
|
|
|
|
insights.OverallArchitectureScore = (patternScore + couplingScore + complexityScore) / 3 * 10;
|
|
|
|
// Identify strength areas
|
|
insights.StrengthAreas = new List<string>();
|
|
|
|
if (patternScore > 0.7)
|
|
insights.StrengthAreas.Add($"Good adherence to {patterns?.DetectedPattern ?? "unknown"} architectural pattern");
|
|
|
|
if (coupling?.CircularDependencies?.Count == 0)
|
|
insights.StrengthAreas.Add("No circular dependencies detected");
|
|
|
|
if (coupling?.LooselyCoupledModules?.Count > coupling?.HighlyCoupledModules?.Count)
|
|
insights.StrengthAreas.Add("Good overall module decoupling");
|
|
|
|
if (analysis.Modules.Any(m => m.Type == "Library"))
|
|
insights.StrengthAreas.Add("Good separation of concerns with dedicated library modules");
|
|
|
|
// Identify improvement areas
|
|
insights.ImprovementAreas = new List<string>();
|
|
|
|
if (coupling?.HighlyCoupledModules?.Count > 0)
|
|
insights.ImprovementAreas.Add($"High coupling in modules: {string.Join(", ", coupling.HighlyCoupledModules!)}");
|
|
|
|
if (patterns?.PatternViolations?.Count > 0)
|
|
insights.ImprovementAreas.Add($"Architectural pattern violations detected ({patterns.PatternViolations!.Count} issues)");
|
|
|
|
if (coupling?.CircularDependencies?.Count > 0)
|
|
insights.ImprovementAreas.Add($"Circular dependencies need resolution ({coupling.CircularDependencies!.Count} cycles)");
|
|
|
|
// Generate refactoring opportunities
|
|
insights.RefactoringOpportunities = await GenerateRefactoringOpportunitiesAsync(analysis, coupling, patterns);
|
|
|
|
// Generate design pattern suggestions
|
|
insights.DesignPatternSuggestions = GenerateDesignPatternSuggestions(analysis, coupling, patterns);
|
|
|
|
return insights;
|
|
}
|
|
|
|
private double CalculateComplexityScore(ProjectAnalysisResult analysis)
|
|
{
|
|
if (analysis.Modules.Count == 0) return 0;
|
|
|
|
var avgLinesPerModule = analysis.Modules.Average(m => m.LineCount);
|
|
var avgClassesPerModule = analysis.Modules.Average(m => m.ClassCount);
|
|
var dependencyRatio = analysis.Dependencies.Count / (double)analysis.Modules.Count;
|
|
|
|
// Normalize scores (lower is better for complexity)
|
|
var linesScore = Math.Max(0, 1 - (avgLinesPerModule / 10000)); // Penalize modules > 10k lines
|
|
var classesScore = Math.Max(0, 1 - (avgClassesPerModule / 50)); // Penalize modules > 50 classes
|
|
var dependencyScore = Math.Max(0, 1 - (dependencyRatio / 5)); // Penalize > 5 deps per module
|
|
|
|
return (linesScore + classesScore + dependencyScore) / 3;
|
|
}
|
|
|
|
private async Task<List<RefactoringOpportunity>> GenerateRefactoringOpportunitiesAsync(
|
|
ProjectAnalysisResult analysis,
|
|
CouplingMetrics? coupling,
|
|
ArchitecturalPatterns? patterns)
|
|
{
|
|
var opportunities = new List<RefactoringOpportunity>();
|
|
|
|
// Large module refactoring
|
|
var largeModules = analysis.Modules.Where(m => m.LineCount > 5000 || m.ClassCount > 30).ToList();
|
|
foreach (var module in largeModules)
|
|
{
|
|
opportunities.Add(new RefactoringOpportunity
|
|
{
|
|
Type = "Split Large Module",
|
|
Target = module.Name,
|
|
Benefit = "Improved maintainability and reduced complexity",
|
|
Effort = module.LineCount > 10000 ? "High" : "Medium",
|
|
Priority = "Medium"
|
|
});
|
|
}
|
|
|
|
// High coupling refactoring
|
|
if (coupling?.HighlyCoupledModules != null)
|
|
{
|
|
foreach (var coupledModule in coupling.HighlyCoupledModules)
|
|
{
|
|
opportunities.Add(new RefactoringOpportunity
|
|
{
|
|
Type = "Reduce Coupling",
|
|
Target = coupledModule,
|
|
Benefit = "Improved testability and flexibility",
|
|
Effort = "Medium",
|
|
Priority = "High"
|
|
});
|
|
}
|
|
}
|
|
|
|
// Circular dependency refactoring
|
|
if (coupling?.CircularDependencies?.Count > 0)
|
|
{
|
|
opportunities.Add(new RefactoringOpportunity
|
|
{
|
|
Type = "Break Circular Dependencies",
|
|
Target = "Multiple modules",
|
|
Benefit = "Simplified dependency graph and better modularity",
|
|
Effort = "High",
|
|
Priority = "High"
|
|
});
|
|
}
|
|
|
|
// Pattern-specific refactoring
|
|
if (patterns?.PatternViolations?.Count > 0)
|
|
{
|
|
opportunities.Add(new RefactoringOpportunity
|
|
{
|
|
Type = $"Fix {patterns?.DetectedPattern ?? "architectural"} Pattern Violations",
|
|
Target = "Architecture",
|
|
Benefit = "Better architectural consistency and maintainability",
|
|
Effort = "Medium",
|
|
Priority = "Medium"
|
|
});
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return opportunities;
|
|
}
|
|
|
|
private List<string> GenerateDesignPatternSuggestions(
|
|
ProjectAnalysisResult analysis,
|
|
CouplingMetrics? coupling,
|
|
ArchitecturalPatterns? patterns)
|
|
{
|
|
var suggestions = new List<string>();
|
|
|
|
// Repository pattern for data access
|
|
var dataModules = analysis.Modules.Where(m =>
|
|
m.Name.ToLower().Contains("data") ||
|
|
m.Name.ToLower().Contains("repository") ||
|
|
m.Name.ToLower().Contains("dal")).ToList();
|
|
|
|
if (dataModules.Any())
|
|
{
|
|
suggestions.Add("Consider implementing Repository pattern for data access abstraction");
|
|
}
|
|
|
|
// Factory pattern for high coupling
|
|
if (coupling?.HighlyCoupledModules?.Count > 0)
|
|
{
|
|
suggestions.Add("Consider Factory or Abstract Factory patterns to reduce coupling in object creation");
|
|
}
|
|
|
|
// Observer pattern for event handling
|
|
var eventModules = analysis.Modules.Where(m =>
|
|
m.Name.ToLower().Contains("event") ||
|
|
m.Name.ToLower().Contains("notification") ||
|
|
m.Classes.Any(c => c.ToLower().Contains("event"))).ToList();
|
|
|
|
if (eventModules.Any())
|
|
{
|
|
suggestions.Add("Consider Observer or Mediator patterns for event-driven communication");
|
|
}
|
|
|
|
// Strategy pattern for business logic
|
|
var businessModules = analysis.Modules.Where(m =>
|
|
m.Name.ToLower().Contains("business") ||
|
|
m.Name.ToLower().Contains("service") ||
|
|
m.Name.ToLower().Contains("logic")).ToList();
|
|
|
|
if (businessModules.Any())
|
|
{
|
|
suggestions.Add("Consider Strategy pattern for varying business logic implementations");
|
|
}
|
|
|
|
// Dependency Injection for tight coupling
|
|
if (coupling?.OverallCouplingScore > 0.6)
|
|
{
|
|
suggestions.Add("Implement Dependency Injection container to improve testability and flexibility");
|
|
}
|
|
|
|
// Command pattern for API modules
|
|
var apiModules = analysis.Modules.Where(m =>
|
|
m.Type == "WebAPI" ||
|
|
m.Name.ToLower().Contains("api") ||
|
|
m.Name.ToLower().Contains("controller")).ToList();
|
|
|
|
if (apiModules.Any())
|
|
{
|
|
suggestions.Add("Consider Command/Query pattern (CQRS) for API request handling");
|
|
}
|
|
|
|
// Facade pattern for complex subsystems
|
|
var complexModules = analysis.Modules.Where(m => m.ClassCount > 20).ToList();
|
|
if (complexModules.Any())
|
|
{
|
|
suggestions.Add("Consider Facade pattern to simplify interfaces to complex subsystems");
|
|
}
|
|
|
|
return suggestions;
|
|
}
|
|
|
|
private async Task<string> SaveMapToFileAsync(object map, string outputPath, string format)
|
|
{
|
|
var directory = Path.GetDirectoryName(outputPath);
|
|
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
|
{
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
|
|
string content = format.ToLower() switch
|
|
{
|
|
"json" => JsonSerializer.Serialize(map, new JsonSerializerOptions { WriteIndented = true }),
|
|
"mermaid" => map.ToString() ?? string.Empty,
|
|
"cytoscape" => JsonSerializer.Serialize(map, new JsonSerializerOptions { WriteIndented = true }),
|
|
"graphviz" => map.ToString() ?? string.Empty,
|
|
_ => JsonSerializer.Serialize(map, new JsonSerializerOptions { WriteIndented = true })
|
|
};
|
|
|
|
await File.WriteAllTextAsync(outputPath, content);
|
|
return outputPath;
|
|
}
|
|
|
|
private string GenerateMermaidDiagram(ProjectAnalysisResult analysis)
|
|
{
|
|
var mermaid = new StringBuilder();
|
|
mermaid.AppendLine("graph TD");
|
|
|
|
// Add nodes with styling
|
|
foreach (var module in analysis.Modules)
|
|
{
|
|
var nodeStyle = module.Type switch
|
|
{
|
|
"WebAPI" => ":::webapi",
|
|
"Library" => ":::library",
|
|
"Console" => ":::console",
|
|
_ => ""
|
|
};
|
|
|
|
mermaid.AppendLine($" {SanitizeNodeName(module.Name)}[\"{module.Name}<br/>({module.Type})\"] {nodeStyle}");
|
|
}
|
|
|
|
// Add dependencies
|
|
foreach (var dependency in analysis.Dependencies)
|
|
{
|
|
var arrow = dependency.Strength switch
|
|
{
|
|
"Strong" => "-->",
|
|
"Medium" => "-.->",
|
|
_ => "-->"
|
|
};
|
|
|
|
var label = dependency.ReferenceCount > 1 ? $"|{dependency.ReferenceCount}|" : "";
|
|
mermaid.AppendLine($" {SanitizeNodeName(dependency.From)} {arrow} {SanitizeNodeName(dependency.To)} {label}");
|
|
}
|
|
|
|
// Add styling
|
|
mermaid.AppendLine();
|
|
mermaid.AppendLine(" classDef webapi fill:#e1f5fe,stroke:#0277bd,stroke-width:2px");
|
|
mermaid.AppendLine(" classDef library fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px");
|
|
mermaid.AppendLine(" classDef console fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px");
|
|
|
|
return mermaid.ToString();
|
|
}
|
|
|
|
private string SanitizeNodeName(string name)
|
|
{
|
|
return Regex.Replace(name ?? string.Empty, @"[^a-zA-Z0-9_]", "_");
|
|
}
|
|
|
|
private object GenerateCytoscapeJson(ProjectAnalysisResult analysis)
|
|
{
|
|
return new
|
|
{
|
|
elements = new
|
|
{
|
|
nodes = analysis.Modules.Select(m => new
|
|
{
|
|
data = new
|
|
{
|
|
id = m.Name,
|
|
label = m.Name,
|
|
type = m.Type,
|
|
lineCount = m.LineCount,
|
|
classCount = m.ClassCount,
|
|
@namespace = m.Namespace
|
|
}
|
|
}),
|
|
edges = analysis.Dependencies.Select(d => new
|
|
{
|
|
data = new
|
|
{
|
|
source = d.From,
|
|
target = d.To,
|
|
type = d.Type,
|
|
strength = d.Strength,
|
|
weight = d.ReferenceCount
|
|
}
|
|
})
|
|
},
|
|
style = new object[]
|
|
{
|
|
new
|
|
{
|
|
selector = "node",
|
|
style = new
|
|
{
|
|
content = "data(label)",
|
|
width = "mapData(lineCount, 0, 10000, 20, 80)",
|
|
height = "mapData(classCount, 0, 50, 20, 60)",
|
|
backgroundColor = "#0074D9"
|
|
}
|
|
},
|
|
new
|
|
{
|
|
selector = "edge",
|
|
style = new
|
|
{
|
|
width = "mapData(weight, 1, 10, 1, 5)",
|
|
lineColor = "#333",
|
|
targetArrowColor = "#333",
|
|
targetArrowShape = "triangle"
|
|
}
|
|
}
|
|
},
|
|
layout = new
|
|
{
|
|
name = "dagre",
|
|
directed = true,
|
|
rankDir = "TB"
|
|
}
|
|
};
|
|
}
|
|
|
|
private string GenerateGraphvizDot(ProjectAnalysisResult analysis)
|
|
{
|
|
var dot = new StringBuilder();
|
|
dot.AppendLine("digraph ModularMap {");
|
|
dot.AppendLine(" rankdir=TB;");
|
|
dot.AppendLine(" node [shape=box, style=filled];");
|
|
dot.AppendLine(" edge [fontsize=10];");
|
|
|
|
// Add nodes with colors based on type
|
|
foreach (var module in analysis.Modules)
|
|
{
|
|
var color = module.Type switch
|
|
{
|
|
"WebAPI" => "lightblue",
|
|
"Library" => "lightgreen",
|
|
"Console" => "lightyellow",
|
|
_ => "lightgray"
|
|
};
|
|
|
|
var label = $"{module.Name}\\n({module.Type})\\n{module.LineCount} lines";
|
|
dot.AppendLine($" \"{module.Name}\" [label=\"{label}\", fillcolor={color}];");
|
|
}
|
|
|
|
// Add edges
|
|
foreach (var dependency in analysis.Dependencies)
|
|
{
|
|
var style = dependency.Strength switch
|
|
{
|
|
"Strong" => "solid",
|
|
"Medium" => "dashed",
|
|
_ => "dotted"
|
|
};
|
|
|
|
var label = dependency.ReferenceCount > 1 ? $"[label=\"{dependency.ReferenceCount}\"]" : "";
|
|
dot.AppendLine($" \"{dependency.From}\" -> \"{dependency.To}\" [style={style}] {label};");
|
|
}
|
|
|
|
dot.AppendLine("}");
|
|
return dot.ToString();
|
|
}
|
|
|
|
private async Task<ModularStructure> GenerateModularStructureAsync(ProjectAnalysisResult analysis, ModularOptions options)
|
|
{
|
|
var modularStructure = new ModularStructure
|
|
{
|
|
GeneratedAt = DateTime.UtcNow,
|
|
GroupingStrategy = options.GroupingStrategy
|
|
};
|
|
|
|
try
|
|
{
|
|
// Step 1: Identify logical modules based on grouping strategy
|
|
var logicalModules = await IdentifyLogicalModulesAsync(analysis, options);
|
|
|
|
// Step 2: Analyze dependencies between modules
|
|
var moduleDependencies = await AnalyzeModuleDependenciesAsync(logicalModules, analysis);
|
|
|
|
// Step 3: Detect platform-specific modules
|
|
if (options.DetectPlatformModules)
|
|
{
|
|
await DetectPlatformSpecificModulesAsync(logicalModules, analysis);
|
|
}
|
|
|
|
// Step 4: Identify entry points and public interfaces
|
|
if (options.IncludeEntryPoints)
|
|
{
|
|
await IdentifyModuleEntryPointsAsync(logicalModules, analysis);
|
|
}
|
|
|
|
// Step 5: Generate scaffolding metadata
|
|
if (options.GenerateScaffoldingMetadata)
|
|
{
|
|
await GenerateScaffoldingMetadataAsync(logicalModules, analysis, options);
|
|
}
|
|
|
|
// Step 6: Analyze module tags and capabilities
|
|
if (options.AnalyzeModuleTags)
|
|
{
|
|
await AnalyzeModuleTagsAsync(logicalModules, analysis);
|
|
}
|
|
|
|
// Step 7: Generate LLM descriptions
|
|
if (options.IncludeLlmDescriptions)
|
|
{
|
|
await GenerateLlmDescriptionsAsync(logicalModules, analysis);
|
|
}
|
|
|
|
// Step 8: Set module flags
|
|
if (options.IncludeModuleFlags)
|
|
{
|
|
await SetModuleFlagsAsync(logicalModules, analysis);
|
|
}
|
|
|
|
// Step 9: Build the final modular structure
|
|
modularStructure.Modules = logicalModules;
|
|
modularStructure.ModuleDependencies = moduleDependencies;
|
|
modularStructure.ReusabilityAnalysis = await AnalyzeModuleReusabilityAsync(logicalModules, moduleDependencies);
|
|
|
|
_logger?.LogInformation("Generated modular structure with {ModuleCount} logical modules", logicalModules.Count);
|
|
|
|
return modularStructure;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogError(ex, "Failed to generate modular structure");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private async Task<List<LogicalModule>> IdentifyLogicalModulesAsync(ProjectAnalysisResult analysis, ModularOptions options)
|
|
{
|
|
var modules = new List<LogicalModule>();
|
|
var namespaceGroups = new Dictionary<string, List<string>>();
|
|
|
|
// Collect all namespaces from all projects
|
|
var allNamespaces = analysis.Modules
|
|
.SelectMany(m => m.Namespaces)
|
|
.Where(ns => !string.IsNullOrEmpty(ns))
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
switch (options.GroupingStrategy.ToLower())
|
|
{
|
|
case "namespace":
|
|
modules = await GroupByNamespacePatternAsync(allNamespaces, analysis);
|
|
break;
|
|
case "folder":
|
|
modules = await GroupByFolderStructureAsync(analysis);
|
|
break;
|
|
case "feature":
|
|
modules = await GroupByFeatureDetectionAsync(allNamespaces, analysis);
|
|
break;
|
|
case "auto":
|
|
default:
|
|
modules = await AutoDetectModulesAsync(allNamespaces, analysis);
|
|
break;
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return modules;
|
|
}
|
|
|
|
private async Task<List<LogicalModule>> GroupByNamespacePatternAsync(List<string> namespaces, ProjectAnalysisResult analysis)
|
|
{
|
|
var modules = new List<LogicalModule>();
|
|
var moduleGroups = new Dictionary<string, List<string>>();
|
|
|
|
foreach (var ns in namespaces)
|
|
{
|
|
// Extract module name from namespace patterns
|
|
var moduleName = ExtractModuleNameFromNamespace(ns);
|
|
|
|
if (!moduleGroups.ContainsKey(moduleName))
|
|
{
|
|
moduleGroups[moduleName] = new List<string>();
|
|
}
|
|
moduleGroups[moduleName].Add(ns);
|
|
}
|
|
|
|
foreach (var group in moduleGroups)
|
|
{
|
|
var module = new LogicalModule
|
|
{
|
|
Name = group.Key,
|
|
Namespaces = group.Value,
|
|
ModuleType = DetermineModuleType(group.Value),
|
|
PlatformSpecific = false, // Will be detected later
|
|
Platforms = new List<string>()
|
|
};
|
|
|
|
// Find related source files
|
|
module.SourceFiles = analysis.Modules
|
|
.Where(m => m.Namespaces.Any(ns => group.Value.Contains(ns)))
|
|
.SelectMany(m => m.SourceFiles)
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
modules.Add(module);
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return modules;
|
|
}
|
|
|
|
private async Task<List<LogicalModule>> GroupByFolderStructureAsync(ProjectAnalysisResult analysis)
|
|
{
|
|
var modules = new List<LogicalModule>();
|
|
var folderGroups = new Dictionary<string, List<string>>();
|
|
|
|
foreach (var project in analysis.Modules)
|
|
{
|
|
var projectDir = Path.GetDirectoryName(project.ProjectPath) ?? string.Empty;
|
|
var subFolders = project.SourceFiles
|
|
.Select(f => Path.GetDirectoryName(f) ?? string.Empty)
|
|
.Where(d => !string.IsNullOrEmpty(d) && d != projectDir)
|
|
.Select(d => Path.GetRelativePath(projectDir, d).Split(Path.DirectorySeparatorChar)[0])
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
foreach (var folder in subFolders)
|
|
{
|
|
if (!folderGroups.ContainsKey(folder))
|
|
{
|
|
folderGroups[folder] = new List<string>();
|
|
}
|
|
|
|
var folderNamespaces = project.Namespaces
|
|
.Where(ns => ns.Contains(folder, StringComparison.OrdinalIgnoreCase))
|
|
.ToList();
|
|
|
|
folderGroups[folder].AddRange(folderNamespaces);
|
|
}
|
|
}
|
|
|
|
foreach (var group in folderGroups.Where(g => g.Value.Any()))
|
|
{
|
|
var module = new LogicalModule
|
|
{
|
|
Name = group.Key,
|
|
Namespaces = group.Value.Distinct().ToList(),
|
|
ModuleType = DetermineModuleType(group.Value),
|
|
PlatformSpecific = false
|
|
};
|
|
|
|
module.SourceFiles = analysis.Modules
|
|
.SelectMany(m => m.SourceFiles)
|
|
.Where(f => Path.GetDirectoryName(f)?.Contains(group.Key, StringComparison.OrdinalIgnoreCase) == true)
|
|
.ToList();
|
|
|
|
modules.Add(module);
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return modules;
|
|
}
|
|
|
|
private async Task<List<LogicalModule>> GroupByFeatureDetectionAsync(List<string> namespaces, ProjectAnalysisResult analysis)
|
|
{
|
|
var modules = new List<LogicalModule>();
|
|
var featurePatterns = new Dictionary<string, string[]>
|
|
{
|
|
["Authentication"] = new[] { "auth", "login", "identity", "security", "token" },
|
|
["Trading"] = new[] { "trading", "trade", "market", "order", "portfolio", "broker" },
|
|
["Charting"] = new[] { "chart", "graph", "plot", "visualization", "technical" },
|
|
["Messaging"] = new[] { "message", "notification", "push", "alert", "communication" },
|
|
["Data"] = new[] { "data", "repository", "storage", "database", "persistence" },
|
|
["UI"] = new[] { "view", "ui", "interface", "controls", "widgets", "pages" },
|
|
["API"] = new[] { "api", "service", "client", "endpoint", "request" },
|
|
["Utilities"] = new[] { "util", "helper", "common", "shared", "extension" },
|
|
["Platform"] = new[] { "platform", "ios", "android", "windows", "macos" },
|
|
["Configuration"] = new[] { "config", "setting", "preference", "option" },
|
|
["Sync"] = new[] { "sync", "synchronization", "backup", "cloud" },
|
|
["Reports"] = new[] { "report", "analytics", "statistics", "metrics" }
|
|
};
|
|
|
|
var featureGroups = new Dictionary<string, List<string>>();
|
|
|
|
foreach (var ns in namespaces)
|
|
{
|
|
var nsLower = ns.ToLower();
|
|
var matchedFeature = "Utilities"; // Default
|
|
|
|
foreach (var feature in featurePatterns)
|
|
{
|
|
if (feature.Value.Any(pattern => nsLower.Contains(pattern)))
|
|
{
|
|
matchedFeature = feature.Key;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!featureGroups.ContainsKey(matchedFeature))
|
|
{
|
|
featureGroups[matchedFeature] = new List<string>();
|
|
}
|
|
featureGroups[matchedFeature].Add(ns);
|
|
}
|
|
|
|
foreach (var group in featureGroups.Where(g => g.Value.Any()))
|
|
{
|
|
var module = new LogicalModule
|
|
{
|
|
Name = group.Key,
|
|
Namespaces = group.Value,
|
|
ModuleType = DetermineModuleType(group.Value),
|
|
PlatformSpecific = group.Key == "Platform"
|
|
};
|
|
|
|
module.SourceFiles = analysis.Modules
|
|
.Where(m => m.Namespaces.Any(ns => group.Value.Contains(ns)))
|
|
.SelectMany(m => m.SourceFiles)
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
modules.Add(module);
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return modules;
|
|
}
|
|
|
|
private async Task<List<LogicalModule>> AutoDetectModulesAsync(List<string> namespaces, ProjectAnalysisResult analysis)
|
|
{
|
|
// Combine multiple strategies for auto-detection
|
|
var namespaceModules = await GroupByNamespacePatternAsync(namespaces, analysis);
|
|
var featureModules = await GroupByFeatureDetectionAsync(namespaces, analysis);
|
|
|
|
// Merge and deduplicate based on namespace overlap
|
|
var mergedModules = new List<LogicalModule>();
|
|
var processedNamespaces = new HashSet<string>();
|
|
|
|
// Prioritize feature-based grouping
|
|
foreach (var featureModule in featureModules)
|
|
{
|
|
if (featureModule.Namespaces.Any(ns => !processedNamespaces.Contains(ns)))
|
|
{
|
|
mergedModules.Add(featureModule);
|
|
foreach (var ns in featureModule.Namespaces)
|
|
{
|
|
processedNamespaces.Add(ns);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add remaining namespaces as separate modules
|
|
foreach (var nsModule in namespaceModules)
|
|
{
|
|
var unprocessedNamespaces = nsModule.Namespaces
|
|
.Where(ns => !processedNamespaces.Contains(ns))
|
|
.ToList();
|
|
|
|
if (unprocessedNamespaces.Any())
|
|
{
|
|
var module = new LogicalModule
|
|
{
|
|
Name = nsModule.Name,
|
|
Namespaces = unprocessedNamespaces,
|
|
ModuleType = nsModule.ModuleType,
|
|
PlatformSpecific = nsModule.PlatformSpecific
|
|
};
|
|
|
|
module.SourceFiles = analysis.Modules
|
|
.Where(m => m.Namespaces.Any(ns => unprocessedNamespaces.Contains(ns)))
|
|
.SelectMany(m => m.SourceFiles)
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
mergedModules.Add(module);
|
|
}
|
|
}
|
|
|
|
return mergedModules;
|
|
}
|
|
|
|
private string ExtractModuleNameFromNamespace(string namespaceName)
|
|
{
|
|
var parts = namespaceName.Split('.');
|
|
|
|
// Look for common module indicators
|
|
var moduleIndicators = new[] { "Services", "Data", "UI", "API", "Core", "Helpers", "Utils", "Models", "Views", "Controllers" };
|
|
parts.Reverse();
|
|
foreach (var part in parts)
|
|
{
|
|
if (moduleIndicators.Contains(part, StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
return part;
|
|
}
|
|
}
|
|
|
|
// Look for feature names (third part usually contains feature name)
|
|
if (parts.Length >= 3)
|
|
{
|
|
return parts[2];
|
|
}
|
|
|
|
// Fall back to last meaningful part
|
|
return parts.LastOrDefault(p => !string.IsNullOrEmpty(p) && p.Length > 2) ?? "Unknown";
|
|
}
|
|
|
|
private string DetermineModuleType(List<string> namespaces)
|
|
{
|
|
var allNamespaces = string.Join(" ", namespaces).ToLower();
|
|
|
|
if (allNamespaces.Contains("service") || allNamespaces.Contains("api"))
|
|
return "Service";
|
|
if (allNamespaces.Contains("data") || allNamespaces.Contains("repository"))
|
|
return "Data";
|
|
if (allNamespaces.Contains("ui") || allNamespaces.Contains("view") || allNamespaces.Contains("page"))
|
|
return "UI";
|
|
if (allNamespaces.Contains("model") || allNamespaces.Contains("entity"))
|
|
return "Model";
|
|
if (allNamespaces.Contains("helper") || allNamespaces.Contains("util"))
|
|
return "Utility";
|
|
if (allNamespaces.Contains("platform") || allNamespaces.Contains("ios") || allNamespaces.Contains("android"))
|
|
return "Platform";
|
|
|
|
return "Library";
|
|
}
|
|
|
|
private async Task<List<ModuleDependency>> AnalyzeModuleDependenciesAsync(List<LogicalModule> modules, ProjectAnalysisResult analysis)
|
|
{
|
|
var dependencies = new List<ModuleDependency>();
|
|
|
|
foreach (var fromModule in modules)
|
|
{
|
|
foreach (var toModule in modules)
|
|
{
|
|
if (fromModule.Name == toModule.Name) continue;
|
|
|
|
var dependencyCount = await CountModuleDependencies(fromModule, toModule, analysis);
|
|
if (dependencyCount > 0)
|
|
{
|
|
dependencies.Add(new ModuleDependency
|
|
{
|
|
From = fromModule.Name,
|
|
To = toModule.Name,
|
|
DependencyType = "Internal",
|
|
ReferenceCount = dependencyCount,
|
|
DependencyStrength = CalculateDependencyStrength(dependencyCount)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return dependencies;
|
|
}
|
|
|
|
private async Task<int> CountModuleDependencies(LogicalModule fromModule, LogicalModule toModule, ProjectAnalysisResult analysis)
|
|
{
|
|
int count = 0;
|
|
|
|
foreach (var sourceFile in fromModule.SourceFiles)
|
|
{
|
|
try
|
|
{
|
|
var content = await File.ReadAllTextAsync(sourceFile);
|
|
var tree = CSharpSyntaxTree.ParseText(content);
|
|
var root = await tree.GetRootAsync();
|
|
|
|
// Count using directives pointing to target module
|
|
var usingDirectives = root.DescendantNodes().OfType<UsingDirectiveSyntax>();
|
|
foreach (var usingDirective in usingDirectives)
|
|
{
|
|
var namespaceName = usingDirective.Name?.ToString();
|
|
if (toModule.Namespaces.Any(ns => namespaceName?.StartsWith(ns) == true))
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
// Count type references
|
|
var identifierNames = root.DescendantNodes().OfType<IdentifierNameSyntax>();
|
|
foreach (var identifier in identifierNames)
|
|
{
|
|
// This is a simplified check - in practice, you'd want semantic analysis
|
|
var identifierText = identifier.Identifier.ValueText;
|
|
if (toModule.Namespaces.Any(ns => ns.EndsWith(identifierText)))
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning("Failed to analyze dependencies in {File}: {Error}", sourceFile, ex.Message);
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
private string CalculateDependencyStrength(int referenceCount)
|
|
{
|
|
return referenceCount switch
|
|
{
|
|
> 20 => "Strong",
|
|
> 5 => "Medium",
|
|
_ => "Weak"
|
|
};
|
|
}
|
|
|
|
private async Task DetectPlatformSpecificModulesAsync(List<LogicalModule> modules, ProjectAnalysisResult analysis)
|
|
{
|
|
var platformPatterns = new Dictionary<string, string[]>
|
|
{
|
|
["iOS"] = new[] { "ios", "iphone", "ipad", "xamarin.ios", "uikit", "foundation" },
|
|
["Android"] = new[] { "android", "xamarin.android", "androidx", "google.android" },
|
|
["Windows"] = new[] { "windows", "win32", "uwp", "winui", "wpf" },
|
|
["macOS"] = new[] { "macos", "osx", "appkit", "xamarin.mac" },
|
|
["Web"] = new[] { "blazor", "web", "browser", "javascript" }
|
|
};
|
|
|
|
foreach (var module in modules)
|
|
{
|
|
var moduleContent = string.Join(" ", module.Namespaces).ToLower();
|
|
|
|
foreach (var platform in platformPatterns)
|
|
{
|
|
if (platform.Value.Any(pattern => moduleContent.Contains(pattern)))
|
|
{
|
|
module.PlatformSpecific = true;
|
|
module.Platforms.Add(platform.Key);
|
|
}
|
|
}
|
|
|
|
// Check source files for platform-specific references
|
|
foreach (var sourceFile in module.SourceFiles.Take(10)) // Sample to avoid performance issues
|
|
{
|
|
try
|
|
{
|
|
var content = await File.ReadAllTextAsync(sourceFile);
|
|
var contentLower = content.ToLower();
|
|
|
|
foreach (var platform in platformPatterns)
|
|
{
|
|
if (platform.Value.Any(pattern => contentLower.Contains(pattern)) &&
|
|
!module.Platforms.Contains(platform.Key))
|
|
{
|
|
module.PlatformSpecific = true;
|
|
module.Platforms.Add(platform.Key);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning("Failed to analyze platform specificity in {File}: {Error}", sourceFile, ex.Message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task IdentifyModuleEntryPointsAsync(List<LogicalModule> modules, ProjectAnalysisResult analysis)
|
|
{
|
|
foreach (var module in modules)
|
|
{
|
|
var entryPoints = new List<string>();
|
|
var publicInterfaces = new List<string>();
|
|
|
|
foreach (var sourceFile in module.SourceFiles)
|
|
{
|
|
try
|
|
{
|
|
var content = await File.ReadAllTextAsync(sourceFile);
|
|
var tree = CSharpSyntaxTree.ParseText(content);
|
|
var root = await tree.GetRootAsync();
|
|
|
|
// Find public classes that could be entry points
|
|
var publicClasses = root.DescendantNodes().OfType<ClassDeclarationSyntax>()
|
|
.Where(c => c.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));
|
|
|
|
foreach (var publicClass in publicClasses)
|
|
{
|
|
var className = publicClass.Identifier.ValueText;
|
|
|
|
// Entry point heuristics
|
|
if (className.EndsWith("Service") || className.EndsWith("Manager") ||
|
|
className.EndsWith("Controller") || className.EndsWith("Client") ||
|
|
className.EndsWith("Facade") || className.EndsWith("Gateway"))
|
|
{
|
|
entryPoints.Add($"{Path.GetFileName(sourceFile)}:{className}");
|
|
}
|
|
}
|
|
|
|
// Find public interfaces
|
|
var publicInterfaces_temp = root.DescendantNodes().OfType<InterfaceDeclarationSyntax>()
|
|
.Where(i => i.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));
|
|
|
|
foreach (var publicInterface in publicInterfaces_temp)
|
|
{
|
|
var interfaceName = publicInterface.Identifier.ValueText;
|
|
publicInterfaces.Add($"{Path.GetFileName(sourceFile)}:{interfaceName}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning("Failed to identify entry points in {File}: {Error}", sourceFile, ex.Message);
|
|
}
|
|
}
|
|
|
|
module.EntryPoints = entryPoints;
|
|
module.PublicInterfaces = publicInterfaces;
|
|
}
|
|
}
|
|
|
|
private async Task<ReusabilityAnalysis> AnalyzeModuleReusabilityAsync(List<LogicalModule> modules, List<ModuleDependency> dependencies)
|
|
{
|
|
var analysis = new ReusabilityAnalysis();
|
|
|
|
foreach (var module in modules)
|
|
{
|
|
var incomingDeps = dependencies.Count(d => d.To == module.Name);
|
|
var outgoingDeps = dependencies.Count(d => d.From == module.Name);
|
|
var totalDeps = incomingDeps + outgoingDeps;
|
|
|
|
var reusabilityScore = CalculateReusabilityScore(module, incomingDeps, outgoingDeps);
|
|
|
|
var moduleReusability = new ModuleReusability
|
|
{
|
|
ModuleName = module.Name,
|
|
ReusabilityScore = reusabilityScore,
|
|
IncomingDependencies = incomingDeps,
|
|
OutgoingDependencies = outgoingDeps,
|
|
PlatformSpecific = module.PlatformSpecific,
|
|
HasPublicInterfaces = module.PublicInterfaces.Any(),
|
|
RecommendedFor = GenerateReusabilityRecommendations(module, reusabilityScore)
|
|
};
|
|
|
|
analysis.ModuleReusability.Add(moduleReusability);
|
|
|
|
// Categorize modules
|
|
if (reusabilityScore > 0.8 && !module.PlatformSpecific)
|
|
{
|
|
analysis.HighlyReusableModules.Add(module.Name);
|
|
}
|
|
else if (module.PlatformSpecific)
|
|
{
|
|
analysis.PlatformSpecificModules.Add(module.Name);
|
|
}
|
|
else if (outgoingDeps > incomingDeps * 2)
|
|
{
|
|
analysis.UtilityModules.Add(module.Name);
|
|
}
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return analysis;
|
|
}
|
|
|
|
private double CalculateReusabilityScore(LogicalModule module, int incomingDeps, int outgoingDeps)
|
|
{
|
|
double score = 0.5; // Base score
|
|
|
|
// Factors that increase reusability
|
|
if (module.PublicInterfaces.Any()) score += 0.2;
|
|
if (!module.PlatformSpecific) score += 0.2;
|
|
if (incomingDeps > 0) score += Math.Min(0.3, incomingDeps * 0.1);
|
|
if (module.ModuleType == "Utility" || module.ModuleType == "Service") score += 0.1;
|
|
|
|
// Factors that decrease reusability
|
|
if (outgoingDeps > 5) score -= Math.Min(0.3, (outgoingDeps - 5) * 0.05);
|
|
if (module.PlatformSpecific) score -= 0.2;
|
|
|
|
return Math.Max(0, Math.Min(1, score));
|
|
}
|
|
|
|
private List<string> GenerateReusabilityRecommendations(LogicalModule module, double reusabilityScore)
|
|
{
|
|
var recommendations = new List<string>();
|
|
|
|
if (reusabilityScore > 0.8)
|
|
{
|
|
recommendations.Add("Highly reusable - excellent candidate for shared libraries");
|
|
recommendations.Add("Consider packaging as NuGet package for cross-project use");
|
|
}
|
|
else if (reusabilityScore > 0.6)
|
|
{
|
|
recommendations.Add("Good reusability potential with minor improvements");
|
|
if (!module.PublicInterfaces.Any())
|
|
recommendations.Add("Add public interfaces to improve API design");
|
|
}
|
|
else
|
|
{
|
|
recommendations.Add("Limited reusability - consider refactoring if needed elsewhere");
|
|
if (module.PlatformSpecific)
|
|
recommendations.Add("Platform-specific - abstract platform dependencies for reusability");
|
|
}
|
|
|
|
return recommendations;
|
|
}
|
|
|
|
private Task<object> GenerateDependencyMapAsync(ProjectAnalysisResult analysis, string format)
|
|
{
|
|
var result = format.ToLower() switch
|
|
{
|
|
"mermaid" => GenerateMermaidDiagram(analysis),
|
|
"cytoscape" => GenerateCytoscapeJson(analysis),
|
|
"graphviz" => GenerateGraphvizDot(analysis),
|
|
_ => GenerateJsonMap(analysis)
|
|
};
|
|
return Task.FromResult(result);
|
|
}
|
|
|
|
private object GenerateJsonMap(ProjectAnalysisResult analysis)
|
|
{
|
|
return new
|
|
{
|
|
metadata = new
|
|
{
|
|
generatedAt = DateTime.UtcNow,
|
|
projectPath = analysis.ProjectPath,
|
|
totalModules = analysis.Modules.Count,
|
|
totalDependencies = analysis.Dependencies.Count
|
|
},
|
|
nodes = analysis.Modules.Select(m => new
|
|
{
|
|
id = m.Name,
|
|
label = m.Name,
|
|
@namespace = m.Namespace,
|
|
type = m.Type,
|
|
lineCount = m.LineCount,
|
|
classCount = m.ClassCount,
|
|
targetFramework = m.TargetFramework,
|
|
namespaces = m.Namespaces,
|
|
projectPath = m.ProjectPath
|
|
}),
|
|
edges = analysis.Dependencies.Select(d => new
|
|
{
|
|
source = d.From,
|
|
target = d.To,
|
|
type = d.Type,
|
|
strength = d.Strength,
|
|
referenceCount = d.ReferenceCount
|
|
}),
|
|
externalDependencies = analysis.ExternalDependencies.Select(ed => new
|
|
{
|
|
name = ed.Name,
|
|
version = ed.Version,
|
|
type = ed.Type,
|
|
usedBy = ed.UsedBy
|
|
})
|
|
};
|
|
}
|
|
|
|
|
|
private object GenerateModularJsonMap(ModularStructure modularStructure)
|
|
{
|
|
return new
|
|
{
|
|
metadata = new
|
|
{
|
|
generatedAt = modularStructure.GeneratedAt,
|
|
groupingStrategy = modularStructure.GroupingStrategy,
|
|
totalLogicalModules = modularStructure.Modules.Count,
|
|
totalModuleDependencies = modularStructure.ModuleDependencies.Count,
|
|
scaffoldingCompatible = true,
|
|
llmOptimized = true
|
|
},
|
|
modules = modularStructure.Modules.Select(m => new
|
|
{
|
|
// Core module information
|
|
name = m.Name,
|
|
namespaces = m.Namespaces,
|
|
moduleType = m.ModuleType,
|
|
platformSpecific = m.PlatformSpecific,
|
|
platforms = m.Platforms,
|
|
entryPoints = m.EntryPoints,
|
|
publicInterfaces = m.PublicInterfaces,
|
|
externalDependencies = m.ExternalDependencies,
|
|
|
|
// Dependencies and relationships
|
|
dependsOn = modularStructure.ModuleDependencies
|
|
.Where(d => d.From == m.Name)
|
|
.Select(d => d.To)
|
|
.ToList(),
|
|
usedBy = modularStructure.ModuleDependencies
|
|
.Where(d => d.To == m.Name)
|
|
.Select(d => d.From)
|
|
.ToList(),
|
|
|
|
// Metrics
|
|
sourceFileCount = m.SourceFiles.Count,
|
|
reusabilityScore = modularStructure.ReusabilityAnalysis?.ModuleReusability
|
|
.FirstOrDefault(r => r.ModuleName == m.Name)?.ReusabilityScore ?? 0,
|
|
|
|
// New generation-focused properties
|
|
tags = m.Tags,
|
|
|
|
// Module flags for CLI/LLM usage
|
|
optional = m.ModuleFlags?.Optional ?? true,
|
|
defaultIncluded = m.ModuleFlags?.DefaultIncluded ?? false,
|
|
coreModule = m.ModuleFlags?.CoreModule ?? false,
|
|
experimentalFeature = m.ModuleFlags?.ExperimentalFeature ?? false,
|
|
requiresConfiguration = m.ModuleFlags?.RequiresConfiguration ?? false,
|
|
hasBreakingChanges = m.ModuleFlags?.HasBreakingChanges ?? false,
|
|
|
|
// Scaffolding metadata
|
|
scaffoldable = m.ScaffoldingMetadata?.Scaffoldable ?? false,
|
|
minimumDependencies = m.ScaffoldingMetadata?.MinimumDependencies ?? new List<string>(),
|
|
lastUpdated = m.ScaffoldingMetadata?.LastUpdated ?? DateTime.UtcNow,
|
|
isDeprecated = m.ScaffoldingMetadata?.IsDeprecated ?? false,
|
|
complexityScore = m.ScaffoldingMetadata?.ComplexityScore ?? 0,
|
|
setupInstructions = m.ScaffoldingMetadata?.SetupInstructions ?? new List<string>(),
|
|
configurationFiles = m.ScaffoldingMetadata?.ConfigurationFiles ?? new List<string>(),
|
|
requiredEnvironmentVariables = m.ScaffoldingMetadata?.RequiredEnvironmentVariables ?? new List<string>(),
|
|
|
|
// LLM-friendly descriptions
|
|
promptDescription = m.LlmMetadata?.PromptDescription ?? $"The {m.Name} module provides {m.ModuleType.ToLower()} functionality.",
|
|
usageExample = m.LlmMetadata?.UsageExample ?? "",
|
|
integrationNotes = m.LlmMetadata?.IntegrationNotes ?? new List<string>(),
|
|
commonUseCases = m.LlmMetadata?.CommonUseCases ?? new List<string>(),
|
|
alternativeModules = m.LlmMetadata?.AlternativeModules ?? new List<string>()
|
|
}),
|
|
|
|
moduleDependencies = modularStructure.ModuleDependencies.Select(d => new
|
|
{
|
|
from = d.From,
|
|
to = d.To,
|
|
dependencyType = d.DependencyType,
|
|
referenceCount = d.ReferenceCount,
|
|
dependencyStrength = d.DependencyStrength
|
|
}),
|
|
|
|
reusabilityAnalysis = new
|
|
{
|
|
highlyReusableModules = modularStructure.ReusabilityAnalysis?.HighlyReusableModules ?? new List<string>(),
|
|
platformSpecificModules = modularStructure.ReusabilityAnalysis?.PlatformSpecificModules ?? new List<string>(),
|
|
utilityModules = modularStructure.ReusabilityAnalysis?.UtilityModules ?? new List<string>(),
|
|
moduleReusability = modularStructure.ReusabilityAnalysis?.ModuleReusability?.Select(r => new
|
|
{
|
|
moduleName = r.ModuleName,
|
|
reusabilityScore = r.ReusabilityScore,
|
|
incomingDependencies = r.IncomingDependencies,
|
|
outgoingDependencies = r.OutgoingDependencies,
|
|
platformSpecific = r.PlatformSpecific,
|
|
hasPublicInterfaces = r.HasPublicInterfaces,
|
|
recommendedFor = r.RecommendedFor
|
|
}).Cast<object>().ToList() ?? new List<object>()
|
|
},
|
|
|
|
// New generation-focused analysis
|
|
scaffoldingGuide = new
|
|
{
|
|
coreModules = modularStructure.Modules
|
|
.Where(m => m.ModuleFlags?.CoreModule == true)
|
|
.Select(m => m.Name)
|
|
.ToList(),
|
|
optionalModules = modularStructure.Modules
|
|
.Where(m => m.ModuleFlags?.Optional == true)
|
|
.Select(m => new { name = m.Name, tags = m.Tags })
|
|
.ToList(),
|
|
platformModules = modularStructure.Modules
|
|
.Where(m => m.PlatformSpecific)
|
|
.GroupBy(m => m.Platforms.FirstOrDefault() ?? "Unknown")
|
|
.ToDictionary(g => g.Key, g => g.Select(m => m.Name).ToList()),
|
|
deprecatedModules = modularStructure.Modules
|
|
.Where(m => m.ScaffoldingMetadata?.IsDeprecated == true)
|
|
.Select(m => m.Name)
|
|
.ToList()
|
|
},
|
|
|
|
llmPromptData = new
|
|
{
|
|
totalModules = modularStructure.Modules.Count,
|
|
modulesByCategory = modularStructure.Modules
|
|
.GroupBy(m => m.ModuleType)
|
|
.ToDictionary(g => g.Key, g => g.Select(m => new
|
|
{
|
|
name = m.Name,
|
|
description = m.LlmMetadata?.PromptDescription ?? "",
|
|
tags = m.Tags,
|
|
optional = m.ModuleFlags?.Optional ?? true
|
|
}).ToList()),
|
|
commonCombinations = GenerateCommonModuleCombinations(modularStructure.Modules),
|
|
quickStartModules = modularStructure.Modules
|
|
.Where(m => m.ModuleFlags?.DefaultIncluded == true)
|
|
.Select(m => m.Name)
|
|
.ToList()
|
|
}
|
|
};
|
|
}
|
|
|
|
private List<object> GenerateCommonModuleCombinations(List<LogicalModule> modules)
|
|
{
|
|
var combinations = new List<object>();
|
|
|
|
// Web API combination
|
|
var webApiModules = modules.Where(m =>
|
|
m.Tags.Contains("api") || m.Tags.Contains("auth") || m.Tags.Contains("database"))
|
|
.Select(m => m.Name).ToList();
|
|
if (webApiModules.Any())
|
|
{
|
|
combinations.Add(new { name = "Web API Stack", modules = webApiModules, useCase = "RESTful API development" });
|
|
}
|
|
|
|
// Mobile app combination
|
|
var mobileModules = modules.Where(m =>
|
|
m.Tags.Contains("ui") || m.Tags.Contains("auth") || m.Tags.Contains("offline") || m.Tags.Contains("notification"))
|
|
.Select(m => m.Name).ToList();
|
|
if (mobileModules.Any())
|
|
{
|
|
combinations.Add(new { name = "Mobile App Stack", modules = mobileModules, useCase = "Cross-platform mobile application" });
|
|
}
|
|
|
|
// Analytics combination
|
|
var analyticsModules = modules.Where(m =>
|
|
m.Tags.Contains("analytics") || m.Tags.Contains("logging") || m.Tags.Contains("database"))
|
|
.Select(m => m.Name).ToList();
|
|
if (analyticsModules.Any())
|
|
{
|
|
combinations.Add(new { name = "Analytics Stack", modules = analyticsModules, useCase = "Data tracking and analysis" });
|
|
}
|
|
|
|
return combinations;
|
|
}
|
|
|
|
private async Task GenerateScaffoldingMetadataAsync(List<LogicalModule> modules, ProjectAnalysisResult analysis, ModularOptions options)
|
|
{
|
|
foreach (var module in modules)
|
|
{
|
|
try
|
|
{
|
|
// Analyze module complexity and dependencies
|
|
var moduleDependencies = await AnalyzeModuleDependencyComplexity(module, modules, analysis);
|
|
|
|
// Determine if module is scaffoldable
|
|
module.ScaffoldingMetadata = new ScaffoldingMetadata
|
|
{
|
|
Scaffoldable = DetermineIfScaffoldable(module),
|
|
MinimumDependencies = moduleDependencies.Where(d => d.IsRequired).Select(d => d.ModuleName).ToList(),
|
|
LastUpdated = await GetModuleLastUpdated(module),
|
|
IsDeprecated = await CheckIfDeprecated(module),
|
|
ComplexityScore = CalculateModuleComplexity(module),
|
|
SetupInstructions = GenerateSetupInstructions(module),
|
|
ConfigurationFiles = await IdentifyConfigurationFiles(module),
|
|
RequiredEnvironmentVariables = await IdentifyRequiredEnvironmentVariables(module)
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning("Failed to generate scaffolding metadata for module {Module}: {Error}", module.Name, ex.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task AnalyzeModuleTagsAsync(List<LogicalModule> modules, ProjectAnalysisResult analysis)
|
|
{
|
|
var tagPatterns = new Dictionary<string, string[]>
|
|
{
|
|
["auth"] = new[] { "authentication", "login", "oauth", "jwt", "identity", "security", "token" },
|
|
["navigation"] = new[] { "navigation", "routing", "menu", "page", "route" },
|
|
["offline"] = new[] { "offline", "cache", "sync", "storage", "local" },
|
|
["firebase"] = new[] { "firebase", "firestore", "messaging", "analytics", "crashlytics" },
|
|
["database"] = new[] { "database", "sql", "entity", "repository", "orm" },
|
|
["api"] = new[] { "api", "http", "rest", "client", "service", "endpoint" },
|
|
["ui"] = new[] { "ui", "view", "component", "control", "widget", "page" },
|
|
["notification"] = new[] { "notification", "push", "alert", "message" },
|
|
["analytics"] = new[] { "analytics", "tracking", "metrics", "telemetry" },
|
|
["payment"] = new[] { "payment", "billing", "stripe", "paypal", "checkout" },
|
|
["media"] = new[] { "image", "video", "audio", "camera", "photo" },
|
|
["location"] = new[] { "location", "gps", "map", "geolocation" },
|
|
["social"] = new[] { "social", "share", "facebook", "twitter", "instagram" },
|
|
["testing"] = new[] { "test", "mock", "stub", "unit", "integration" },
|
|
["logging"] = new[] { "log", "logger", "diagnostic", "debug", "trace" },
|
|
["configuration"] = new[] { "config", "setting", "preference", "option" },
|
|
["crypto"] = new[] { "crypto", "encryption", "hash", "cipher", "secure" },
|
|
["network"] = new[] { "network", "connectivity", "reachability", "internet" }
|
|
};
|
|
|
|
foreach (var module in modules)
|
|
{
|
|
var tags = new HashSet<string>();
|
|
var moduleContent = string.Join(" ", module.Namespaces).ToLower();
|
|
|
|
// Add class names and comments for better tag detection
|
|
var allContent = moduleContent;
|
|
foreach (var sourceFile in module.SourceFiles.Take(10)) // Limit for performance
|
|
{
|
|
try
|
|
{
|
|
var content = await File.ReadAllTextAsync(sourceFile);
|
|
allContent += " " + content.ToLower();
|
|
}
|
|
catch
|
|
{
|
|
// Ignore file read errors
|
|
}
|
|
}
|
|
|
|
foreach (var tagPattern in tagPatterns)
|
|
{
|
|
if (tagPattern.Value.Any(pattern => allContent.Contains(pattern)))
|
|
{
|
|
tags.Add(tagPattern.Key);
|
|
}
|
|
}
|
|
|
|
// Add module type as a tag
|
|
tags.Add(module.ModuleType.ToLower());
|
|
|
|
// Add platform tags if platform-specific
|
|
if (module.PlatformSpecific)
|
|
{
|
|
foreach (var platform in module.Platforms)
|
|
{
|
|
tags.Add(platform.ToLower());
|
|
}
|
|
}
|
|
|
|
module.Tags = tags.ToList();
|
|
}
|
|
}
|
|
|
|
private async Task GenerateLlmDescriptionsAsync(List<LogicalModule> modules, ProjectAnalysisResult analysis)
|
|
{
|
|
foreach (var module in modules)
|
|
{
|
|
try
|
|
{
|
|
var description = await GenerateModuleDescription(module, analysis);
|
|
module.LlmMetadata = new LlmMetadata
|
|
{
|
|
PromptDescription = description,
|
|
UsageExample = GenerateUsageExample(module),
|
|
IntegrationNotes = GenerateIntegrationNotes(module),
|
|
CommonUseCases = GenerateCommonUseCases(module),
|
|
AlternativeModules = await FindAlternativeModules(module, modules)
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning("Failed to generate LLM description for module {Module}: {Error}", module.Name, ex.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task SetModuleFlagsAsync(List<LogicalModule> modules, ProjectAnalysisResult analysis)
|
|
{
|
|
foreach (var module in modules)
|
|
{
|
|
module.ModuleFlags = new ModuleFlags
|
|
{
|
|
Optional = DetermineIfOptional(module),
|
|
DefaultIncluded = DetermineDefaultIncluded(module),
|
|
CoreModule = DetermineIfCoreModule(module, modules),
|
|
ExperimentalFeature = await CheckIfExperimental(module),
|
|
RequiresConfiguration = await CheckIfRequiresConfiguration(module),
|
|
HasBreakingChanges = await CheckForBreakingChanges(module)
|
|
};
|
|
}
|
|
}
|
|
|
|
// Helper methods for scaffolding metadata
|
|
private bool DetermineIfScaffoldable(LogicalModule module)
|
|
{
|
|
// Modules are scaffoldable if they have clear interfaces and aren't too complex
|
|
return module.PublicInterfaces.Any() &&
|
|
!module.Name.ToLower().Contains("legacy") &&
|
|
!module.Name.ToLower().Contains("deprecated");
|
|
}
|
|
|
|
private Task<DateTime> GetModuleLastUpdated(LogicalModule module)
|
|
{
|
|
try
|
|
{
|
|
var latestDate = DateTime.MinValue;
|
|
foreach (var file in module.SourceFiles.Take(10)) // Sample for performance
|
|
{
|
|
if (File.Exists(file))
|
|
{
|
|
var fileInfo = new FileInfo(file);
|
|
if (fileInfo.LastWriteTime > latestDate)
|
|
{
|
|
latestDate = fileInfo.LastWriteTime;
|
|
}
|
|
}
|
|
}
|
|
return Task.FromResult(latestDate == DateTime.MinValue ? DateTime.UtcNow : latestDate);
|
|
}
|
|
catch
|
|
{
|
|
return Task.FromResult(DateTime.UtcNow);
|
|
}
|
|
}
|
|
|
|
private async Task<bool> CheckIfDeprecated(LogicalModule module)
|
|
{
|
|
try
|
|
{
|
|
foreach (var file in module.SourceFiles.Take(5))
|
|
{
|
|
var content = await File.ReadAllTextAsync(file);
|
|
if (content.ToLower().Contains("deprecated") ||
|
|
content.ToLower().Contains("obsolete") ||
|
|
content.Contains("[Obsolete"))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private double CalculateModuleComplexity(LogicalModule module)
|
|
{
|
|
// Simple complexity calculation based on various factors
|
|
double complexity = 0;
|
|
|
|
complexity += module.SourceFiles.Count * 0.1; // File count factor
|
|
complexity += module.PublicInterfaces.Count * 0.2; // Interface complexity
|
|
complexity += module.EntryPoints.Count * 0.15; // Entry point complexity
|
|
complexity += module.Namespaces.Count * 0.05; // Namespace spread
|
|
|
|
if (module.PlatformSpecific) complexity += 0.3; // Platform complexity
|
|
|
|
return Math.Min(1.0, complexity); // Cap at 1.0
|
|
}
|
|
|
|
private List<string> GenerateSetupInstructions(LogicalModule module)
|
|
{
|
|
var instructions = new List<string>();
|
|
|
|
instructions.Add($"1. Add reference to {module.Name} module");
|
|
|
|
if (module.PublicInterfaces.Any())
|
|
{
|
|
instructions.Add($"2. Register services/interfaces in DI container");
|
|
}
|
|
|
|
if (module.PlatformSpecific)
|
|
{
|
|
instructions.Add($"3. Configure platform-specific settings for {string.Join(", ", module.Platforms)}");
|
|
}
|
|
|
|
if (module.Tags.Contains("configuration"))
|
|
{
|
|
instructions.Add($"4. Update configuration files with required settings");
|
|
}
|
|
|
|
instructions.Add($"5. Initialize {module.Name} in application startup");
|
|
|
|
return instructions;
|
|
}
|
|
|
|
private Task<List<string>> IdentifyConfigurationFiles(LogicalModule module)
|
|
{
|
|
var configFiles = new List<string>();
|
|
var configPatterns = new[] { "config", "settings", "appsettings", "web.config", ".json", ".xml", ".yml", ".yaml" };
|
|
|
|
foreach (var file in module.SourceFiles)
|
|
{
|
|
var fileName = Path.GetFileName(file).ToLower();
|
|
if (configPatterns.Any(pattern => fileName.Contains(pattern)))
|
|
{
|
|
configFiles.Add(fileName);
|
|
}
|
|
}
|
|
|
|
return Task.FromResult(configFiles.Distinct().ToList());
|
|
}
|
|
|
|
private async Task<List<string>> IdentifyRequiredEnvironmentVariables(LogicalModule module)
|
|
{
|
|
var envVars = new HashSet<string>();
|
|
var envPatterns = new[] { "Environment.GetEnvironmentVariable", "Environment.GetVariable", "Environment[", "GetEnvironmentVariable", "env.", "process.env" };
|
|
|
|
foreach (var file in module.SourceFiles.Take(10))
|
|
{
|
|
try
|
|
{
|
|
var content = await File.ReadAllTextAsync(file);
|
|
foreach (var pattern in envPatterns)
|
|
{
|
|
if (content.Contains(pattern))
|
|
{
|
|
// Simple extraction - could be enhanced with regex
|
|
var lines = content.Split('\n').Where(l => l.Contains(pattern));
|
|
foreach (var line in lines)
|
|
{
|
|
var envVarMatch = ExtractEnvironmentVariableName(line);
|
|
if (!string.IsNullOrEmpty(envVarMatch))
|
|
{
|
|
envVars.Add(envVarMatch);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore file read errors
|
|
}
|
|
}
|
|
|
|
return envVars.ToList();
|
|
}
|
|
|
|
private string? ExtractEnvironmentVariableName(string line)
|
|
{
|
|
// Simple extraction - looks for quoted strings after environment variable calls
|
|
var patterns = new[]
|
|
{
|
|
@"Environment\.GetEnvironmentVariable\(\s*""([^""]+)""",
|
|
@"Environment\[\s*""([^""]+)""",
|
|
@"process\.env\.([A-Z_]+)",
|
|
@"env\.([A-Z_]+)"
|
|
};
|
|
|
|
foreach (var pattern in patterns)
|
|
{
|
|
var match = System.Text.RegularExpressions.Regex.Match(line, pattern);
|
|
if (match.Success)
|
|
{
|
|
return match.Groups[1].Value;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private async Task<string> GenerateModuleDescription(LogicalModule module, ProjectAnalysisResult analysis)
|
|
{
|
|
var description = $"The '{module.Name}' module is a {module.ModuleType.ToLower()} component that ";
|
|
|
|
// Add functionality description based on tags
|
|
var functionDescriptions = new List<string>();
|
|
|
|
if (module.Tags.Contains("auth"))
|
|
functionDescriptions.Add("handles user authentication and authorization");
|
|
if (module.Tags.Contains("api"))
|
|
functionDescriptions.Add("provides API integration and data communication");
|
|
if (module.Tags.Contains("ui"))
|
|
functionDescriptions.Add("manages user interface components and interactions");
|
|
if (module.Tags.Contains("database"))
|
|
functionDescriptions.Add("manages data persistence and database operations");
|
|
if (module.Tags.Contains("analytics"))
|
|
functionDescriptions.Add("tracks user behavior and application metrics");
|
|
if (module.Tags.Contains("notification"))
|
|
functionDescriptions.Add("manages push notifications and messaging");
|
|
|
|
if (functionDescriptions.Any())
|
|
{
|
|
description += string.Join(", ", functionDescriptions);
|
|
}
|
|
else
|
|
{
|
|
description += $"provides {module.ModuleType.ToLower()} functionality";
|
|
}
|
|
|
|
// Add platform information
|
|
if (module.PlatformSpecific)
|
|
{
|
|
description += $" specifically for {string.Join(" and ", module.Platforms)} platforms";
|
|
}
|
|
|
|
// Add dependency information
|
|
if (module.EntryPoints.Any())
|
|
{
|
|
description += $". Main entry points include {string.Join(", ", module.EntryPoints.Take(3))}";
|
|
}
|
|
|
|
description += ".";
|
|
|
|
await Task.CompletedTask;
|
|
return description;
|
|
}
|
|
|
|
private string GenerateUsageExample(LogicalModule module)
|
|
{
|
|
if (module.EntryPoints.Any())
|
|
{
|
|
var mainEntry = module.EntryPoints.First();
|
|
var serviceName = mainEntry.Split(':').Last();
|
|
|
|
return $"// Example usage:\nvar {serviceName.ToLower()} = new {serviceName}();\n// Use {serviceName.ToLower()} for {module.ModuleType.ToLower()} operations";
|
|
}
|
|
|
|
return $"// Register {module.Name} module in your DI container\nservices.Add{module.Name}();";
|
|
}
|
|
|
|
private List<string> GenerateIntegrationNotes(LogicalModule module)
|
|
{
|
|
var notes = new List<string>();
|
|
|
|
if (module.PlatformSpecific)
|
|
{
|
|
notes.Add($"Platform-specific module - requires {string.Join(", ", module.Platforms)} runtime");
|
|
}
|
|
|
|
if (module.Tags.Contains("configuration"))
|
|
{
|
|
notes.Add("Requires configuration setup before use");
|
|
}
|
|
|
|
if (module.Tags.Contains("api"))
|
|
{
|
|
notes.Add("May require API keys or authentication tokens");
|
|
}
|
|
|
|
if (module.Tags.Contains("database"))
|
|
{
|
|
notes.Add("Requires database connection configuration");
|
|
}
|
|
|
|
return notes;
|
|
}
|
|
|
|
private List<string> GenerateCommonUseCases(LogicalModule module)
|
|
{
|
|
var useCases = new List<string>();
|
|
|
|
foreach (var tag in module.Tags.Take(5))
|
|
{
|
|
var useCase = tag switch
|
|
{
|
|
"auth" => "User login and session management",
|
|
"api" => "External service integration and data synchronization",
|
|
"ui" => "Building responsive user interfaces",
|
|
"database" => "Data storage and retrieval operations",
|
|
"analytics" => "User behavior tracking and reporting",
|
|
"notification" => "Real-time user notifications",
|
|
"payment" => "Processing financial transactions",
|
|
"media" => "Image and video processing",
|
|
_ => $"General {tag} functionality"
|
|
};
|
|
useCases.Add(useCase);
|
|
}
|
|
|
|
return useCases;
|
|
}
|
|
|
|
private async Task<List<string>> FindAlternativeModules(LogicalModule module, List<LogicalModule> allModules)
|
|
{
|
|
var alternatives = new List<string>();
|
|
|
|
// Find modules with similar tags
|
|
foreach (var otherModule in allModules)
|
|
{
|
|
if (otherModule.Name == module.Name) continue;
|
|
|
|
var commonTags = module.Tags.Intersect(otherModule.Tags).Count();
|
|
if (commonTags >= 2) // At least 2 common tags
|
|
{
|
|
alternatives.Add(otherModule.Name);
|
|
}
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
return alternatives.Take(3).ToList(); // Limit to top 3 alternatives
|
|
}
|
|
|
|
private bool DetermineIfOptional(LogicalModule module)
|
|
{
|
|
// Core functionality modules are not optional
|
|
var coreModules = new[] { "core", "data", "api", "auth", "main" };
|
|
return !coreModules.Any(core => module.Name.ToLower().Contains(core)) &&
|
|
!module.Tags.Contains("auth") &&
|
|
module.ModuleType != "Service";
|
|
}
|
|
|
|
private bool DetermineDefaultIncluded(LogicalModule module)
|
|
{
|
|
// Include core modules and commonly used utilities by default
|
|
return module.Tags.Contains("auth") ||
|
|
module.Tags.Contains("api") ||
|
|
module.Tags.Contains("ui") ||
|
|
module.ModuleType == "Service" ||
|
|
module.Name.ToLower().Contains("core");
|
|
}
|
|
|
|
private bool DetermineIfCoreModule(LogicalModule module, List<LogicalModule> allModules)
|
|
{
|
|
// A module is core if many other modules depend on it
|
|
var dependents = allModules.Count(m =>
|
|
m.Namespaces.Any(ns => module.Namespaces.Any(mns => ns.StartsWith(mns))));
|
|
|
|
return dependents > allModules.Count * 0.3 || // More than 30% of modules depend on it
|
|
module.Name.ToLower().Contains("core") ||
|
|
module.Tags.Contains("auth");
|
|
}
|
|
|
|
private async Task<bool> CheckIfExperimental(LogicalModule module)
|
|
{
|
|
try
|
|
{
|
|
foreach (var file in module.SourceFiles.Take(5))
|
|
{
|
|
var content = await File.ReadAllTextAsync(file);
|
|
if (content.ToLower().Contains("experimental") ||
|
|
content.ToLower().Contains("preview") ||
|
|
content.ToLower().Contains("beta"))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private async Task<bool> CheckIfRequiresConfiguration(LogicalModule module)
|
|
{
|
|
return module.Tags.Contains("configuration") ||
|
|
module.Tags.Contains("api") ||
|
|
module.Tags.Contains("database") ||
|
|
module.PlatformSpecific ||
|
|
(await IdentifyConfigurationFiles(module)).Any();
|
|
}
|
|
|
|
private async Task<bool> CheckForBreakingChanges(LogicalModule module)
|
|
{
|
|
try
|
|
{
|
|
foreach (var file in module.SourceFiles.Take(5))
|
|
{
|
|
var content = await File.ReadAllTextAsync(file);
|
|
if (content.Contains("BREAKING") ||
|
|
content.Contains("breaking change") ||
|
|
content.Contains("incompatible"))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private async Task<List<ModuleDependencyInfo>> AnalyzeModuleDependencyComplexity(LogicalModule module, List<LogicalModule> allModules, ProjectAnalysisResult analysis)
|
|
{
|
|
var dependencies = new List<ModuleDependencyInfo>();
|
|
|
|
foreach (var otherModule in allModules)
|
|
{
|
|
if (otherModule.Name == module.Name) continue;
|
|
|
|
var dependencyCount = await CountModuleDependencies(module, otherModule, analysis);
|
|
if (dependencyCount > 0)
|
|
{
|
|
dependencies.Add(new ModuleDependencyInfo
|
|
{
|
|
ModuleName = otherModule.Name,
|
|
IsRequired = dependencyCount > 5 || otherModule.Tags.Contains("auth"), // Heuristic
|
|
ComplexityImpact = CalculateDependencyComplexity(dependencyCount, otherModule)
|
|
});
|
|
}
|
|
}
|
|
|
|
return dependencies;
|
|
}
|
|
|
|
private double CalculateDependencyComplexity(int dependencyCount, LogicalModule targetModule)
|
|
{
|
|
double complexity = dependencyCount * 0.1;
|
|
if (targetModule.PlatformSpecific) complexity += 0.3;
|
|
if (targetModule.Tags.Contains("api")) complexity += 0.2;
|
|
return Math.Min(1.0, complexity);
|
|
}
|
|
|
|
public class AnalysisOptions
|
|
{
|
|
public bool IncludeExternalDependencies { get; set; }
|
|
public bool InternalOnly { get; set; }
|
|
public int MaxDepth { get; set; }
|
|
public string? NamespaceFilter { get; set; }
|
|
public bool IncludeMethodLevel { get; set; }
|
|
public bool EnableModuleGrouping { get; set; }
|
|
public string ModuleGroupingStrategy { get; set; } = string.Empty;
|
|
public bool DetectPlatformModules { get; set; }
|
|
public bool IncludeEntryPoints { get; set; }
|
|
public bool GenerateModuleDefinitions { get; set; }
|
|
public bool GenerateScaffoldingMetadata { get; set; }
|
|
public bool IncludeLlmDescriptions { get; set; }
|
|
public bool AnalyzeModuleTags { get; set; }
|
|
public bool IncludeModuleFlags { get; set; }
|
|
}
|
|
|
|
public class ModularStructure
|
|
{
|
|
public DateTime GeneratedAt { get; set; }
|
|
public string GroupingStrategy { get; set; } = string.Empty;
|
|
public List<LogicalModule> Modules { get; set; } = new();
|
|
public List<ModuleDependency> ModuleDependencies { get; set; } = new();
|
|
public ReusabilityAnalysis ReusabilityAnalysis { get; set; } = new();
|
|
}
|
|
|
|
public class ModuleDependency
|
|
{
|
|
public string From { get; set; } = string.Empty;
|
|
public string To { get; set; } = string.Empty;
|
|
public string DependencyType { get; set; } = string.Empty;
|
|
public int ReferenceCount { get; set; }
|
|
public string DependencyStrength { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class ReusabilityAnalysis
|
|
{
|
|
public List<string> HighlyReusableModules { get; set; } = new();
|
|
public List<string> PlatformSpecificModules { get; set; } = new();
|
|
public List<string> UtilityModules { get; set; } = new();
|
|
public List<ModuleReusability> ModuleReusability { get; set; } = new();
|
|
}
|
|
|
|
public class ModuleReusability
|
|
{
|
|
public string ModuleName { get; set; } = string.Empty;
|
|
public double ReusabilityScore { get; set; }
|
|
public int IncomingDependencies { get; set; }
|
|
public int OutgoingDependencies { get; set; }
|
|
public bool PlatformSpecific { get; set; }
|
|
public bool HasPublicInterfaces { get; set; }
|
|
public List<string> RecommendedFor { get; set; } = new();
|
|
}
|
|
|
|
// Update the GenerateDependencyMapAsync method to handle modular structure
|
|
private Task<object> GenerateDependencyMapAsync(ProjectAnalysisResult analysis, string format, ModularStructure? modularStructure = null)
|
|
{
|
|
// If we have modular structure, use it for enhanced visualization
|
|
if (modularStructure != null)
|
|
{
|
|
var result = format.ToLower() switch
|
|
{
|
|
"mermaid" => GenerateModularMermaidDiagram(modularStructure),
|
|
"cytoscape" => GenerateModularCytoscapeJson(modularStructure),
|
|
"graphviz" => GenerateModularGraphvizDot(modularStructure),
|
|
_ => GenerateModularJsonMap(modularStructure)
|
|
};
|
|
return Task.FromResult(result);
|
|
}
|
|
|
|
// Fall back to project-level analysis
|
|
var fallbackResult = format.ToLower() switch
|
|
{
|
|
"mermaid" => GenerateMermaidDiagram(analysis),
|
|
"cytoscape" => GenerateCytoscapeJson(analysis),
|
|
"graphviz" => GenerateGraphvizDot(analysis),
|
|
_ => GenerateJsonMap(analysis)
|
|
};
|
|
return Task.FromResult(fallbackResult);
|
|
}
|
|
|
|
private string GenerateModularMermaidDiagram(ModularStructure modularStructure)
|
|
{
|
|
var mermaid = new StringBuilder();
|
|
mermaid.AppendLine("graph TD");
|
|
|
|
// Add nodes with enhanced styling based on module properties
|
|
foreach (var module in modularStructure.Modules)
|
|
{
|
|
var nodeStyle = GetModuleStyle(module);
|
|
var platformInfo = module.PlatformSpecific ? $"<br/>[{string.Join(",", module.Platforms)}]" : "";
|
|
|
|
mermaid.AppendLine($" {SanitizeNodeName(module.Name)}[\"{module.Name}<br/>({module.ModuleType}){platformInfo}\"] {nodeStyle}");
|
|
}
|
|
|
|
// Add dependencies with enhanced information
|
|
foreach (var dependency in modularStructure.ModuleDependencies)
|
|
{
|
|
var arrow = dependency.DependencyStrength switch
|
|
{
|
|
"Strong" => "==>",
|
|
"Medium" => "-->",
|
|
_ => "-..->"
|
|
};
|
|
|
|
var label = dependency.ReferenceCount > 1 ? $"|{dependency.ReferenceCount}|" : "";
|
|
mermaid.AppendLine($" {SanitizeNodeName(dependency.From)} {arrow} {SanitizeNodeName(dependency.To)} {label}");
|
|
}
|
|
|
|
// Add enhanced styling
|
|
mermaid.AppendLine();
|
|
mermaid.AppendLine(" classDef service fill:#e3f2fd,stroke:#1976d2,stroke-width:2px");
|
|
mermaid.AppendLine(" classDef ui fill:#fce4ec,stroke:#c2185b,stroke-width:2px");
|
|
mermaid.AppendLine(" classDef data fill:#e8f5e8,stroke:#388e3c,stroke-width:2px");
|
|
mermaid.AppendLine(" classDef platform fill:#fff3e0,stroke:#f57c00,stroke-width:2px");
|
|
mermaid.AppendLine(" classDef utility fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px");
|
|
|
|
return mermaid.ToString();
|
|
}
|
|
|
|
private string GetModuleStyle(LogicalModule module)
|
|
{
|
|
return module.ModuleType.ToLower() switch
|
|
{
|
|
"service" => ":::service",
|
|
"ui" => ":::ui",
|
|
"data" => ":::data",
|
|
"platform" => ":::platform",
|
|
"utility" => ":::utility",
|
|
_ => ""
|
|
};
|
|
}
|
|
|
|
private object GenerateModularCytoscapeJson(ModularStructure modularStructure)
|
|
{
|
|
return new
|
|
{
|
|
elements = new
|
|
{
|
|
nodes = modularStructure.Modules.Select(m => new
|
|
{
|
|
data = new
|
|
{
|
|
id = m.Name,
|
|
label = m.Name,
|
|
moduleType = m.ModuleType,
|
|
platformSpecific = m.PlatformSpecific,
|
|
platforms = string.Join(",", m.Platforms),
|
|
entryPointCount = m.EntryPoints.Count,
|
|
interfaceCount = m.PublicInterfaces.Count,
|
|
sourceFileCount = m.SourceFiles.Count,
|
|
reusabilityScore = modularStructure.ReusabilityAnalysis?.ModuleReusability
|
|
.FirstOrDefault(r => r.ModuleName == m.Name)?.ReusabilityScore ?? 0
|
|
}
|
|
}),
|
|
edges = modularStructure.ModuleDependencies.Select(d => new
|
|
{
|
|
data = new
|
|
{
|
|
source = d.From,
|
|
target = d.To,
|
|
dependencyType = d.DependencyType,
|
|
dependencyStrength = d.DependencyStrength,
|
|
weight = d.ReferenceCount
|
|
}
|
|
})
|
|
},
|
|
style = new object[]
|
|
{
|
|
new
|
|
{
|
|
selector = "node",
|
|
style = new
|
|
{
|
|
content = "data(label)",
|
|
width = "mapData(sourceFileCount, 1, 50, 30, 100)",
|
|
height = "mapData(entryPointCount, 0, 10, 30, 80)",
|
|
backgroundColor = "mapData(reusabilityScore, 0, 1, #ff6b6b, #51cf66)",
|
|
borderWidth = "mapData(interfaceCount, 0, 5, 1, 5)",
|
|
borderColor = "#333"
|
|
}
|
|
},
|
|
new
|
|
{
|
|
selector = "node[platformSpecific = 'true']",
|
|
style = new
|
|
{
|
|
shape = "diamond"
|
|
}
|
|
},
|
|
new
|
|
{
|
|
selector = "edge",
|
|
style = new
|
|
{
|
|
width = "mapData(weight, 1, 20, 2, 8)",
|
|
lineColor = "mapData(weight, 1, 20, #ddd, #333)",
|
|
targetArrowColor = "mapData(weight, 1, 20, #ddd, #333)",
|
|
targetArrowShape = "triangle",
|
|
curveStyle = "bezier"
|
|
}
|
|
}
|
|
},
|
|
layout = new
|
|
{
|
|
name = "cose",
|
|
directed = true,
|
|
padding = 20,
|
|
nodeRepulsion = 400000,
|
|
idealEdgeLength = 100,
|
|
edgeElasticity = 100
|
|
}
|
|
};
|
|
}
|
|
|
|
private string GenerateModularGraphvizDot(ModularStructure modularStructure)
|
|
{
|
|
var dot = new StringBuilder();
|
|
dot.AppendLine("digraph ModularMap {");
|
|
dot.AppendLine(" rankdir=TB;");
|
|
dot.AppendLine(" node [shape=box, style=filled];");
|
|
dot.AppendLine(" edge [fontsize=10];");
|
|
dot.AppendLine();
|
|
|
|
// Group modules by type for better layout
|
|
var modulesByType = modularStructure.Modules.GroupBy(m => m.ModuleType);
|
|
|
|
foreach (var typeGroup in modulesByType)
|
|
{
|
|
dot.AppendLine($" subgraph cluster_{typeGroup.Key.ToLower()} {{");
|
|
dot.AppendLine($" label=\"{typeGroup.Key} Modules\";");
|
|
dot.AppendLine(" style=rounded;");
|
|
|
|
foreach (var module in typeGroup)
|
|
{
|
|
var color = GetGraphvizColor(module);
|
|
var shape = module.PlatformSpecific ? "diamond" : "box";
|
|
var platformInfo = module.PlatformSpecific ? $"\\n[{string.Join(",", module.Platforms)}]" : "";
|
|
var label = $"{module.Name}\\n({module.ModuleType}){platformInfo}\\n{module.SourceFiles.Count} files";
|
|
|
|
dot.AppendLine($" \"{module.Name}\" [label=\"{label}\", fillcolor={color}, shape={shape}];");
|
|
}
|
|
|
|
dot.AppendLine(" }");
|
|
dot.AppendLine();
|
|
}
|
|
|
|
// Add edges with enhanced styling
|
|
foreach (var dependency in modularStructure.ModuleDependencies)
|
|
{
|
|
var style = dependency.DependencyStrength switch
|
|
{
|
|
"Strong" => "bold",
|
|
"Medium" => "solid",
|
|
_ => "dashed"
|
|
};
|
|
|
|
var color = dependency.DependencyStrength switch
|
|
{
|
|
"Strong" => "red",
|
|
"Medium" => "blue",
|
|
_ => "gray"
|
|
};
|
|
|
|
var label = dependency.ReferenceCount > 1 ? $"[label=\"{dependency.ReferenceCount}\"]" : "";
|
|
dot.AppendLine($" \"{dependency.From}\" -> \"{dependency.To}\" [style={style}, color={color}] {label};");
|
|
}
|
|
|
|
dot.AppendLine("}");
|
|
return dot.ToString();
|
|
}
|
|
|
|
private string GetGraphvizColor(LogicalModule module)
|
|
{
|
|
return module.ModuleType.ToLower() switch
|
|
{
|
|
"service" => "lightblue",
|
|
"ui" => "lightpink",
|
|
"data" => "lightgreen",
|
|
"platform" => "orange",
|
|
"utility" => "lightyellow",
|
|
_ => "lightgray"
|
|
};
|
|
}
|
|
|
|
// Update the main execution method to use the new GenerateDependencyMapAsync signature
|
|
// (This would go in the ExecuteAsync method where GenerateDependencyMapAsync is called)
|
|
// var dependencyMap = await GenerateDependencyMapAsync(analysisResult, outputFormat, modularStructure);
|
|
}
|
|
|
|
// Supporting data structures
|
|
public class AnalysisOptions
|
|
{
|
|
public bool IncludeExternalDependencies { get; set; }
|
|
public bool InternalOnly { get; set; }
|
|
public int MaxDepth { get; set; }
|
|
public string? NamespaceFilter { get; set; }
|
|
public bool IncludeMethodLevel { get; set; }
|
|
}
|
|
|
|
public class ProjectAnalysisResult
|
|
{
|
|
public string ProjectPath { get; set; } = string.Empty;
|
|
public List<ModuleInfo> Modules { get; set; } = new();
|
|
public List<DependencyInfo> Dependencies { get; set; } = new();
|
|
public List<ExternalDependencyInfo> ExternalDependencies { get; set; } = new();
|
|
public int MaxDepthReached { get; set; }
|
|
}
|
|
|
|
public class ModuleInfo
|
|
{
|
|
public string Name { get; set; } = string.Empty;
|
|
public string Namespace { get; set; } = string.Empty;
|
|
public string Type { get; set; } = string.Empty; // Library, WebAPI, Console, etc.
|
|
public int LineCount { get; set; }
|
|
public int ClassCount { get; set; }
|
|
public string TargetFramework { get; set; } = string.Empty;
|
|
public string ProjectPath { get; set; } = string.Empty;
|
|
public List<string> SourceFiles { get; set; } = new();
|
|
public List<string> Namespaces { get; set; } = new();
|
|
public List<string> Classes { get; set; } = new();
|
|
}
|
|
|
|
public class DependencyInfo
|
|
{
|
|
public string From { get; set; } = string.Empty;
|
|
public string To { get; set; } = string.Empty;
|
|
public string Type { get; set; } = string.Empty; // ProjectReference, PackageReference, NamespaceReference
|
|
public string Strength { get; set; } = string.Empty; // Strong, Medium, Weak
|
|
public int ReferenceCount { get; set; }
|
|
}
|
|
|
|
public class ExternalDependencyInfo
|
|
{
|
|
public string Name { get; set; } = string.Empty;
|
|
public string Version { get; set; } = string.Empty;
|
|
public string Type { get; set; } = string.Empty; // NuGet, Framework
|
|
public List<string> UsedBy { get; set; } = new();
|
|
}
|
|
|
|
public class CouplingMetrics
|
|
{
|
|
public double OverallCouplingScore { get; set; }
|
|
public List<string> HighlyCoupledModules { get; set; } = new();
|
|
public List<string> LooselyCoupledModules { get; set; } = new();
|
|
public Dictionary<string, double> InstabilityScores { get; set; } = new();
|
|
public List<string> CircularDependencies { get; set; } = new();
|
|
public List<string> Recommendations { get; set; } = new();
|
|
}
|
|
|
|
public class ModuleCouplingMetrics
|
|
{
|
|
public int AfferentCoupling { get; set; }
|
|
public int EfferentCoupling { get; set; }
|
|
public double Instability { get; set; }
|
|
public double Abstractness { get; set; }
|
|
public double Distance { get; set; }
|
|
}
|
|
|
|
public class ArchitecturalPatterns
|
|
{
|
|
public string DetectedPattern { get; set; } = string.Empty;
|
|
public double Confidence { get; set; }
|
|
public List<string> PatternViolations { get; set; } = new();
|
|
public Dictionary<string, List<string>> LayerDefinitions { get; set; } = new();
|
|
public List<string> Suggestions { get; set; } = new();
|
|
}
|
|
|
|
public class ArchitecturalInsights
|
|
{
|
|
public double OverallArchitectureScore { get; set; }
|
|
public List<string> StrengthAreas { get; set; } = new();
|
|
public List<string> ImprovementAreas { get; set; } = new();
|
|
public List<RefactoringOpportunity> RefactoringOpportunities { get; set; } = new();
|
|
public List<string> DesignPatternSuggestions { get; set; } = new();
|
|
}
|
|
|
|
public class RefactoringOpportunity
|
|
{
|
|
public string Type { get; set; } = string.Empty;
|
|
public string Target { get; set; } = string.Empty;
|
|
public string Benefit { get; set; } = string.Empty;
|
|
public string Effort { get; set; } = string.Empty; // Low, Medium, High
|
|
public string Priority { get; set; } = string.Empty; // Low, Medium, High
|
|
}
|
|
|
|
public class LogicalModule
|
|
{
|
|
public string Name { get; set; } = string.Empty;
|
|
public List<string> Namespaces { get; set; } = new();
|
|
public string ModuleType { get; set; } = string.Empty;
|
|
public bool PlatformSpecific { get; set; }
|
|
public List<string> Platforms { get; set; } = new();
|
|
public List<string> EntryPoints { get; set; } = new();
|
|
public List<string> PublicInterfaces { get; set; } = new();
|
|
public List<string> ExternalDependencies { get; set; } = new();
|
|
public List<string> SourceFiles { get; set; } = new();
|
|
|
|
// Add these missing properties
|
|
public List<string> Tags { get; set; } = new();
|
|
public ScaffoldingMetadata ScaffoldingMetadata { get; set; } = new();
|
|
public LlmMetadata LlmMetadata { get; set; } = new();
|
|
public ModuleFlags ModuleFlags { get; set; } = new();
|
|
}
|
|
|
|
// Add these missing properties to the existing ModularOptions class
|
|
public class ModularOptions
|
|
{
|
|
public string GroupingStrategy { get; set; } = string.Empty;
|
|
public bool DetectPlatformModules { get; set; }
|
|
public bool IncludeEntryPoints { get; set; }
|
|
|
|
// Add these missing properties
|
|
public bool GenerateScaffoldingMetadata { get; set; }
|
|
public bool IncludeLlmDescriptions { get; set; }
|
|
public bool AnalyzeModuleTags { get; set; }
|
|
public bool IncludeModuleFlags { get; set; }
|
|
}
|
|
|
|
// Add these missing classes
|
|
public class ScaffoldingMetadata
|
|
{
|
|
public bool Scaffoldable { get; set; }
|
|
public List<string> MinimumDependencies { get; set; } = new();
|
|
public DateTime LastUpdated { get; set; }
|
|
public bool IsDeprecated { get; set; }
|
|
public double ComplexityScore { get; set; }
|
|
public List<string> SetupInstructions { get; set; } = new();
|
|
public List<string> ConfigurationFiles { get; set; } = new();
|
|
public List<string> RequiredEnvironmentVariables { get; set; } = new();
|
|
}
|
|
|
|
public class LlmMetadata
|
|
{
|
|
public string PromptDescription { get; set; } = string.Empty;
|
|
public string UsageExample { get; set; } = string.Empty;
|
|
public List<string> IntegrationNotes { get; set; } = new();
|
|
public List<string> CommonUseCases { get; set; } = new();
|
|
public List<string> AlternativeModules { get; set; } = new();
|
|
}
|
|
|
|
public class ModuleFlags
|
|
{
|
|
public bool Optional { get; set; }
|
|
public bool DefaultIncluded { get; set; }
|
|
public bool CoreModule { get; set; }
|
|
public bool ExperimentalFeature { get; set; }
|
|
public bool RequiresConfiguration { get; set; }
|
|
public bool HasBreakingChanges { get; set; }
|
|
}
|
|
|
|
public class ModuleDependencyInfo
|
|
{
|
|
public string ModuleName { get; set; } = string.Empty;
|
|
public bool IsRequired { get; set; }
|
|
public double ComplexityImpact { get; set; }
|
|
}
|
|
}
|