using DiffPlex.DiffBuilder; using DiffPlex.DiffBuilder.Model; using MeetingAssistant.MeetingNotes; using System.Text; namespace MeetingAssistant.Summary; public sealed class SummaryAgentWriteAudit { private readonly MeetingSessionArtifacts artifacts; private readonly List changes = []; public SummaryAgentWriteAudit(MeetingSessionArtifacts artifacts) { this.artifacts = artifacts; } public async Task CaptureFileWriteAsync( string path, Func writeOperation, CancellationToken cancellationToken = default) { var fullPath = Path.GetFullPath(path); var before = File.Exists(fullPath) ? await File.ReadAllTextAsync(fullPath, cancellationToken) : ""; await writeOperation(); var after = File.Exists(fullPath) ? await File.ReadAllTextAsync(fullPath, cancellationToken) : ""; Capture(fullPath, before, after, cancellationToken); } public void Capture( string path, string before, string after, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); var fullPath = Path.GetFullPath(path); if (IsOwnedArtifact(fullPath) || string.Equals(before, after, StringComparison.Ordinal)) { return; } var diffLines = CreateDiffLines(before, after); if (diffLines.Count == 0) { return; } changes.Add(new FileChange(fullPath, diffLines)); } public async Task AppendToAssistantContextAsync(CancellationToken cancellationToken) { if (changes.Count == 0) { return; } if (!File.Exists(artifacts.AssistantContextPath)) { return; } var existing = await File.ReadAllTextAsync(artifacts.AssistantContextPath, cancellationToken); var builder = new StringBuilder(); if (!string.IsNullOrEmpty(existing) && !existing.EndsWith(Environment.NewLine, StringComparison.Ordinal)) { builder.AppendLine(); } builder.AppendLine(); builder.AppendLine("## Summary Agent External File Changes"); builder.AppendLine(); foreach (var change in changes) { builder.AppendLine($"### {change.Path}"); builder.AppendLine(); builder.AppendLine("```diff"); foreach (var line in change.DiffLines) { builder.AppendLine(line); } builder.AppendLine("```"); builder.AppendLine(); } await File.AppendAllTextAsync(artifacts.AssistantContextPath, builder.ToString(), cancellationToken); } private bool IsOwnedArtifact(string fullPath) { return IsSamePath(fullPath, artifacts.SummaryPath) || IsSamePath(fullPath, artifacts.AssistantContextPath); } private static bool IsSamePath(string first, string second) { return string.Equals( Path.GetFullPath(first), Path.GetFullPath(second), StringComparison.OrdinalIgnoreCase); } private static List CreateDiffLines(string before, string after) { var model = InlineDiffBuilder.Diff(before, after); return model.Lines .Where(line => line.Type is ChangeType.Deleted or ChangeType.Inserted) .Select(line => $"{(line.Type == ChangeType.Inserted ? "+" : "-")} {line.Text}") .ToList(); } private sealed record FileChange(string Path, IReadOnlyList DiffLines); }