From 2a25367757f90131810e5b17f4557cd01d4e271f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 17:10:48 +0000 Subject: [PATCH] fix(gendoc): log skipped assemblies in FailureBehavior.Continue HandleFailure returned without any trace in Continue mode (the CLI default outside --strict), so target-not-found, worker-produced-no-output, unreadable-output, TargetPath resolution failures and missing assemblies dropped assemblies silently. Emit a Warning (with the exception detail when present) before continuing so every skipped assembly leaves a diagnosable trace. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MUSHBAhJ5R2PFp9HismMZs --- ...olutionErrorDocumentationGeneratorTests.cs | 28 +++++++++++++++++++ .../SolutionErrorDocumentationGenerator.cs | 10 ++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/FirstClassErrors.GenDoc.UnitTests/SolutionErrorDocumentationGeneratorTests.cs b/FirstClassErrors.GenDoc.UnitTests/SolutionErrorDocumentationGeneratorTests.cs index c3130c4..4350fec 100644 --- a/FirstClassErrors.GenDoc.UnitTests/SolutionErrorDocumentationGeneratorTests.cs +++ b/FirstClassErrors.GenDoc.UnitTests/SolutionErrorDocumentationGeneratorTests.cs @@ -73,6 +73,34 @@ public void AMissingAssemblyIsSkippedWhenContinuing() { Check.That(result).IsEmpty(); } + [Fact(DisplayName = "A skipped assembly is logged as a warning (never silent) when the failure behavior is Continue.")] + public void ASkippedAssemblyIsLoggedWhenContinuing() { + // Setup + RecordingGenerationLogger logger = new(); + SolutionGenerationOptions options = new() { + FailureBehavior = FailureBehavior.Continue, + Logger = logger + }; + + // Exercise + SolutionErrorDocumentationGenerator.GetErrorDocumentationFromAssemblies(new[] { "this-assembly-does-not-exist.dll" }, options); + + // Verify: the skipped assembly leaves a warning trace mentioning the missing file. + Check.That(logger.Warnings).HasSize(1); + Check.That(logger.Warnings[0]).Contains("this-assembly-does-not-exist.dll"); + } + + private sealed class RecordingGenerationLogger : IGenerationLogger { + + public List Warnings { get; } = new(); + + public void Info(string message) { } + public void Warning(string message) { Warnings.Add(message); } + public void Error(string message) { } + public void Debug(string message) { } + + } + [Fact(DisplayName = "A missing assembly aborts when the failure behavior is Stop.")] public void AMissingAssemblyAbortsWhenStopping() { // Setup diff --git a/FirstClassErrors.GenDoc/SolutionErrorDocumentationGenerator.cs b/FirstClassErrors.GenDoc/SolutionErrorDocumentationGenerator.cs index 99f9b82..e64b87b 100644 --- a/FirstClassErrors.GenDoc/SolutionErrorDocumentationGenerator.cs +++ b/FirstClassErrors.GenDoc/SolutionErrorDocumentationGenerator.cs @@ -350,7 +350,15 @@ private static ProcessResult RunProcess(string fileName, string arguments, strin } private static void HandleFailure(SolutionGenerationOptions options, string message, Exception? ex = null) { - if (options.FailureBehavior == FailureBehavior.Continue) { return; } + if (options.FailureBehavior == FailureBehavior.Continue) { + // In Continue mode the failure is swallowed so the generation proceeds with the remaining assemblies, but it + // must never be silent: log it as a warning (with the exception detail when present) so the skipped assembly + // leaves a trace the caller can diagnose. + options.Logger.Warning(ex is not null ? $"{message} {ex}" : message); + + return; + } + if (ex is not null) { throw new SolutionDocumentationGenerationException(message, ex); } throw new SolutionDocumentationGenerationException(message);