587 lines
21 KiB
C#
Executable File
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;
|
|
}
|
|
}
|
|
} |