< 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
98%
Covered lines: 121
Uncovered lines: 2
Coverable lines: 123
Total lines: 279
Line coverage: 98.3%
Branch coverage
93%
Covered branches: 60
Total branches: 64
Branch coverage: 93.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ExecuteAsync()100%22100%
ExecuteContextChanges()100%1818100%
SelectDocuments(...)100%44100%
ShowDocumentChanges()100%1616100%
ShowDocumentDiff(...)71.43%141494.12%
EscapeMarkup(...)100%11100%
GenerateSimpleDiff(...)100%1010100%

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
 118    public ContextChangesCommand(
 119        IAnsiConsole console,
 120        IFirebaseServiceFactory firebaseServiceFactory,
 121        ILogger<ContextChangesCommand> logger)
 22    {
 123        _console = console;
 124        _firebaseServiceFactory = firebaseServiceFactory;
 125        _logger = logger;
 126    }
 27
 28    public override async Task<int> ExecuteAsync(CommandContext context, ContextChangesSettings settings)
 29    {
 30
 31        try
 32        {
 133            _console.MarkupLine($"[green]Context changes command initialized for community context:[/] [yellow]{settings
 34
 135            if (settings.Verbose)
 36            {
 137                _console.MarkupLine("[dim]Verbose mode enabled[/]");
 38            }
 39
 40            // Execute the context changes workflow
 141            await ExecuteContextChanges(settings);
 42
 143            return 0;
 44        }
 145        catch (Exception ex)
 46        {
 147            _logger.LogError(ex, "Error executing context-changes command");
 148            _console.MarkupLine($"[red]Error:[/] {ex.Message}");
 149            return 1;
 50        }
 151    }
 52
 53    private async Task ExecuteContextChanges(ContextChangesSettings settings)
 54    {
 55        // Create context repository using factory (factory handles env var loading)
 156        var contextRepository = _firebaseServiceFactory.CreateContextRepository();
 57
 158        _console.MarkupLine($"[blue]Getting context document names for community:[/] [yellow]{settings.CommunityContext}
 59
 60        // Get all context document names
 161        var allDocumentNames = await contextRepository.GetContextDocumentNamesAsync(settings.CommunityContext);
 62
 163        if (!allDocumentNames.Any())
 64        {
 165            _console.MarkupLine("[yellow]No context documents found for this community[/]");
 166            return;
 67        }
 68
 169        if (settings.Verbose)
 70        {
 171            _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)
 175        var documentsToShow = SelectDocuments(allDocumentNames, settings.Count, settings.Seed);
 76
 177        if (settings.Verbose && documentsToShow.Count < allDocumentNames.Count)
 78        {
 179            _console.MarkupLine($"[dim]Showing {documentsToShow.Count} of {allDocumentNames.Count} documents{(settings.S
 80        }
 81
 182        var changesFound = 0;
 83
 184        foreach (var documentName in documentsToShow)
 85        {
 186            if (settings.Verbose)
 87            {
 188                _console.MarkupLine($"[dim]Checking document: {documentName}[/]");
 89            }
 90
 191            var hasChanges = await ShowDocumentChanges(contextRepository, documentName, settings.CommunityContext, setti
 192            if (hasChanges)
 93            {
 194                changesFound++;
 95            }
 96        }
 97
 198        _console.WriteLine();
 199        if (changesFound == 0)
 100        {
 1101            _console.MarkupLine("[yellow]No changes found between versions[/]");
 102        }
 103        else
 104        {
 1105            _console.MarkupLine($"[green]Found changes in {changesFound} document(s)[/]");
 106        }
 1107    }
 108
 109    private static List<string> SelectDocuments(IReadOnlyList<string> allDocuments, int count, int? seed)
 110    {
 1111        if (allDocuments.Count <= count)
 112        {
 1113            return allDocuments.ToList();
 114        }
 115
 1116        var random = seed.HasValue ? new Random(seed.Value) : new Random();
 1117        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
 1125            var latestDocument = await contextRepository.GetLatestContextDocumentAsync(documentName, communityContext);
 126
 1127            if (latestDocument == null)
 128            {
 1129                if (verbose)
 130                {
 1131                    _console.MarkupLine($"[dim]Document '{documentName}' not found[/]");
 132                }
 1133                return false;
 134            }
 135
 1136            if (latestDocument.Version == 0)
 137            {
 1138                if (verbose)
 139                {
 1140                    _console.MarkupLine($"[dim]Document '{documentName}' has only one version (v{latestDocument.Version}
 141                }
 1142                return false;
 143            }
 144
 145            // Get the previous version
 1146            var previousDocument = await contextRepository.GetContextDocumentAsync(documentName, latestDocument.Version 
 147
 1148            if (previousDocument == null)
 149            {
 1150                if (verbose)
 151                {
 1152                    _console.MarkupLine($"[dim]Previous version of '{documentName}' not found[/]");
 153                }
 1154                return false;
 155            }
 156
 157            // Check if content actually differs
 1158            if (latestDocument.Content == previousDocument.Content)
 159            {
 1160                if (verbose)
 161                {
 1162                    _console.MarkupLine($"[dim]Document '{documentName}' has no content changes between v{previousDocume
 163                }
 1164                return false;
 165            }
 166
 167            // Show the diff
 1168            ShowDocumentDiff(documentName, previousDocument, latestDocument);
 1169            return true;
 170        }
 1171        catch (Exception ex)
 172        {
 1173            _console.MarkupLine($"[red]Error processing document '{documentName}': {ex.Message}[/]");
 1174            return false;
 175        }
 1176    }
 177
 178    private void ShowDocumentDiff(string documentName, ContextDocument oldDocument, ContextDocument newDocument)
 179    {
 1180        var panel = new Panel($"[bold]{documentName}[/]")
 1181            .Border(BoxBorder.Rounded)
 1182            .BorderColor(Color.Blue);
 1183        _console.Write(panel);
 184
 1185        _console.MarkupLine($"[dim]Changes from v{oldDocument.Version} ({oldDocument.CreatedAt:yyyy-MM-dd HH:mm}) to v{n
 1186        _console.WriteLine();
 187
 188        // Simple line-by-line diff
 1189        var oldLines = oldDocument.Content.Split('\n');
 1190        var newLines = newDocument.Content.Split('\n');
 191
 1192        var diff = GenerateSimpleDiff(oldLines, newLines);
 193
 1194        var table = new Table();
 1195        table.AddColumn("Line");
 1196        table.AddColumn("Change");
 1197        table.AddColumn("Content");
 1198        table.Border = TableBorder.Minimal;
 199
 1200        foreach (var diffLine in diff)
 201        {
 1202            var lineNumber = diffLine.LineNumber?.ToString() ?? "";
 1203            var changeType = diffLine.Type switch
 1204            {
 1205                DiffLineType.Added => "[green]+[/]",
 1206                DiffLineType.Removed => "[red]-[/]",
 1207                DiffLineType.Unchanged => " ",
 0208                _ => " "
 1209            };
 210
 1211            var content = diffLine.Type switch
 1212            {
 1213                DiffLineType.Added => $"[green]{EscapeMarkup(diffLine.Content)}[/]",
 1214                DiffLineType.Removed => $"[red]{EscapeMarkup(diffLine.Content)}[/]",
 1215                DiffLineType.Unchanged => $"[dim]{EscapeMarkup(diffLine.Content)}[/]",
 0216                _ => EscapeMarkup(diffLine.Content)
 1217            };
 218
 1219            table.AddRow(lineNumber, changeType, content);
 220        }
 221
 1222        _console.Write(table);
 1223        _console.WriteLine();
 1224    }
 225
 226    private static string EscapeMarkup(string text)
 227    {
 1228        return text.Replace("[", "[[").Replace("]", "]]");
 229    }
 230
 231    private static List<DiffLine> GenerateSimpleDiff(string[] oldLines, string[] newLines)
 232    {
 1233        var result = new List<DiffLine>();
 1234        var oldIndex = 0;
 1235        var newIndex = 0;
 236
 1237        while (oldIndex < oldLines.Length || newIndex < newLines.Length)
 238        {
 1239            if (oldIndex >= oldLines.Length)
 240            {
 241                // Only new lines remaining
 1242                result.Add(new DiffLine(DiffLineType.Added, newIndex + 1, newLines[newIndex]));
 1243                newIndex++;
 244            }
 1245            else if (newIndex >= newLines.Length)
 246            {
 247                // Only old lines remaining
 1248                result.Add(new DiffLine(DiffLineType.Removed, oldIndex + 1, oldLines[oldIndex]));
 1249                oldIndex++;
 250            }
 1251            else if (oldLines[oldIndex] == newLines[newIndex])
 252            {
 253                // Lines are the same
 1254                result.Add(new DiffLine(DiffLineType.Unchanged, newIndex + 1, newLines[newIndex]));
 1255                oldIndex++;
 1256                newIndex++;
 257            }
 258            else
 259            {
 260                // Lines differ - simple approach: mark old as removed, new as added
 1261                result.Add(new DiffLine(DiffLineType.Removed, oldIndex + 1, oldLines[oldIndex]));
 1262                result.Add(new DiffLine(DiffLineType.Added, newIndex + 1, newLines[newIndex]));
 1263                oldIndex++;
 1264                newIndex++;
 265            }
 266        }
 267
 1268        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);