MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Refacto.../Pipeline/RefactoringPipeline.cs

587 lines
21 KiB
C#
Executable File

using MarketAlly.AIPlugin;
using MarketAlly.AIPlugin.Refactoring.Core;
using MarketAlly.AIPlugin.Refactoring.Telemetry;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MarketAlly.AIPlugin.Refactoring.Pipeline
{
public interface IRefactoringStage
{
string Name { get; }
int Priority { get; }
bool IsEnabled { get; set; }
Task<RefactoringContext> ProcessAsync(RefactoringContext context, CancellationToken cancellationToken = default);
Task<bool> CanProcessAsync(RefactoringContext context);
Task InitializeAsync(IReadOnlyDictionary<string, object> configuration);
Task CleanupAsync();
}
public interface IRefactoringPipeline
{
Task<PipelineResult> ExecuteAsync(
RefactoringContext context,
CancellationToken cancellationToken = default);
void AddStage(IRefactoringStage stage);
void RemoveStage(string stageName);
void ConfigureStage(string stageName, IReadOnlyDictionary<string, object> configuration);
IEnumerable<IRefactoringStage> GetStages();
PipelineStatistics GetStatistics();
}
public class RefactoringContext
{
public string? ProjectPath { get; set; }
public List<string> FilePaths { get; set; } = new();
public IReadOnlyDictionary<string, object> Parameters { get; set; } = new Dictionary<string, object>();
public Dictionary<string, object> Data { get; set; } = new();
public List<string> Operations { get; set; } = new();
public bool ShouldStop { get; set; }
public string? StopReason { get; set; }
public List<AIPluginResult> Results { get; set; } = new();
public List<string> Warnings { get; set; } = new();
public List<string> Errors { get; set; } = new();
public DateTime StartTime { get; set; } = DateTime.UtcNow;
public Dictionary<string, TimeSpan> StageTimings { get; set; } = new();
}
public class PipelineResult
{
public bool Success { get; set; }
public RefactoringContext Context { get; set; } = new();
public List<StageResult> StageResults { get; set; } = new();
public TimeSpan TotalDuration { get; set; }
public string? FailureReason { get; set; }
public Exception? Exception { get; set; }
}
public class StageResult
{
public string StageName { get; set; } = string.Empty;
public bool Success { get; set; }
public TimeSpan Duration { get; set; }
public string? Error { get; set; }
public Dictionary<string, object> Metrics { get; set; } = new();
}
public class PipelineStatistics
{
public int TotalExecutions { get; set; }
public int SuccessfulExecutions { get; set; }
public int FailedExecutions { get; set; }
public TimeSpan AverageExecutionTime { get; set; }
public Dictionary<string, StageStatistics> StageStats { get; set; } = new();
}
public class StageStatistics
{
public string StageName { get; set; } = string.Empty;
public int Executions { get; set; }
public int Successes { get; set; }
public int Failures { get; set; }
public TimeSpan AverageDuration { get; set; }
public TimeSpan MinDuration { get; set; }
public TimeSpan MaxDuration { get; set; }
}
public class RefactoringPipeline : IRefactoringPipeline, IDisposable
{
private readonly List<IRefactoringStage> _stages = new();
private readonly ILogger<RefactoringPipeline>? _logger;
private readonly IRefactoringTelemetry _telemetry;
private readonly Dictionary<string, StageStatistics> _stageStats = new();
private readonly object _statsLock = new();
private int _totalExecutions = 0;
private int _successfulExecutions = 0;
public RefactoringPipeline(
ILogger<RefactoringPipeline>? logger = null,
IRefactoringTelemetry? telemetry = null)
{
_logger = logger;
_telemetry = telemetry ?? TelemetryFactory.Default;
}
public async Task<PipelineResult> ExecuteAsync(
RefactoringContext context,
CancellationToken cancellationToken = default)
{
return await _telemetry.TrackOperationAsync("Pipeline.Execute", async () =>
{
var result = new PipelineResult
{
Context = context,
StageResults = new List<StageResult>(),
Success = true // Initialize to true, will be set to false if any stage fails
};
var pipelineStopwatch = System.Diagnostics.Stopwatch.StartNew();
try
{
_logger?.LogInformation("Starting refactoring pipeline with {StageCount} stages", _stages.Count);
// Sort stages by priority
var sortedStages = _stages
.Where(s => s.IsEnabled)
.OrderBy(s => s.Priority)
.ToList();
foreach (var stage in sortedStages)
{
if (context.ShouldStop)
{
_logger?.LogInformation("Pipeline execution stopped at stage {StageName}: {Reason}",
stage.Name, context.StopReason);
break;
}
var stageResult = await ExecuteStageAsync(stage, context, cancellationToken);
result.StageResults.Add(stageResult);
if (!stageResult.Success)
{
result.Success = false;
result.FailureReason = $"Stage '{stage.Name}' failed: {stageResult.Error}";
break;
}
}
pipelineStopwatch.Stop();
result.TotalDuration = pipelineStopwatch.Elapsed;
if (result.Success && !context.ShouldStop)
{
result.Success = true;
Interlocked.Increment(ref _successfulExecutions);
_logger?.LogInformation("Pipeline execution completed successfully in {Duration}ms",
pipelineStopwatch.ElapsedMilliseconds);
}
else
{
_logger?.LogWarning("Pipeline execution completed with issues in {Duration}ms",
pipelineStopwatch.ElapsedMilliseconds);
}
}
catch (Exception ex)
{
pipelineStopwatch.Stop();
result.Success = false;
result.Exception = ex;
result.FailureReason = ex.Message;
result.TotalDuration = pipelineStopwatch.Elapsed;
_logger?.LogError(ex, "Pipeline execution failed after {Duration}ms",
pipelineStopwatch.ElapsedMilliseconds);
}
finally
{
Interlocked.Increment(ref _totalExecutions);
}
return result;
});
}
private async Task<StageResult> ExecuteStageAsync(
IRefactoringStage stage,
RefactoringContext context,
CancellationToken cancellationToken)
{
var stageResult = new StageResult
{
StageName = stage.Name
};
var stageStopwatch = System.Diagnostics.Stopwatch.StartNew();
try
{
_logger?.LogDebug("Executing stage: {StageName}", stage.Name);
// Check if stage can process the context
if (!await stage.CanProcessAsync(context))
{
_logger?.LogDebug("Stage {StageName} skipped - cannot process context", stage.Name);
stageResult.Success = true; // Skipping is not a failure
return stageResult;
}
// Execute the stage
context = await stage.ProcessAsync(context, cancellationToken);
stageResult.Success = true;
_logger?.LogDebug("Stage {StageName} completed successfully in {Duration}ms",
stage.Name, stageStopwatch.ElapsedMilliseconds);
}
catch (Exception ex)
{
stageResult.Success = false;
stageResult.Error = ex.Message;
_logger?.LogError(ex, "Stage {StageName} failed after {Duration}ms",
stage.Name, stageStopwatch.ElapsedMilliseconds);
}
finally
{
stageStopwatch.Stop();
stageResult.Duration = stageStopwatch.Elapsed;
context.StageTimings[stage.Name] = stageResult.Duration;
// Update stage statistics
UpdateStageStatistics(stage.Name, stageResult.Duration, stageResult.Success);
}
return stageResult;
}
public void AddStage(IRefactoringStage stage)
{
if (stage == null)
throw new ArgumentNullException(nameof(stage));
if (_stages.Any(s => s.Name == stage.Name))
throw new InvalidOperationException($"Stage with name '{stage.Name}' already exists");
_stages.Add(stage);
_logger?.LogDebug("Added stage: {StageName} with priority {Priority}", stage.Name, stage.Priority);
}
public void RemoveStage(string stageName)
{
var stage = _stages.FirstOrDefault(s => s.Name == stageName);
if (stage != null)
{
_stages.Remove(stage);
_logger?.LogDebug("Removed stage: {StageName}", stageName);
}
}
public void ConfigureStage(string stageName, IReadOnlyDictionary<string, object> configuration)
{
var stage = _stages.FirstOrDefault(s => s.Name == stageName);
if (stage != null)
{
_ = Task.Run(async () =>
{
try
{
await stage.InitializeAsync(configuration);
_logger?.LogDebug("Configured stage: {StageName}", stageName);
}
catch (Exception ex)
{
_logger?.LogError(ex, "Failed to configure stage: {StageName}", stageName);
}
});
}
}
public IEnumerable<IRefactoringStage> GetStages()
{
return _stages.AsReadOnly();
}
public PipelineStatistics GetStatistics()
{
lock (_statsLock)
{
var totalDuration = _stageStats.Values.Sum(s => s.AverageDuration.TotalMilliseconds * s.Executions);
var averageExecution = _totalExecutions > 0
? TimeSpan.FromMilliseconds(totalDuration / _totalExecutions)
: TimeSpan.Zero;
return new PipelineStatistics
{
TotalExecutions = _totalExecutions,
SuccessfulExecutions = _successfulExecutions,
FailedExecutions = _totalExecutions - _successfulExecutions,
AverageExecutionTime = averageExecution,
StageStats = _stageStats.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
};
}
}
private void UpdateStageStatistics(string stageName, TimeSpan duration, bool success)
{
lock (_statsLock)
{
if (!_stageStats.TryGetValue(stageName, out var stats))
{
stats = new StageStatistics
{
StageName = stageName,
MinDuration = duration,
MaxDuration = duration
};
_stageStats[stageName] = stats;
}
stats.Executions++;
if (success) stats.Successes++;
else stats.Failures++;
if (duration < stats.MinDuration) stats.MinDuration = duration;
if (duration > stats.MaxDuration) stats.MaxDuration = duration;
// Calculate average duration
var totalDuration = stats.AverageDuration.TotalMilliseconds * (stats.Executions - 1) + duration.TotalMilliseconds;
stats.AverageDuration = TimeSpan.FromMilliseconds(totalDuration / stats.Executions);
}
}
public void Dispose()
{
// Cleanup all stages
var cleanupTasks = _stages.Select(stage =>
Task.Run(async () =>
{
try
{
await stage.CleanupAsync();
}
catch (Exception ex)
{
_logger?.LogError(ex, "Failed to cleanup stage: {StageName}", stage.Name);
}
}));
try
{
Task.WaitAll(cleanupTasks.ToArray(), TimeSpan.FromSeconds(30));
}
catch (Exception ex)
{
_logger?.LogError(ex, "Failed to cleanup some pipeline stages");
}
}
}
// Base class for refactoring stages
public abstract class BaseRefactoringStage : IRefactoringStage
{
protected readonly ILogger? Logger;
protected BaseRefactoringStage(ILogger? logger = null)
{
Logger = logger;
}
public abstract string Name { get; }
public abstract int Priority { get; }
public bool IsEnabled { get; set; } = true;
public abstract Task<RefactoringContext> ProcessAsync(RefactoringContext context, CancellationToken cancellationToken = default);
public virtual Task<bool> CanProcessAsync(RefactoringContext context)
{
return Task.FromResult(IsEnabled);
}
public virtual Task InitializeAsync(IReadOnlyDictionary<string, object> configuration)
{
return Task.CompletedTask;
}
public virtual Task CleanupAsync()
{
return Task.CompletedTask;
}
}
// Example pipeline stages
public class ValidationStage : BaseRefactoringStage
{
public override string Name => "Validation";
public override int Priority => 10;
public ValidationStage(ILogger<ValidationStage>? logger = null) : base(logger) { }
public override async Task<RefactoringContext> ProcessAsync(RefactoringContext context, CancellationToken cancellationToken = default)
{
Logger?.LogDebug("Validating refactoring context");
// Validate project path
if (string.IsNullOrEmpty(context.ProjectPath))
{
context.Errors.Add("Project path is required");
context.ShouldStop = true;
context.StopReason = "Validation failed: Missing project path";
return context;
}
// Validate file paths
if (!context.FilePaths.Any())
{
context.Warnings.Add("No files specified for processing");
}
// Validate operations
if (!context.Operations.Any())
{
context.Errors.Add("No operations specified");
context.ShouldStop = true;
context.StopReason = "Validation failed: No operations specified";
return context;
}
Logger?.LogDebug("Validation completed successfully");
return context;
}
}
public class FileDiscoveryStage : BaseRefactoringStage
{
public override string Name => "FileDiscovery";
public override int Priority => 20;
public FileDiscoveryStage(ILogger<FileDiscoveryStage>? logger = null) : base(logger) { }
public override async Task<RefactoringContext> ProcessAsync(RefactoringContext context, CancellationToken cancellationToken = default)
{
Logger?.LogDebug("Discovering files for processing");
if (!context.FilePaths.Any() && !string.IsNullOrEmpty(context.ProjectPath))
{
// Auto-discover C# files in the project
var discoveredFiles = System.IO.Directory.GetFiles(context.ProjectPath, "*.cs", System.IO.SearchOption.AllDirectories)
.Where(f => !ShouldExcludeFile(f))
.ToList();
context.FilePaths.AddRange(discoveredFiles);
Logger?.LogDebug("Discovered {FileCount} files", discoveredFiles.Count);
}
context.Data["DiscoveredFileCount"] = context.FilePaths.Count;
return context;
}
private bool ShouldExcludeFile(string filePath)
{
var fileName = System.IO.Path.GetFileName(filePath);
var excludePatterns = new[] { ".Designer.cs", ".generated.cs", ".g.cs", "AssemblyInfo.cs" };
return excludePatterns.Any(pattern => fileName.Contains(pattern, StringComparison.OrdinalIgnoreCase));
}
}
public class OperationExecutionStage : BaseRefactoringStage
{
private readonly IServiceProvider? _serviceProvider;
public override string Name => "OperationExecution";
public override int Priority => 100;
public OperationExecutionStage(IServiceProvider? serviceProvider = null, ILogger<OperationExecutionStage>? logger = null)
: base(logger)
{
_serviceProvider = serviceProvider;
}
public override async Task<RefactoringContext> ProcessAsync(RefactoringContext context, CancellationToken cancellationToken = default)
{
Logger?.LogDebug("Executing refactoring operations");
foreach (var operation in context.Operations)
{
if (cancellationToken.IsCancellationRequested)
{
context.ShouldStop = true;
context.StopReason = "Operation cancelled";
break;
}
try
{
var result = await ExecuteOperationAsync(operation, context, cancellationToken);
context.Results.Add(result);
if (!result.Success)
{
context.Errors.Add($"Operation '{operation}' failed: {result.Message}");
}
}
catch (Exception ex)
{
context.Errors.Add($"Operation '{operation}' threw exception: {ex.Message}");
Logger?.LogError(ex, "Operation {Operation} failed", operation);
}
}
context.Data["OperationsExecuted"] = context.Results.Count;
context.Data["SuccessfulOperations"] = context.Results.Count(r => r.Success);
return context;
}
private async Task<AIPluginResult> ExecuteOperationAsync(string operation, RefactoringContext context, CancellationToken cancellationToken)
{
// This would normally use the plugin discovery system to find and execute the appropriate plugin
// For now, we'll create a placeholder result
return await Task.FromResult(new AIPluginResult(new
{
Operation = operation,
Message = $"Operation '{operation}' executed successfully",
ProcessedFiles = context.FilePaths.Count
}));
}
}
// Pipeline builder for easy configuration
public class RefactoringPipelineBuilder
{
private readonly List<IRefactoringStage> _stages = new();
private ILogger<RefactoringPipeline>? _logger;
private IRefactoringTelemetry? _telemetry;
public RefactoringPipelineBuilder WithLogger(ILogger<RefactoringPipeline> logger)
{
_logger = logger;
return this;
}
public RefactoringPipelineBuilder WithTelemetry(IRefactoringTelemetry telemetry)
{
_telemetry = telemetry;
return this;
}
public RefactoringPipelineBuilder AddStage(IRefactoringStage stage)
{
_stages.Add(stage);
return this;
}
public RefactoringPipelineBuilder AddValidation()
{
return AddStage(new ValidationStage());
}
public RefactoringPipelineBuilder AddFileDiscovery()
{
return AddStage(new FileDiscoveryStage());
}
public RefactoringPipelineBuilder AddOperationExecution(IServiceProvider? serviceProvider = null)
{
return AddStage(new OperationExecutionStage(serviceProvider));
}
public IRefactoringPipeline Build()
{
var pipeline = new RefactoringPipeline(_logger, _telemetry);
foreach (var stage in _stages)
{
pipeline.AddStage(stage);
}
return pipeline;
}
}
}