379 lines
17 KiB
C#
Executable File
379 lines
17 KiB
C#
Executable File
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
|
|
{
|
|
/// <summary>
|
|
/// Standalone plugin that provides unified context preparation combining
|
|
/// real-time code intelligence with historical memory and decision tracking
|
|
/// </summary>
|
|
[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<string, Type> SupportedParameters => new Dictionary<string, Type>
|
|
{
|
|
["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<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
|
|
{
|
|
try
|
|
{
|
|
// Build service provider
|
|
_serviceProvider = BuildServiceProvider(parameters);
|
|
var unifiedContextService = _serviceProvider.GetRequiredService<IUnifiedContextService>();
|
|
|
|
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<AIPluginResult> PrepareContextAsync(IUnifiedContextService service, IReadOnlyDictionary<string, object> 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<AIPluginResult> InitializeSessionAsync(IUnifiedContextService service, IReadOnlyDictionary<string, object> 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<AIPluginResult> StoreInsightAsync(IUnifiedContextService service, IReadOnlyDictionary<string, object> 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<string, object>? metadata = null;
|
|
if (!string.IsNullOrWhiteSpace(metadataStr))
|
|
{
|
|
try
|
|
{
|
|
metadata = JsonSerializer.Deserialize<Dictionary<string, object>>(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<AIPluginResult> FindSimilarIssuesAsync(IUnifiedContextService service, IReadOnlyDictionary<string, object> 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<AIPluginResult> GetRelatedDecisionsAsync(IUnifiedContextService service, IReadOnlyDictionary<string, object> 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<AIPluginResult> StoreDecisionAsync(IUnifiedContextService service, IReadOnlyDictionary<string, object> 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<Dictionary<string, object>>(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<AIPluginResult> FinalizeSessionAsync(IUnifiedContextService service, IReadOnlyDictionary<string, object> 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<string, object> metrics;
|
|
try
|
|
{
|
|
metrics = JsonSerializer.Deserialize<Dictionary<string, object>>(metadataStr) ?? new Dictionary<string, object>();
|
|
}
|
|
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<string, object> parameters)
|
|
{
|
|
var services = new ServiceCollection();
|
|
|
|
// Configuration
|
|
var configuration = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(new Dictionary<string, string?>
|
|
{
|
|
["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<IConfiguration>(configuration);
|
|
services.Configure<LearningConfiguration>(configuration.GetSection(LearningConfiguration.SectionName));
|
|
|
|
// Logging
|
|
services.AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information));
|
|
|
|
// Core services
|
|
services.AddSingleton<ISecurityService, SecurityService>();
|
|
services.AddSingleton<ILLMContextService, LLMContextService>();
|
|
services.AddSingleton<IUnifiedContextService, UnifiedContextService>();
|
|
|
|
// Mock RefactorIQ client (would need actual implementation)
|
|
// services.AddSingleton<IRefactorIQClient, MockRefactorIQClient>();
|
|
|
|
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;
|
|
}
|
|
}
|
|
} |