< Summary

Information
Class: Orchestrator.Commands.Observability.ContextChanges.ContextChangesCommand
Assembly: Orchestrator
File(s): /home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Commands/Observability/ContextChanges/ContextChangesCommand.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 123
Coverable lines: 123
Total lines: 279
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 64
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
ExecuteAsync()0%620%
ExecuteContextChanges()0%342180%
SelectDocuments(...)0%2040%
ShowDocumentChanges()0%272160%
ShowDocumentDiff(...)0%210140%
EscapeMarkup(...)100%210%
GenerateSimpleDiff(...)0%110100%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Commands/Observability/ContextChanges/ContextChangesCommand.cs

#LineLine coverage
 1using EHonda.KicktippAi.Core;
 2using Microsoft.Extensions.Logging;
 3using Spectre.Console.Cli;
 4using Spectre.Console;
 5using Orchestrator.Infrastructure.Factories;
 6
 7namespace Orchestrator.Commands.Observability.ContextChanges;
 8
 9/// <summary>
 10/// Command for showing changes between latest and previous versions of context documents.
 11/// </summary>
 12public class ContextChangesCommand : AsyncCommand<ContextChangesSettings>
 13{
 14    private readonly IAnsiConsole _console;
 15    private readonly IFirebaseServiceFactory _firebaseServiceFactory;
 16    private readonly ILogger<ContextChangesCommand> _logger;
 17
 018    public ContextChangesCommand(
 019        IAnsiConsole console,
 020        IFirebaseServiceFactory firebaseServiceFactory,
 021        ILogger<ContextChangesCommand> logger)
 22    {
 023        _console = console;
 024        _firebaseServiceFactory = firebaseServiceFactory;
 025        _logger = logger;
 026    }
 27
 28    public override async Task<int> ExecuteAsync(CommandContext context, ContextChangesSettings settings)
 29    {
 30
 31        try
 32        {
 033            _console.MarkupLine($"[green]Context changes command initialized for community context:[/] [yellow]{settings
 34
 035            if (settings.Verbose)
 36            {
 037                _console.MarkupLine("[dim]Verbose mode enabled[/]");
 38            }
 39
 40            // Execute the context changes workflow
 041            await ExecuteContextChanges(settings);
 42
 043            return 0;
 44        }
 045        catch (Exception ex)
 46        {
 047            _logger.LogError(ex, "Error executing context-changes command");
 048            _console.MarkupLine($"[red]Error:[/] {ex.Message}");
 049            return 1;
 50        }
 051    }
 52
 53    private async Task ExecuteContextChanges(ContextChangesSettings settings)
 54    {
 55        // Create context repository using factory (factory handles env var loading)
 056        var contextRepository = _firebaseServiceFactory.CreateContextRepository();
 57
 058        _console.MarkupLine($"[blue]Getting context document names for community:[/] [yellow]{settings.CommunityContext}
 59
 60        // Get all context document names
 061        var allDocumentNames = await contextRepository.GetContextDocumentNamesAsync(settings.CommunityContext);
 62
 063        if (!allDocumentNames.Any())
 64        {
 065            _console.MarkupLine("[yellow]No context documents found for this community[/]");
 066            return;
 67        }
 68
 069        if (settings.Verbose)
 70        {
 071            _console.MarkupLine($"[dim]Found {allDocumentNames.Count} context document(s)[/]");
 72        }
 73
 74        // Select documents to show (either random selection or all if count is larger)
 075        var documentsToShow = SelectDocuments(allDocumentNames, settings.Count, settings.Seed);
 76
 077        if (settings.Verbose && documentsToShow.Count < allDocumentNames.Count)
 78        {
 079            _console.MarkupLine($"[dim]Showing {documentsToShow.Count} of {allDocumentNames.Count} documents{(settings.S
 80        }
 81
 082        var changesFound = 0;
 83
 084        foreach (var documentName in documentsToShow)
 85        {
 086            if (settings.Verbose)
 87            {
 088                _console.MarkupLine($"[dim]Checking document: {documentName}[/]");
 89            }
 90
 091            var hasChanges = await ShowDocumentChanges(contextRepository, documentName, settings.CommunityContext, setti
 092            if (hasChanges)
 93            {
 094                changesFound++;
 95            }
 96        }
 97
 098        _console.WriteLine();
 099        if (changesFound == 0)
 100        {
 0101            _console.MarkupLine("[yellow]No changes found between versions[/]");
 102        }
 103        else
 104        {
 0105            _console.MarkupLine($"[green]Found changes in {changesFound} document(s)[/]");
 106        }
 0107    }
 108
 109    private static List<string> SelectDocuments(IReadOnlyList<string> allDocuments, int count, int? seed)
 110    {
 0111        if (allDocuments.Count <= count)
 112        {
 0113            return allDocuments.ToList();
 114        }
 115
 0116        var random = seed.HasValue ? new Random(seed.Value) : new Random();
 0117        return allDocuments.OrderBy(x => random.Next()).Take(count).ToList();
 118    }
 119
 120    private async Task<bool> ShowDocumentChanges(IContextRepository contextRepository, string documentName, string commu
 121    {
 122        try
 123        {
 124            // Get the latest document
 0125            var latestDocument = await contextRepository.GetLatestContextDocumentAsync(documentName, communityContext);
 126
 0127            if (latestDocument == null)
 128            {
 0129                if (verbose)
 130                {
 0131                    _console.MarkupLine($"[dim]Document '{documentName}' not found[/]");
 132                }
 0133                return false;
 134            }
 135
 0136            if (latestDocument.Version == 0)
 137            {
 0138                if (verbose)
 139                {
 0140                    _console.MarkupLine($"[dim]Document '{documentName}' has only one version (v{latestDocument.Version}
 141                }
 0142                return false;
 143            }
 144
 145            // Get the previous version
 0146            var previousDocument = await contextRepository.GetContextDocumentAsync(documentName, latestDocument.Version 
 147
 0148            if (previousDocument == null)
 149            {
 0150                if (verbose)
 151                {
 0152                    _console.MarkupLine($"[dim]Previous version of '{documentName}' not found[/]");
 153                }
 0154                return false;
 155            }
 156
 157            // Check if content actually differs
 0158            if (latestDocument.Content == previousDocument.Content)
 159            {
 0160                if (verbose)
 161                {
 0162                    _console.MarkupLine($"[dim]Document '{documentName}' has no content changes between v{previousDocume
 163                }
 0164                return false;
 165            }
 166
 167            // Show the diff
 0168            ShowDocumentDiff(documentName, previousDocument, latestDocument);
 0169            return true;
 170        }
 0171        catch (Exception ex)
 172        {
 0173            _console.MarkupLine($"[red]Error processing document '{documentName}': {ex.Message}[/]");
 0174            return false;
 175        }
 0176    }
 177
 178    private void ShowDocumentDiff(string documentName, ContextDocument oldDocument, ContextDocument newDocument)
 179    {
 0180        var panel = new Panel($"[bold]{documentName}[/]")
 0181            .Border(BoxBorder.Rounded)
 0182            .BorderColor(Color.Blue);
 0183        _console.Write(panel);
 184
 0185        _console.MarkupLine($"[dim]Changes from v{oldDocument.Version} ({oldDocument.CreatedAt:yyyy-MM-dd HH:mm}) to v{n
 0186        _console.WriteLine();
 187
 188        // Simple line-by-line diff
 0189        var oldLines = oldDocument.Content.Split('\n');
 0190        var newLines = newDocument.Content.Split('\n');
 191
 0192        var diff = GenerateSimpleDiff(oldLines, newLines);
 193
 0194        var table = new Table();
 0195        table.AddColumn("Line");
 0196        table.AddColumn("Change");
 0197        table.AddColumn("Content");
 0198        table.Border = TableBorder.Minimal;
 199
 0200        foreach (var diffLine in diff)
 201        {
 0202            var lineNumber = diffLine.LineNumber?.ToString() ?? "";
 0203            var changeType = diffLine.Type switch
 0204            {
 0205                DiffLineType.Added => "[green]+[/]",
 0206                DiffLineType.Removed => "[red]-[/]",
 0207                DiffLineType.Unchanged => " ",
 0208                _ => " "
 0209            };
 210
 0211            var content = diffLine.Type switch
 0212            {
 0213                DiffLineType.Added => $"[green]{EscapeMarkup(diffLine.Content)}[/]",
 0214                DiffLineType.Removed => $"[red]{EscapeMarkup(diffLine.Content)}[/]",
 0215                DiffLineType.Unchanged => $"[dim]{EscapeMarkup(diffLine.Content)}[/]",
 0216                _ => EscapeMarkup(diffLine.Content)
 0217            };
 218
 0219            table.AddRow(lineNumber, changeType, content);
 220        }
 221
 0222        _console.Write(table);
 0223        _console.WriteLine();
 0224    }
 225
 226    private static string EscapeMarkup(string text)
 227    {
 0228        return text.Replace("[", "[[").Replace("]", "]]");
 229    }
 230
 231    private static List<DiffLine> GenerateSimpleDiff(string[] oldLines, string[] newLines)
 232    {
 0233        var result = new List<DiffLine>();
 0234        var oldIndex = 0;
 0235        var newIndex = 0;
 236
 0237        while (oldIndex < oldLines.Length || newIndex < newLines.Length)
 238        {
 0239            if (oldIndex >= oldLines.Length)
 240            {
 241                // Only new lines remaining
 0242                result.Add(new DiffLine(DiffLineType.Added, newIndex + 1, newLines[newIndex]));
 0243                newIndex++;
 244            }
 0245            else if (newIndex >= newLines.Length)
 246            {
 247                // Only old lines remaining
 0248                result.Add(new DiffLine(DiffLineType.Removed, oldIndex + 1, oldLines[oldIndex]));
 0249                oldIndex++;
 250            }
 0251            else if (oldLines[oldIndex] == newLines[newIndex])
 252            {
 253                // Lines are the same
 0254                result.Add(new DiffLine(DiffLineType.Unchanged, newIndex + 1, newLines[newIndex]));
 0255                oldIndex++;
 0256                newIndex++;
 257            }
 258            else
 259            {
 260                // Lines differ - simple approach: mark old as removed, new as added
 0261                result.Add(new DiffLine(DiffLineType.Removed, oldIndex + 1, oldLines[oldIndex]));
 0262                result.Add(new DiffLine(DiffLineType.Added, newIndex + 1, newLines[newIndex]));
 0263                oldIndex++;
 0264                newIndex++;
 265            }
 266        }
 267
 0268        return result;
 269    }
 270}
 271
 272public enum DiffLineType
 273{
 274    Unchanged,
 275    Added,
 276    Removed
 277}
 278
 279public record DiffLine(DiffLineType Type, int? LineNumber, string Content);