From dcb7ba61c8723454e2c0aa0a170318ca354b0c34 Mon Sep 17 00:00:00 2001 From: mikemikimike <13286568797@163.com> Date: Wed, 4 Nov 2026 02:46:44 +0800 Subject: [PATCH 1/3] fix(doctor): read active daemon logs on Windows --- .../MemoryCurationLlmDoctorCheckTests.cs | 42 +++++++++++++++++++ .../Doctor/MemoryCurationLlmDoctorCheck.cs | 7 +++- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs diff --git a/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs b/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs new file mode 100644 index 0000000000..6dc122fd2d --- /dev/null +++ b/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs @@ -0,0 +1,42 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2026 - 2026 Petabridge, LLC +// +// ----------------------------------------------------------------------- +using Microsoft.Extensions.Time.Testing; +using Netclaw.Cli.Doctor; +using Netclaw.Configuration; +using Netclaw.Tests.Utilities; +using Xunit; + +namespace Netclaw.Cli.Tests.Doctor; + +public sealed class MemoryCurationLlmDoctorCheckTests : IDisposable +{ + private readonly DisposableTempDir _temp = new(); + + public void Dispose() => _temp.Dispose(); + + [Fact] + public async Task Reads_and_classifies_log_while_daemon_keeps_it_open_for_append() + { + var paths = new NetclawPaths(_temp.Path); + paths.EnsureDirectoriesExist(); + + var logPath = Path.Combine(paths.LogsDirectory, "daemon-2026-05-20.log"); + using var writer = new StreamWriter(logPath, append: true); + writer.WriteLine("curation_llm_decision"); + writer.WriteLine("curation_llm_timeout"); + writer.Flush(); + + var time = new FakeTimeProvider( + new DateTimeOffset(2026, 5, 20, 12, 0, 0, TimeSpan.Zero)); + var result = await new MemoryCurationLlmDoctorCheck(paths, time) + .RunAsync(TestContext.Current.CancellationToken); + + Assert.Equal("Memory Curation LLM", result.Name); + Assert.Equal(DoctorSeverity.Warning, result.Severity); + Assert.Contains("50%", result.Message); + Assert.Contains("(1/2)", result.Message); + } +} diff --git a/src/Netclaw.Cli/Doctor/MemoryCurationLlmDoctorCheck.cs b/src/Netclaw.Cli/Doctor/MemoryCurationLlmDoctorCheck.cs index 0237fb14ea..d5b7920b61 100644 --- a/src/Netclaw.Cli/Doctor/MemoryCurationLlmDoctorCheck.cs +++ b/src/Netclaw.Cli/Doctor/MemoryCurationLlmDoctorCheck.cs @@ -60,7 +60,12 @@ private async Task RunCoreAsync(CancellationToken cancellatio continue; cancellationToken.ThrowIfCancellationRequested(); - using var reader = new StreamReader(file); + using var stream = new FileStream( + file, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite | FileShare.Delete); + using var reader = new StreamReader(stream); while (await reader.ReadLineAsync(cancellationToken) is { } line) { // Failure markers first: none of them contains the success From e7d075b7a8e1d9cbadf67b11970dfadedc56a544 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Thu, 5 Nov 2026 20:38:07 +0000 Subject: [PATCH 2/3] test(doctor): do not depend on the culture's percent format The message formats the failure rate with the current culture. CI wrote "50 %" and the test expected "50%", so the test failed on Ubuntu. The test now checks the severity and the counts "(1/2)", which prove that doctor read and classified the open log. --- .../Doctor/MemoryCurationLlmDoctorCheckTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs b/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs index 6dc122fd2d..8a4376ce6f 100644 --- a/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs +++ b/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs @@ -36,7 +36,8 @@ public async Task Reads_and_classifies_log_while_daemon_keeps_it_open_for_append Assert.Equal("Memory Curation LLM", result.Name); Assert.Equal(DoctorSeverity.Warning, result.Severity); - Assert.Contains("50%", result.Message); + // The percent text follows the current culture ("50%" or "50 %"), so + // check the counts, which do not change with the culture. Assert.Contains("(1/2)", result.Message); } } From 3236191621024d8d5d41cf88f96ee3c6e3a72643 Mon Sep 17 00:00:00 2001 From: mikemikimike <13286568797@163.com> Date: Fri, 6 Nov 2026 12:59:26 +0800 Subject: [PATCH 3/3] test(doctor): allow shared log writer in regression test --- .../Doctor/MemoryCurationLlmDoctorCheckTests.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs b/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs index 8a4376ce6f..220d3b8a8e 100644 --- a/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs +++ b/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs @@ -24,7 +24,12 @@ public async Task Reads_and_classifies_log_while_daemon_keeps_it_open_for_append paths.EnsureDirectoriesExist(); var logPath = Path.Combine(paths.LogsDirectory, "daemon-2026-05-20.log"); - using var writer = new StreamWriter(logPath, append: true); + using var writerStream = new FileStream( + logPath, + FileMode.Append, + FileAccess.Write, + FileShare.ReadWrite | FileShare.Delete); + using var writer = new StreamWriter(writerStream); writer.WriteLine("curation_llm_decision"); writer.WriteLine("curation_llm_timeout"); writer.Flush();