MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Learning/RefactorIQIntegration.cs

544 lines
19 KiB
C#
Executable File

using MarketAlly.ProjectDetector;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using RefactorIQ.Core;
using RefactorIQ.Core.Models;
using RefactorIQ.Domain.Models;
using RefactorIQ.Domain.Models.Aggregates;
using RefactorIQ.Persistence.Models;
using RefactorIQ.Services;
using RefactorIQ.Services.Configuration;
using RefactorIQ.Services.Interfaces;
using RefactorIQ.Services.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MarketAlly.AIPlugin.Learning
{
public class RefactorIQIntegration
{
private readonly string _configPath;
private readonly IConfiguration _configuration;
private IServiceProvider _serviceProvider;
private IRefactorIQClient _client;
public RefactorIQIntegration(string configPath)
{
_configPath = configPath ?? Directory.GetCurrentDirectory();
_configuration = BuildConfiguration();
_serviceProvider = BuildServiceProvider();
_client = _serviceProvider.GetRequiredService<IRefactorIQClient>();
}
/// <summary>
/// Create a RefactorIQIntegration instance with project-specific database
/// </summary>
public static RefactorIQIntegration ForProject(Guid projectId, Guid tenantId, string? databaseDirectory = null)
{
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddConsole());
// Build configuration
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true)
.AddEnvironmentVariables()
.Build();
// Configure RefactorIQ with project-specific database
services.AddRefactorIQServices(options =>
{
// Set project-specific database path with tenant organization
// Use configured path from appsettings or environment variable
var baseRefactorIQPath = configuration["RefactorIQ:DatabaseBasePath"]
?? Environment.GetEnvironmentVariable("REFACTORIQ_DATABASE_PATH")
?? "/home/repositories"; // Default to repositories base path
// Organize by tenant: /home/repositories/{tenantId}/refactoriq/{projectId}.refactoriq.sqlite
var dbDirectory = databaseDirectory ?? Path.Combine(baseRefactorIQPath, tenantId.ToString(), "refactoriq");
Directory.CreateDirectory(dbDirectory);
var dbPath = Path.Combine(dbDirectory, $"{projectId}.refactoriq.sqlite");
options.ConnectionString = $"Data Source={dbPath}";
// Configure database options
options.Database.UseSolutionSpecificDatabase = false; // We're handling it manually
// Configure OpenAI settings from configuration
var openAIApiKey = configuration["RefactorIQ:OpenAI:ApiKey"] ?? Environment.GetEnvironmentVariable("OPENAI_API_KEY");
if (!string.IsNullOrEmpty(openAIApiKey))
{
options.OpenAI.ApiKey = openAIApiKey;
options.OpenAI.Model = configuration["RefactorIQ:OpenAI:Model"] ?? "text-embedding-3-small";
options.OpenAI.MaxRetries = int.Parse(configuration["RefactorIQ:OpenAI:MaxRetries"] ?? "3");
}
// Configure embedding settings
options.Embedding.BatchSize = int.Parse(configuration["RefactorIQ:Embedding:BatchSize"] ?? "10");
options.Embedding.EnableProgressSaving = bool.Parse(configuration["RefactorIQ:Embedding:EnableProgressSaving"] ?? "true");
options.Embedding.ProgressSaveInterval = int.Parse(configuration["RefactorIQ:Embedding:ProgressSaveInterval"] ?? "10");
});
// Add performance optimizations (caching, parallel processing)
services.AddPerformanceOptimization(configuration);
var serviceProvider = services.BuildServiceProvider();
// Ensure database schema is created by running migrations
using (var scope = serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<RefactorIQ.Persistence.AppDbContext>();
try
{
// Run migrations to create the database schema (including Solutions table)
dbContext.Database.Migrate();
}
catch (Exception migrationEx)
{
// If migrations fail, try EnsureCreated as fallback
var logger = serviceProvider.GetRequiredService<ILogger<RefactorIQIntegration>>();
logger.LogWarning(migrationEx, "Failed to run migrations, falling back to EnsureCreated");
dbContext.Database.EnsureCreated();
}
}
var client = serviceProvider.GetRequiredService<IRefactorIQClient>();
// Log database creation
var logger2 = serviceProvider.GetRequiredService<ILogger<RefactorIQIntegration>>();
logger2.LogInformation("Created and initialized RefactorIQ database for project {ProjectId} in directory: {DatabaseDirectory}", projectId, databaseDirectory ?? Directory.GetCurrentDirectory());
return new RefactorIQIntegration(databaseDirectory ?? Directory.GetCurrentDirectory())
{
_serviceProvider = serviceProvider,
_client = client
};
}
private IConfiguration BuildConfiguration()
{
var configDir = Directory.Exists(_configPath) ? _configPath : Path.GetDirectoryName(_configPath);
return new ConfigurationBuilder()
.SetBasePath(configDir)
.AddJsonFile("appsettings.json", optional: true)
.AddEnvironmentVariables()
.Build();
}
private IServiceProvider BuildServiceProvider()
{
var services = new ServiceCollection();
// Add logging
services.AddLogging(builder => builder.AddConsole());
// Add RefactorIQ services with correct configuration
services.AddRefactorIQServices(options =>
{
// Set connection string
var connectionString = _configuration.GetConnectionString("RefactorIQ");
if (string.IsNullOrEmpty(connectionString))
{
var dbPath = Path.Combine(_configPath, "refactoriq.db");
connectionString = $"Data Source={dbPath}";
}
options.ConnectionString = connectionString;
// Configure database options for solution-specific databases
options.Database.UseSolutionSpecificDatabase = bool.Parse(_configuration["RefactorIQ:Database:UseSolutionSpecificDatabase"] ?? "true");
// Use project ID pattern for better uniqueness and multi-tenant safety
options.Database.DatabaseNamePattern = _configuration["RefactorIQ:Database:DatabaseNamePattern"] ?? "{ProjectId}.refactoriq.sqlite";
options.Database.DefaultPath = _configuration["RefactorIQ:Database:DefaultPath"] ?? "RefactorIQ.sqlite";
// Configure OpenAI settings
var openAIApiKey = _configuration["RefactorIQ:OpenAI:ApiKey"] ?? Environment.GetEnvironmentVariable("OPENAI_API_KEY");
if (!string.IsNullOrEmpty(openAIApiKey))
{
options.OpenAI.ApiKey = openAIApiKey;
options.OpenAI.Model = _configuration["RefactorIQ:OpenAI:Model"] ?? "text-embedding-3-small";
options.OpenAI.MaxRetries = int.Parse(_configuration["RefactorIQ:OpenAI:MaxRetries"] ?? "3");
}
// Configure embedding settings
options.Embedding.BatchSize = int.Parse(_configuration["RefactorIQ:Embedding:BatchSize"] ?? "10");
options.Embedding.EnableProgressSaving = bool.Parse(_configuration["RefactorIQ:Embedding:EnableProgressSaving"] ?? "true");
options.Embedding.ProgressSaveInterval = int.Parse(_configuration["RefactorIQ:Embedding:ProgressSaveInterval"] ?? "10");
});
// Add performance optimizations (caching, parallel processing)
services.AddPerformanceOptimization(_configuration);
var serviceProvider = services.BuildServiceProvider();
// Ensure database schema is created by running migrations (same fix as in main app)
using (var scope = serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<RefactorIQ.Persistence.AppDbContext>();
try
{
// Run migrations to create the database schema (including Solutions table)
dbContext.Database.Migrate();
}
catch (Exception migrationEx)
{
// If migrations fail, try EnsureCreated as fallback
var logger = serviceProvider.GetRequiredService<ILogger<RefactorIQIntegration>>();
logger.LogWarning(migrationEx, "Failed to run migrations, falling back to EnsureCreated");
dbContext.Database.EnsureCreated();
}
}
return serviceProvider;
}
/// <summary>
/// Index a multi-language project (TypeScript, JavaScript, Python, PHP, Java, Go)
/// </summary>
public virtual async Task<RefactorIQResult> IndexProjectAsync(string projectPath, ProjectType type = ProjectType.Unknown)
{
var result = new RefactorIQResult
{
StartTime = DateTime.UtcNow,
Operation = "IndexProject"
};
try
{
Console.WriteLine($"🔍 Starting RefactorIQ multi-language project indexing for: {projectPath}");
// Use IndexSolutionAsync which can handle both .sln files and project directories
// The MultiLanguageIndexerService will detect if it's a project directory and use IndexProjectAsync internally
var indexResult = await _client.IndexSolutionAsync(projectPath, type, CancellationToken.None);
result.Success = indexResult.IsSuccess;
result.Error = indexResult.ErrorMessage;
if (indexResult.IsSuccess && indexResult.Data != null)
{
var indexedSolution = indexResult.Data;
var types = indexedSolution.TypeIndex.Types;
result.SymbolCount = types.Sum(t => t.Members.Count);
result.TypeCount = types.Count;
result.ProjectCount = types.Select(t => t.ProjectName).Distinct().Count();
Console.WriteLine($"✅ RefactorIQ project indexing completed: {result.SymbolCount} symbols in {result.TypeCount} types");
Console.WriteLine($" Languages detected: {string.Join(", ", types.Select(t => t.Language).Distinct())}");
// Generate AI embeddings if configured
if (!string.IsNullOrEmpty(_configuration["RefactorIQ:OpenAI:ApiKey"]))
{
Console.WriteLine("🧠 Generating AI embeddings for multi-language project...");
var embeddingProgress = new Progress<RefactorIQ.Services.Models.EmbeddingProgress>(p =>
{
Console.WriteLine($"🤖 Embedding progress: {p.ProcessedItems}/{p.TotalItems} items");
});
var embeddingResult = await _client.GenerateEmbeddingsAsync(projectPath, embeddingProgress);
if (embeddingResult.IsSuccess)
{
Console.WriteLine("✅ AI embeddings generated for multi-language project");
}
else
{
Console.WriteLine($"⚠️ Embedding generation failed: {embeddingResult.ErrorMessage}");
}
}
}
else
{
Console.WriteLine($"❌ RefactorIQ project indexing failed: {result.Error}");
}
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
Console.WriteLine($"❌ RefactorIQ project indexing failed: {ex.Message}");
}
finally
{
result.EndTime = DateTime.UtcNow;
result.Duration = result.EndTime - result.StartTime;
}
return result;
}
public virtual async Task<RefactorIQResult> IndexSolutionAsync(string solutionPath, ProjectType type = ProjectType.Unknown)
{
var result = new RefactorIQResult
{
StartTime = DateTime.UtcNow,
Operation = "Index"
};
try
{
Console.WriteLine("🔍 Starting RefactorIQ indexing with enhanced services...");
// Use the correct IRefactorIQClient API
var indexResult = await _client.IndexSolutionAsync(solutionPath, type, CancellationToken.None);
result.Success = indexResult.IsSuccess;
result.Error = indexResult.ErrorMessage;
if (indexResult.IsSuccess && indexResult.Data != null)
{
var indexedSolution = indexResult.Data;
var types = indexedSolution.TypeIndex.Types;
result.SymbolCount = types.Sum(t => t.Members.Count);
result.TypeCount = types.Count;
result.ProjectCount = types.Select(t => t.ProjectName).Distinct().Count();
Console.WriteLine($"✅ RefactorIQ indexing completed: {result.SymbolCount} symbols in {result.TypeCount} types across {result.ProjectCount} projects");
// Generate AI embeddings if configured
if (!string.IsNullOrEmpty(_configuration["RefactorIQ:OpenAI:ApiKey"]))
{
Console.WriteLine("🧠 Generating AI embeddings...");
var embeddingProgress = new Progress<RefactorIQ.Services.Models.EmbeddingProgress>(p =>
{
Console.WriteLine($"🤖 Embedding progress: {p.ProcessedItems}/{p.TotalItems} items");
});
var embeddingResult = await _client.GenerateEmbeddingsAsync(solutionPath, embeddingProgress);
if (embeddingResult.IsSuccess)
{
Console.WriteLine("✅ AI embeddings generated");
}
else
{
Console.WriteLine($"⚠️ Embedding generation failed: {embeddingResult.ErrorMessage}");
}
}
}
else
{
Console.WriteLine($"❌ RefactorIQ indexing failed: {result.Error}");
}
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
Console.WriteLine($"❌ RefactorIQ indexing failed: {ex.Message}");
}
finally
{
result.EndTime = DateTime.UtcNow;
result.Duration = result.EndTime - result.StartTime;
}
return result;
}
public async Task<RefactorIQResult> RefreshSolutionAsync(string solutionPath, ProjectType type = ProjectType.Unknown)
{
var result = new RefactorIQResult
{
StartTime = DateTime.UtcNow,
Operation = "Refresh"
};
try
{
Console.WriteLine("🔄 Refreshing RefactorIQ database with incremental updates...");
var refreshResult = await _client.RefreshSolutionAsync(solutionPath, type, CancellationToken.None);
result.Success = refreshResult.IsSuccess;
result.Error = refreshResult.ErrorMessage;
if (refreshResult.IsSuccess && refreshResult.Data != null)
{
var indexedSolution = refreshResult.Data;
var types = indexedSolution.TypeIndex.Types;
result.SymbolCount = types.Sum(t => t.Members.Count);
result.TypeCount = types.Count;
result.ProjectCount = types.Select(t => t.ProjectName).Distinct().Count();
Console.WriteLine($"✅ RefactorIQ refresh completed: {result.SymbolCount} symbols in {result.TypeCount} types across {result.ProjectCount} projects");
// Refresh embeddings if AI features are enabled
if (!string.IsNullOrEmpty(_configuration["RefactorIQ:OpenAI:ApiKey"]))
{
Console.WriteLine("🧠 Refreshing AI embeddings for modified files...");
var embeddingProgress = new Progress<RefactorIQ.Services.Models.EmbeddingProgress>(p =>
{
Console.WriteLine($"🤖 Embedding refresh: {p.ProcessedItems}/{p.TotalItems} items");
});
// Get project names from the indexed solution
var projects = types.Select(t => t.ProjectName).Distinct().ToList();
foreach (var project in projects)
{
var embeddingResult = await _client.GenerateIncrementalEmbeddingsAsync(project, embeddingProgress);
if (!embeddingResult.IsSuccess)
{
Console.WriteLine($"⚠️ Embedding refresh failed for {project}: {embeddingResult.ErrorMessage}");
}
}
Console.WriteLine("✅ AI embeddings refreshed");
}
}
else
{
Console.WriteLine($"❌ RefactorIQ refresh failed: {result.Error}");
}
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
Console.WriteLine($"❌ RefactorIQ refresh failed: {ex.Message}");
}
finally
{
result.EndTime = DateTime.UtcNow;
result.Duration = result.EndTime - result.StartTime;
}
return result;
}
/// <summary>
/// Search for similar code patterns using AI embeddings
/// </summary>
public async Task<List<RefactorIQ.Core.Models.VectorSearchResult>> SearchSimilarCodeAsync(string query, string? projectName = null, int maxResults = 10)
{
try
{
Console.WriteLine($"🔍 Searching for similar code: '{query}'");
var searchResult = await _client.SearchSimilarAsync(query, projectName, maxResults);
if (searchResult.IsSuccess && searchResult.Data != null)
{
Console.WriteLine($"✅ Found {searchResult.Data.Count} similar code patterns");
return searchResult.Data;
}
else
{
Console.WriteLine($"❌ Search failed: {searchResult.ErrorMessage}");
return new List<RefactorIQ.Core.Models.VectorSearchResult>();
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Semantic search failed: {ex.Message}");
return new List<RefactorIQ.Core.Models.VectorSearchResult>();
}
}
/// <summary>
/// Get detailed embedding statistics
/// </summary>
public async Task<Dictionary<string, int>> GetEmbeddingStatsAsync()
{
try
{
var statsResult = await _client.GetEmbeddingStatsAsync();
if (statsResult.IsSuccess && statsResult.Data != null)
{
return statsResult.Data;
}
else
{
Console.WriteLine($"❌ Failed to get embedding stats: {statsResult.ErrorMessage}");
return new Dictionary<string, int>();
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Failed to get embedding stats: {ex.Message}");
return new Dictionary<string, int>();
}
}
/// <summary>
/// Get list of indexed projects
/// </summary>
public async Task<List<string>> GetIndexedProjectsAsync()
{
try
{
var projectsResult = await _client.GetProjectNamesAsync();
if (projectsResult.IsSuccess && projectsResult.Data != null)
{
return projectsResult.Data;
}
else
{
Console.WriteLine($"❌ Failed to get project names: {projectsResult.ErrorMessage}");
return new List<string>();
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Failed to get project names: {ex.Message}");
return new List<string>();
}
}
/// <summary>
/// Get indexed types from a solution for method extraction
/// </summary>
public async Task<List<RefactorIQ.Domain.Models.IndexedType>> GetIndexedTypesAsync(string solutionPath, ProjectType type = ProjectType.Unknown)
{
try
{
Console.WriteLine($"🔍 Getting indexed types from RefactorIQ for: {solutionPath}");
var indexResult = await _client.IndexSolutionAsync(solutionPath, type, CancellationToken.None);
if (indexResult.IsSuccess && indexResult.Data != null)
{
var indexedSolution = indexResult.Data;
var types = indexedSolution.TypeIndex.Types.ToList();
Console.WriteLine($"✅ Retrieved {types.Count} indexed types from RefactorIQ");
return types;
}
else
{
Console.WriteLine($"❌ Failed to get indexed types: {indexResult.ErrorMessage}");
return new List<RefactorIQ.Domain.Models.IndexedType>();
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Exception getting indexed types: {ex.Message}");
return new List<RefactorIQ.Domain.Models.IndexedType>();
}
}
/// <summary>
/// Clean up resources
/// </summary>
public void Dispose()
{
if (_serviceProvider is IDisposable disposable)
{
disposable.Dispose();
}
}
}
public class RefactorIQResult
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public TimeSpan Duration { get; set; }
public string Operation { get; set; }
public bool Success { get; set; }
public string Error { get; set; }
public int SymbolCount { get; set; }
public int TypeCount { get; set; }
public int ProjectCount { get; set; }
}
}