From 789731a86380ae19d0c11781f47a6a21a0c15479 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 23:01:24 +0000 Subject: [PATCH 1/2] Initial plan From 87c9bcec28acd8bb01d65dd8baf8fe91b14472f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 23:06:53 +0000 Subject: [PATCH 2/2] Address review comments: thread-safe counters, remove redundant awaits, Task.WhenAll, remove duplicate ping, fix CountSections Co-authored-by: jongalloway <68539+jongalloway@users.noreply.github.com> --- src/NewsletterGenerator/Program.cs | 36 ++++++++++--------- .../Services/CacheService.cs | 20 ++++++----- .../Services/NewsletterService.cs | 17 ++++----- 3 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/NewsletterGenerator/Program.cs b/src/NewsletterGenerator/Program.cs index 3f7be32..a46ee21 100644 --- a/src/NewsletterGenerator/Program.cs +++ b/src/NewsletterGenerator/Program.cs @@ -151,20 +151,6 @@ public override async Task ExecuteAsync(CommandContext context, CommandSett var models = await NewsletterApp.PrintCopilotStartupStatusAsync(); var healthy = models != null && models.Count > 0; - // Lightweight connectivity ping - try - { - await using var client = new CopilotClient(); - await client.StartAsync(); - var ping = await client.PingAsync(); - AnsiConsole.MarkupLine($"[green]✓[/] Ping: OK"); - } - catch (Exception ex) - { - AnsiConsole.MarkupLine($"[yellow]⚠[/] Ping failed: {Markup.Escape(ex.Message)}"); - healthy = false; - } - if (healthy) AnsiConsole.MarkupLine("[green]Environment checks passed.[/]"); else @@ -1385,6 +1371,8 @@ await AnsiConsole.Progress().AutoClear(false).HideCompleted(false).StartAsync(as metrics, "Generate: Project updates"); + await Task.WhenAll(newsSectionTask, releaseSectionTask); + newsSection = await newsSectionTask; releaseSection = await releaseSectionTask; @@ -1466,10 +1454,26 @@ public static string FindRepoRoot(string startDir) private static int CountSections(string content) { - var headingCount = content.Split('\n') + var lines = content.Split('\n'); + + var headingCount = lines .Count(line => line.StartsWith("## ", StringComparison.Ordinal) || line.StartsWith("### ", StringComparison.Ordinal)); - return headingCount + 1; // Include the welcome section. + // Treat any non-empty content before the first heading as a "welcome" section. + var hasWelcomeSection = false; + foreach (var line in lines) + { + if (line.StartsWith("## ", StringComparison.Ordinal) || line.StartsWith("### ", StringComparison.Ordinal)) + break; + + if (!string.IsNullOrWhiteSpace(line)) + { + hasWelcomeSection = true; + break; + } + } + + return headingCount + (hasWelcomeSection ? 1 : 0); } private static async Task RunTrackedTaskAsync( diff --git a/src/NewsletterGenerator/Services/CacheService.cs b/src/NewsletterGenerator/Services/CacheService.cs index d60b4d1..88af44a 100644 --- a/src/NewsletterGenerator/Services/CacheService.cs +++ b/src/NewsletterGenerator/Services/CacheService.cs @@ -11,9 +11,13 @@ public class CacheService(ILogger logger, string? cacheDirectory = private readonly bool _forceRefresh = forceRefresh; private readonly ConcurrentDictionary _sectionMetrics = new(StringComparer.OrdinalIgnoreCase); - public int CacheHits { get; private set; } - public int CacheMisses { get; private set; } - public int CacheSkips { get; private set; } + private int _cacheHits; + private int _cacheMisses; + private int _cacheSkips; + + public int CacheHits => Volatile.Read(ref _cacheHits); + public int CacheMisses => Volatile.Read(ref _cacheMisses); + public int CacheSkips => Volatile.Read(ref _cacheSkips); public IReadOnlyList GetSectionMetrics() => _sectionMetrics.Values @@ -39,7 +43,7 @@ public static string GetContentHash(string content) { if (_forceRefresh) { - CacheSkips++; + Interlocked.Increment(ref _cacheSkips); RecordReadOutcome(cacheKey, "skip"); ServiceLogMessages.CacheSkipForceRefresh(logger, cacheKey); return null; @@ -50,7 +54,7 @@ public static string GetContentHash(string content) if (!File.Exists(cacheFile)) { - CacheMisses++; + Interlocked.Increment(ref _cacheMisses); RecordReadOutcome(cacheKey, "miss"); ServiceLogMessages.CacheMissNoFile(logger, cacheKey); return null; @@ -63,19 +67,19 @@ public static string GetContentHash(string content) if (cached?.SourceHash == sourceHash) { - CacheHits++; + Interlocked.Increment(ref _cacheHits); RecordReadOutcome(cacheKey, "hit", cached.Content.Length); ServiceLogMessages.CacheHit(logger, cacheKey, sourceHash[..12], cached.Content.Length); return cached.Content; } - CacheMisses++; + Interlocked.Increment(ref _cacheMisses); RecordReadOutcome(cacheKey, "mismatch"); ServiceLogMessages.CacheMissHashMismatch(logger, cacheKey, sourceHash[..12], cached?.SourceHash?[..12]); } catch (Exception ex) { - CacheMisses++; + Interlocked.Increment(ref _cacheMisses); RecordReadOutcome(cacheKey, "error"); ServiceLogMessages.CacheReadFailed(logger, ex, cacheKey); } diff --git a/src/NewsletterGenerator/Services/NewsletterService.cs b/src/NewsletterGenerator/Services/NewsletterService.cs index 160077d..d2bdc66 100644 --- a/src/NewsletterGenerator/Services/NewsletterService.cs +++ b/src/NewsletterGenerator/Services/NewsletterService.cs @@ -9,26 +9,23 @@ public class NewsletterService(ILogger logger) { private SessionHooks CreateSessionHooks() => new() { - OnErrorOccurred = async (input, invocation) => + OnErrorOccurred = (input, invocation) => { logger.LogWarning("Session error in {Context}: {Error}", input.ErrorContext, input.Error); - await Task.CompletedTask; - return new ErrorOccurredHookOutput + return Task.FromResult(new ErrorOccurredHookOutput { ErrorHandling = "retry" - }; + }); }, - OnSessionStart = async (input, invocation) => + OnSessionStart = (input, invocation) => { logger.LogDebug("Session started (source={Source})", input.Source); - await Task.CompletedTask; - return new SessionStartHookOutput(); + return Task.FromResult(new SessionStartHookOutput()); }, - OnSessionEnd = async (input, invocation) => + OnSessionEnd = (input, invocation) => { logger.LogDebug("Session ended (reason={Reason})", input.Reason); - await Task.CompletedTask; - return null; + return Task.FromResult(null); } };