using MarketAlly.AIPlugin.Learning.Configuration; using MarketAlly.AIPlugin.Learning.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System.Text.Json; namespace MarketAlly.AIPlugin.Learning { /// /// Standalone plugin that provides unified context preparation combining /// real-time code intelligence with historical memory and decision tracking /// [AIPlugin("UnifiedContext", "Comprehensive context preparation combining real-time code analysis with historical insights and decision tracking")] public class UnifiedContextPlugin : IAIPlugin, IDisposable { [AIParameter("Action to perform", required: true)] public string Action { get; set; } = string.Empty; [AIParameter("Query or topic for context preparation", required: false)] public string Query { get; set; } = string.Empty; [AIParameter("File path for targeted analysis", required: false)] public string FilePath { get; set; } = string.Empty; [AIParameter("Project path for context initialization", required: false)] public string ProjectPath { get; set; } = string.Empty; [AIParameter("Maximum tokens for context preparation", required: false)] public int MaxTokens { get; set; } = 8000; [AIParameter("Insight or decision content to store", required: false)] public string Content { get; set; } = string.Empty; [AIParameter("Category for insight storage", required: false)] public string Category { get; set; } = string.Empty; [AIParameter("Session summary for finalization", required: false)] public string SessionSummary { get; set; } = string.Empty; [AIParameter("Additional metadata as JSON string", required: false)] public string Metadata { get; set; } = string.Empty; public IReadOnlyDictionary SupportedParameters => new Dictionary { ["action"] = typeof(string), ["query"] = typeof(string), ["filePath"] = typeof(string), ["projectPath"] = typeof(string), ["maxTokens"] = typeof(int), ["content"] = typeof(string), ["category"] = typeof(string), ["sessionSummary"] = typeof(string), ["metadata"] = typeof(string) }; private IServiceProvider? _serviceProvider; private bool _disposed = false; public async Task ExecuteAsync(IReadOnlyDictionary parameters) { try { // Build service provider _serviceProvider = BuildServiceProvider(parameters); var unifiedContextService = _serviceProvider.GetRequiredService(); var action = parameters["action"].ToString()?.ToLower() ?? ""; return action switch { "prepare-context" => await PrepareContextAsync(unifiedContextService, parameters), "initialize-session" => await InitializeSessionAsync(unifiedContextService, parameters), "store-insight" => await StoreInsightAsync(unifiedContextService, parameters), "find-similar" => await FindSimilarIssuesAsync(unifiedContextService, parameters), "get-decisions" => await GetRelatedDecisionsAsync(unifiedContextService, parameters), "store-decision" => await StoreDecisionAsync(unifiedContextService, parameters), "finalize-session" => await FinalizeSessionAsync(unifiedContextService, parameters), _ => new AIPluginResult(null, $"Unknown action: {action}. Supported actions: prepare-context, initialize-session, store-insight, find-similar, get-decisions, store-decision, finalize-session") }; } catch (Exception ex) { return new AIPluginResult(ex, $"UnifiedContext plugin failed: {ex.Message}"); } } private async Task PrepareContextAsync(IUnifiedContextService service, IReadOnlyDictionary parameters) { var query = parameters.GetValueOrDefault("query", "")?.ToString() ?? ""; var filePath = parameters.GetValueOrDefault("filePath", null)?.ToString(); var maxTokens = Convert.ToInt32(parameters.GetValueOrDefault("maxTokens", 8000)); if (string.IsNullOrWhiteSpace(query)) { return new AIPluginResult(null, "Query parameter is required for context preparation"); } var context = await service.PrepareFullContextAsync(query, filePath, maxTokens); var result = new { Query = context.Query, FilePath = context.FilePath, GeneratedAt = context.GeneratedAt, EstimatedTokens = context.EstimatedTotalTokens, CurrentAnalysis = new { CodeChunks = context.CurrentCodeAnalysis?.CodeChunks?.Count ?? 0, Tokens = context.CurrentCodeAnalysis?.EstimatedTokens ?? 0 }, HistoricalInsights = context.HistoricalInsights.Select(h => new { h.Summary, h.Relevance, h.Timestamp, h.Tags }).ToList(), RelatedDecisions = context.RelatedDecisions.Select(d => new { d.Summary, d.Successful, d.Relevance, d.Timestamp, d.Tags }).ToList(), ProjectContext = context.ProjectContext != null ? new { context.ProjectContext.ProjectPath, RecentChangesCount = context.ProjectContext.RecentChanges.Count, context.ProjectContext.LastAnalyzed } : null }; return new AIPluginResult(result, $"Comprehensive context prepared: {context.EstimatedTotalTokens} tokens, {context.HistoricalInsights.Count} insights, {context.RelatedDecisions.Count} decisions"); } private async Task InitializeSessionAsync(IUnifiedContextService service, IReadOnlyDictionary parameters) { var projectPath = parameters.GetValueOrDefault("projectPath", "")?.ToString() ?? ""; var query = parameters.GetValueOrDefault("query", "Learning session")?.ToString() ?? "Learning session"; if (string.IsNullOrWhiteSpace(projectPath)) { return new AIPluginResult(null, "ProjectPath parameter is required for session initialization"); } var sessionContext = await service.InitializeLearningSessionAsync(projectPath, query); var result = new { sessionContext.SessionId, sessionContext.ProjectPath, sessionContext.Topic, sessionContext.InitializedAt, ProjectContext = sessionContext.ProjectContext != null ? new { RecentChangesCount = sessionContext.ProjectContext.RecentChanges.Count, HasProjectPath = !string.IsNullOrEmpty(sessionContext.ProjectContext.ProjectPath), sessionContext.ProjectContext.LastAnalyzed } : null }; return new AIPluginResult(result, $"Learning session initialized: {sessionContext.SessionId}"); } private async Task StoreInsightAsync(IUnifiedContextService service, IReadOnlyDictionary parameters) { var content = parameters.GetValueOrDefault("content", "")?.ToString() ?? ""; var category = parameters.GetValueOrDefault("category", "general")?.ToString() ?? "general"; var filePath = parameters.GetValueOrDefault("filePath", null)?.ToString(); var metadataStr = parameters.GetValueOrDefault("metadata", "{}")?.ToString() ?? "{}"; if (string.IsNullOrWhiteSpace(content)) { return new AIPluginResult(null, "Content parameter is required for insight storage"); } Dictionary? metadata = null; if (!string.IsNullOrWhiteSpace(metadataStr)) { try { metadata = JsonSerializer.Deserialize>(metadataStr); } catch { return new AIPluginResult(null, "Invalid JSON format in metadata parameter"); } } await service.StoreLearningInsightAsync(content, category, filePath, metadata); return new AIPluginResult(new { Success = true, Category = category, FilePath = filePath }, $"Insight stored successfully in category: {category}"); } private async Task FindSimilarIssuesAsync(IUnifiedContextService service, IReadOnlyDictionary parameters) { var query = parameters.GetValueOrDefault("query", "")?.ToString() ?? ""; var projectPath = parameters.GetValueOrDefault("projectPath", null)?.ToString(); if (string.IsNullOrWhiteSpace(query)) { return new AIPluginResult(null, "Query parameter is required for finding similar issues"); } var insights = await service.FindSimilarPastIssuesAsync(query, projectPath); var result = new { Query = query, ResultCount = insights.Count, Insights = insights.Select(i => new { i.Summary, i.Relevance, i.Timestamp, i.Tags, ContentPreview = i.Content.Count > 10 ? $"{i.Content.Count} items" : JsonSerializer.Serialize(i.Content) }).ToList() }; return new AIPluginResult(result, $"Found {insights.Count} similar past issues"); } private async Task GetRelatedDecisionsAsync(IUnifiedContextService service, IReadOnlyDictionary parameters) { var query = parameters.GetValueOrDefault("query", "")?.ToString() ?? ""; if (string.IsNullOrWhiteSpace(query)) { return new AIPluginResult(null, "Query parameter is required for finding related decisions"); } var decisions = await service.GetRelatedDecisionsAsync(query); var result = new { Query = query, ResultCount = decisions.Count, Decisions = decisions.Select(d => new { d.Summary, d.Successful, d.Relevance, d.Timestamp, d.Tags, ContentPreview = d.Content.Count > 10 ? $"{d.Content.Count} items" : JsonSerializer.Serialize(d.Content) }).ToList(), SuccessfulCount = decisions.Count(d => d.Successful), FailedCount = decisions.Count(d => !d.Successful) }; return new AIPluginResult(result, $"Found {decisions.Count} related decisions ({decisions.Count(d => d.Successful)} successful, {decisions.Count(d => !d.Successful)} failed)"); } private async Task StoreDecisionAsync(IUnifiedContextService service, IReadOnlyDictionary parameters) { var content = parameters.GetValueOrDefault("content", "")?.ToString() ?? ""; var filePath = parameters.GetValueOrDefault("filePath", "")?.ToString() ?? ""; var metadataStr = parameters.GetValueOrDefault("metadata", "{}")?.ToString() ?? "{}"; if (string.IsNullOrWhiteSpace(content) || string.IsNullOrWhiteSpace(filePath)) { return new AIPluginResult(null, "Content and FilePath parameters are required for decision storage"); } // Parse success from metadata or default to true var successful = true; try { if (!string.IsNullOrWhiteSpace(metadataStr)) { var metadata = JsonSerializer.Deserialize>(metadataStr); if (metadata?.ContainsKey("successful") == true) { successful = Convert.ToBoolean(metadata["successful"]); } } } catch { // Use default value } await service.StoreRefactoringDecisionAsync(content, "Decision stored via UnifiedContext plugin", filePath, successful); return new AIPluginResult(new { Success = true, Outcome = successful ? "Successful" : "Failed", FilePath = filePath }, $"Decision stored for {Path.GetFileName(filePath)}: {(successful ? "successful" : "failed")}"); } private async Task FinalizeSessionAsync(IUnifiedContextService service, IReadOnlyDictionary parameters) { var sessionSummary = parameters.GetValueOrDefault("sessionSummary", "")?.ToString() ?? ""; var metadataStr = parameters.GetValueOrDefault("metadata", "{}")?.ToString() ?? "{}"; if (string.IsNullOrWhiteSpace(sessionSummary)) { return new AIPluginResult(null, "SessionSummary parameter is required for session finalization"); } Dictionary metrics; try { metrics = JsonSerializer.Deserialize>(metadataStr) ?? new Dictionary(); } catch { return new AIPluginResult(null, "Invalid JSON format in metadata parameter"); } var summary = await service.FinalizeLearningSessionAsync(sessionSummary, metrics); var result = new { summary.SessionId, summary.ProjectPath, summary.Summary, summary.FinalizedAt, MetricsCount = summary.Metrics.Count }; return new AIPluginResult(result, $"Session finalized: {summary.SessionId}"); } private IServiceProvider BuildServiceProvider(IReadOnlyDictionary parameters) { var services = new ServiceCollection(); // Configuration var configuration = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["Learning:AI:EnableSemanticSearch"] = "true", ["Learning:AI:MaxSearchResults"] = "10", ["Learning:AI:MinSimilarityScore"] = "0.7", ["Learning:AI:MaxContextTokens"] = parameters.GetValueOrDefault("maxTokens", 8000).ToString(), ["Learning:Security:EnablePathValidation"] = "true", ["Learning:Security:EnableInputSanitization"] = "true" }) .Build(); services.AddSingleton(configuration); services.Configure(configuration.GetSection(LearningConfiguration.SectionName)); // Logging services.AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information)); // Core services services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); // Mock RefactorIQ client (would need actual implementation) // services.AddSingleton(); return services.BuildServiceProvider(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed && disposing) { if (_serviceProvider is IDisposable disposableProvider) { disposableProvider.Dispose(); } } _disposed = true; } } }