< Summary

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

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
get_Type()100%210%
set_Type(...)100%210%
get_LineNumber()100%210%
set_LineNumber(...)100%210%
get_Content()100%210%
set_Content(...)100%210%
.ctor(...)100%210%

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
 18    public ContextChangesCommand(
 19        IAnsiConsole console,
 20        IFirebaseServiceFactory firebaseServiceFactory,
 21        ILogger<ContextChangesCommand> logger)
 22    {
 23        _console = console;
 24        _firebaseServiceFactory = firebaseServiceFactory;
 25        _logger = logger;
 26    }
 27
 28    public override async Task<int> ExecuteAsync(CommandContext context, ContextChangesSettings settings)
 29    {
 30
 31        try
 32        {
 33            _console.MarkupLine($"[green]Context changes command initialized for community context:[/] [yellow]{settings
 34
 35            if (settings.Verbose)
 36            {
 37                _console.MarkupLine("[dim]Verbose mode enabled[/]");
 38            }
 39
 40            // Execute the context changes workflow
 41            await ExecuteContextChanges(settings);
 42
 43            return 0;
 44        }
 45        catch (Exception ex)
 46        {
 47            _logger.LogError(ex, "Error executing context-changes command");
 48            _console.MarkupLine($"[red]Error:[/] {ex.Message}");
 49            return 1;
 50        }
 51    }
 52
 53    private async Task ExecuteContextChanges(ContextChangesSettings settings)
 54    {
 55        // Create context repository using factory (factory handles env var loading)
 56        var contextRepository = _firebaseServiceFactory.CreateContextRepository();
 57
 58        _console.MarkupLine($"[blue]Getting context document names for community:[/] [yellow]{settings.CommunityContext}
 59
 60        // Get all context document names
 61        var allDocumentNames = await contextRepository.GetContextDocumentNamesAsync(settings.CommunityContext);
 62
 63        if (!allDocumentNames.Any())
 64        {
 65            _console.MarkupLine("[yellow]No context documents found for this community[/]");
 66            return;
 67        }
 68
 69        if (settings.Verbose)
 70        {
 71            _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)
 75        var documentsToShow = SelectDocuments(allDocumentNames, settings.Count, settings.Seed);
 76
 77        if (settings.Verbose && documentsToShow.Count < allDocumentNames.Count)
 78        {
 79            _console.MarkupLine($"[dim]Showing {documentsToShow.Count} of {allDocumentNames.Count} documents{(settings.S
 80        }
 81
 82        var changesFound = 0;
 83
 84        foreach (var documentName in documentsToShow)
 85        {
 86            if (settings.Verbose)
 87            {
 88                _console.MarkupLine($"[dim]Checking document: {documentName}[/]");
 89            }
 90
 91            var hasChanges = await ShowDocumentChanges(contextRepository, documentName, settings.CommunityContext, setti
 92            if (hasChanges)
 93            {
 94                changesFound++;
 95            }
 96        }
 97
 98        _console.WriteLine();
 99        if (changesFound == 0)
 100        {
 101            _console.MarkupLine("[yellow]No changes found between versions[/]");
 102        }
 103        else
 104        {
 105            _console.MarkupLine($"[green]Found changes in {changesFound} document(s)[/]");
 106        }
 107    }
 108
 109    private static List<string> SelectDocuments(IReadOnlyList<string> allDocuments, int count, int? seed)
 110    {
 111        if (allDocuments.Count <= count)
 112        {
 113            return allDocuments.ToList();
 114        }
 115
 116        var random = seed.HasValue ? new Random(seed.Value) : new Random();
 117        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
 125            var latestDocument = await contextRepository.GetLatestContextDocumentAsync(documentName, communityContext);
 126
 127            if (latestDocument == null)
 128            {
 129                if (verbose)
 130                {
 131                    _console.MarkupLine($"[dim]Document '{documentName}' not found[/]");
 132                }
 133                return false;
 134            }
 135
 136            if (latestDocument.Version == 0)
 137            {
 138                if (verbose)
 139                {
 140                    _console.MarkupLine($"[dim]Document '{documentName}' has only one version (v{latestDocument.Version}
 141                }
 142                return false;
 143            }
 144
 145            // Get the previous version
 146            var previousDocument = await contextRepository.GetContextDocumentAsync(documentName, latestDocument.Version 
 147
 148            if (previousDocument == null)
 149            {
 150                if (verbose)
 151                {
 152                    _console.MarkupLine($"[dim]Previous version of '{documentName}' not found[/]");
 153                }
 154                return false;
 155            }
 156
 157            // Check if content actually differs
 158            if (latestDocument.Content == previousDocument.Content)
 159            {
 160                if (verbose)
 161                {
 162                    _console.MarkupLine($"[dim]Document '{documentName}' has no content changes between v{previousDocume
 163                }
 164                return false;
 165            }
 166
 167            // Show the diff
 168            ShowDocumentDiff(documentName, previousDocument, latestDocument);
 169            return true;
 170        }
 171        catch (Exception ex)
 172        {
 173            _console.MarkupLine($"[red]Error processing document '{documentName}': {ex.Message}[/]");
 174            return false;
 175        }
 176    }
 177
 178    private void ShowDocumentDiff(string documentName, ContextDocument oldDocument, ContextDocument newDocument)
 179    {
 180        var panel = new Panel($"[bold]{documentName}[/]")
 181            .Border(BoxBorder.Rounded)
 182            .BorderColor(Color.Blue);
 183        _console.Write(panel);
 184
 185        _console.MarkupLine($"[dim]Changes from v{oldDocument.Version} ({oldDocument.CreatedAt:yyyy-MM-dd HH:mm}) to v{n
 186        _console.WriteLine();
 187
 188        // Simple line-by-line diff
 189        var oldLines = oldDocument.Content.Split('\n');
 190        var newLines = newDocument.Content.Split('\n');
 191
 192        var diff = GenerateSimpleDiff(oldLines, newLines);
 193
 194        var table = new Table();
 195        table.AddColumn("Line");
 196        table.AddColumn("Change");
 197        table.AddColumn("Content");
 198        table.Border = TableBorder.Minimal;
 199
 200        foreach (var diffLine in diff)
 201        {
 202            var lineNumber = diffLine.LineNumber?.ToString() ?? "";
 203            var changeType = diffLine.Type switch
 204            {
 205                DiffLineType.Added => "[green]+[/]",
 206                DiffLineType.Removed => "[red]-[/]",
 207                DiffLineType.Unchanged => " ",
 208                _ => " "
 209            };
 210
 211            var content = diffLine.Type switch
 212            {
 213                DiffLineType.Added => $"[green]{EscapeMarkup(diffLine.Content)}[/]",
 214                DiffLineType.Removed => $"[red]{EscapeMarkup(diffLine.Content)}[/]",
 215                DiffLineType.Unchanged => $"[dim]{EscapeMarkup(diffLine.Content)}[/]",
 216                _ => EscapeMarkup(diffLine.Content)
 217            };
 218
 219            table.AddRow(lineNumber, changeType, content);
 220        }
 221
 222        _console.Write(table);
 223        _console.WriteLine();
 224    }
 225
 226    private static string EscapeMarkup(string text)
 227    {
 228        return text.Replace("[", "[[").Replace("]", "]]");
 229    }
 230
 231    private static List<DiffLine> GenerateSimpleDiff(string[] oldLines, string[] newLines)
 232    {
 233        var result = new List<DiffLine>();
 234        var oldIndex = 0;
 235        var newIndex = 0;
 236
 237        while (oldIndex < oldLines.Length || newIndex < newLines.Length)
 238        {
 239            if (oldIndex >= oldLines.Length)
 240            {
 241                // Only new lines remaining
 242                result.Add(new DiffLine(DiffLineType.Added, newIndex + 1, newLines[newIndex]));
 243                newIndex++;
 244            }
 245            else if (newIndex >= newLines.Length)
 246            {
 247                // Only old lines remaining
 248                result.Add(new DiffLine(DiffLineType.Removed, oldIndex + 1, oldLines[oldIndex]));
 249                oldIndex++;
 250            }
 251            else if (oldLines[oldIndex] == newLines[newIndex])
 252            {
 253                // Lines are the same
 254                result.Add(new DiffLine(DiffLineType.Unchanged, newIndex + 1, newLines[newIndex]));
 255                oldIndex++;
 256                newIndex++;
 257            }
 258            else
 259            {
 260                // Lines differ - simple approach: mark old as removed, new as added
 261                result.Add(new DiffLine(DiffLineType.Removed, oldIndex + 1, oldLines[oldIndex]));
 262                result.Add(new DiffLine(DiffLineType.Added, newIndex + 1, newLines[newIndex]));
 263                oldIndex++;
 264                newIndex++;
 265            }
 266        }
 267
 268        return result;
 269    }
 270}
 271
 272public enum DiffLineType
 273{
 274    Unchanged,
 275    Added,
 276    Removed
 277}
 278
 0279public record DiffLine(DiffLineType Type, int? LineNumber, string Content);