diff --git a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs index c889baff..b3717a56 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs @@ -95,7 +95,7 @@ public class ArchitectureLifecycleBehaviorTests /// 验证用户初始化失败时,等待 Ready 的任务会失败并进入 FailedInitialization 阶段。 /// [Test] - public async Task InitializeAsync_When_OnInitialize_Throws_Should_Mark_FailedInitialization() + public void InitializeAsync_When_OnInitialize_Throws_Should_Mark_FailedInitialization() { var architecture = new PhaseTrackingArchitecture(() => throw new InvalidOperationException("boom")); diff --git a/GFramework.Core.Tests/Concurrency/AsyncKeyLockManagerTests.cs b/GFramework.Core.Tests/Concurrency/AsyncKeyLockManagerTests.cs index 9e43e880..4709a5bd 100644 --- a/GFramework.Core.Tests/Concurrency/AsyncKeyLockManagerTests.cs +++ b/GFramework.Core.Tests/Concurrency/AsyncKeyLockManagerTests.cs @@ -123,7 +123,7 @@ public sealed class AsyncKeyLockManagerTests } // Assert - Assert.DoesNotThrowAsync(() => Task.WhenAll(tasks)); + await Task.WhenAll(tasks).ConfigureAwait(false); } [Test] @@ -260,8 +260,8 @@ public sealed class AsyncKeyLockManagerTests await cts.CancelAsync().ConfigureAwait(false); // Assert - Assert.CatchAsync(async () => - await manager.AcquireLockAsync("test-key", cts.Token).ConfigureAwait(false)); + Assert.CatchAsync(() => + manager.AcquireLockAsync("test-key", cts.Token).AsTask()); } [Test] @@ -302,7 +302,7 @@ public sealed class AsyncKeyLockManagerTests } // Assert - Assert.DoesNotThrowAsync(() => Task.WhenAll(tasks)); + await Task.WhenAll(tasks).ConfigureAwait(false); } [Test] diff --git a/GFramework.Core.Tests/Extensions/AsyncExtensionsTests.cs b/GFramework.Core.Tests/Extensions/AsyncExtensionsTests.cs index ed678431..0b96d022 100644 --- a/GFramework.Core.Tests/Extensions/AsyncExtensionsTests.cs +++ b/GFramework.Core.Tests/Extensions/AsyncExtensionsTests.cs @@ -123,7 +123,7 @@ public class AsyncExtensionsTests // Act & Assert Assert.ThrowsAsync(() => AsyncExtensions.WithTimeoutAsync( - ct => Task.Delay(TimeSpan.FromSeconds(2), ct).ConfigureAwait(false), + ct => Task.Delay(TimeSpan.FromSeconds(2), ct), TimeSpan.FromMilliseconds(100))); } diff --git a/GFramework.Core.Tests/State/StateMachineSystemTests.cs b/GFramework.Core.Tests/State/StateMachineSystemTests.cs index 58d818e3..63cb5b22 100644 --- a/GFramework.Core.Tests/State/StateMachineSystemTests.cs +++ b/GFramework.Core.Tests/State/StateMachineSystemTests.cs @@ -136,7 +136,7 @@ public class StateMachineSystemTests /// 测试DestroyAsync方法不抛出异常 /// [Test] - public async Task DestroyAsync_Should_Not_Throw_Exception() + public void DestroyAsync_Should_Not_Throw_Exception() { Assert.That(() => _stateMachine!.DestroyAsync(), Throws.Nothing); } diff --git a/GFramework.Core.Tests/Tests/AsyncArchitectureTests.cs b/GFramework.Core.Tests/Tests/AsyncArchitectureTests.cs index 93895979..b95a70e2 100644 --- a/GFramework.Core.Tests/Tests/AsyncArchitectureTests.cs +++ b/GFramework.Core.Tests/Tests/AsyncArchitectureTests.cs @@ -89,7 +89,7 @@ public class AsyncArchitectureTests : ArchitectureTestsBase /// 异步任务 [Test] - public async Task Architecture_Should_Stop_Initialization_When_Model_Init_Fails() + public void Architecture_Should_Stop_Initialization_When_Model_Init_Fails() { Architecture!.AddPostRegistrationHook(a => { a.RegisterModel(new FailingModel()); }); @@ -134,7 +134,7 @@ public class AsyncArchitectureTests : ArchitectureTestsBase /// 异步任务 [Test] - public async Task InitializeAsync_Should_Handle_Exception_Correctly() + public void InitializeAsync_Should_Handle_Exception_Correctly() { Architecture!.AddPostRegistrationHook(a => a.RegisterModel(new FailingModel()) diff --git a/GFramework.Core/Extensions/NumericExtensions.cs b/GFramework.Core/Extensions/NumericExtensions.cs index f27503b1..c00f0044 100644 --- a/GFramework.Core/Extensions/NumericExtensions.cs +++ b/GFramework.Core/Extensions/NumericExtensions.cs @@ -26,19 +26,29 @@ public static class NumericExtensions public static bool Between(this T value, T min, T max, bool inclusive = true) where T : IComparable { if (value is null) + { throw new ArgumentNullException(nameof(value)); + } if (min is null) + { throw new ArgumentNullException(nameof(min)); + } if (max is null) + { throw new ArgumentNullException(nameof(max)); + } if (min.CompareTo(max) > 0) + { throw new ArgumentException($"最小值 ({min}) 不能大于最大值 ({max})", nameof(min)); + } if (inclusive) + { return value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0; + } return value.CompareTo(min) > 0 && value.CompareTo(max) < 0; } @@ -76,7 +86,9 @@ public static class NumericExtensions public static float InverseLerp(this float value, float from, float to) { if (Math.Abs(to - from) < float.Epsilon) + { throw new DivideByZeroException("起始值和目标值不能相等"); + } return (value - from) / (to - from); } diff --git a/GFramework.Core/Extensions/StringExtensions.cs b/GFramework.Core/Extensions/StringExtensions.cs index f2403aec..616c3a83 100644 --- a/GFramework.Core/Extensions/StringExtensions.cs +++ b/GFramework.Core/Extensions/StringExtensions.cs @@ -39,17 +39,25 @@ public static class StringExtensions public static string Truncate(this string str, int maxLength, string suffix = "...") { if (str is null) + { throw new ArgumentNullException(nameof(str)); + } if (suffix is null) + { throw new ArgumentNullException(nameof(suffix)); + } if (maxLength < suffix.Length) + { throw new ArgumentOutOfRangeException(nameof(maxLength), $"最大长度必须至少为后缀长度 ({suffix.Length})"); + } if (str.Length <= maxLength) + { return str; + } return string.Concat(str.AsSpan(0, maxLength - suffix.Length), suffix); } @@ -70,10 +78,14 @@ public static class StringExtensions public static string Join(this IEnumerable values, string separator) { if (values is null) + { throw new ArgumentNullException(nameof(values)); + } if (separator is null) + { throw new ArgumentNullException(nameof(separator)); + } return string.Join(separator, values); } diff --git a/GFramework.Core/StateManagement/StoreBuilder.cs b/GFramework.Core/StateManagement/StoreBuilder.cs index fca6bc34..bf2565ea 100644 --- a/GFramework.Core/StateManagement/StoreBuilder.cs +++ b/GFramework.Core/StateManagement/StoreBuilder.cs @@ -40,7 +40,9 @@ public sealed class StoreBuilder : IStoreBuilder public IStoreBuilder UseMiddleware(IStoreMiddleware middleware) { if (middleware is null) + { throw new ArgumentNullException(nameof(middleware)); + } _configurators.Add(store => store.UseMiddleware(middleware)); return this; @@ -111,7 +113,9 @@ public sealed class StoreBuilder : IStoreBuilder public IStoreBuilder AddReducer(Func reducer) { if (reducer is null) + { throw new ArgumentNullException(nameof(reducer)); + } _configurators.Add(store => store.RegisterReducer(reducer)); return this; @@ -126,7 +130,9 @@ public sealed class StoreBuilder : IStoreBuilder public IStoreBuilder AddReducer(IReducer reducer) { if (reducer is null) + { throw new ArgumentNullException(nameof(reducer)); + } _configurators.Add(store => store.RegisterReducer(reducer)); return this; diff --git a/GFramework.Core/StateManagement/StoreSelection.cs b/GFramework.Core/StateManagement/StoreSelection.cs index 9f0bbe6f..867831b0 100644 --- a/GFramework.Core/StateManagement/StoreSelection.cs +++ b/GFramework.Core/StateManagement/StoreSelection.cs @@ -27,7 +27,17 @@ public sealed class StoreSelection : IReadonlyBindablePropert /// /// 保护监听器集合和底层 Store 订阅句柄的同步锁。 /// +#if NET9_0_OR_GREATER + /// + /// net9.0 及以上目标使用专用 Lock,以满足分析器对专用同步原语的建议。 + /// + private readonly System.Threading.Lock _lock = new(); +#else + /// + /// net8.0 目标仍回退到 object 锁,以保持多目标编译兼容性。 + /// private readonly object _lock = new(); +#endif /// /// 负责从完整状态中投影出局部状态的选择器。 diff --git a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md index 0cec9f17..9130d00b 100644 --- a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md +++ b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md @@ -6,14 +6,15 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-062` -- 当前阶段:`Phase 62` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-063` +- 当前阶段:`Phase 63` - 当前焦点: - - `2026-04-25` 本轮 `$gframework-batch-boot 75` 已达到主停止条件,当前代码 stop commit 为 `9ce1fa6` + - `2026-04-25` 当前 turn 先执行 `$gframework-pr-review`,核对 PR #288 的 latest-head AI review 与本地真实状态 + - 已修复 `GFramework.Core.Tests/Extensions/AsyncExtensionsTests.cs` 的 `ConfiguredTaskAwaitable -> Task` 编译错误,并顺手收敛一批同类测试/样式残留 - 基线 `origin/main` 仍为 `9964962`(`2026-04-24T23:05:53+08:00`) - 当前累计 branch diff 相对 `origin/main` 为 `75` 个文件、`2098` 行,已触达本轮 `75 files` 阈值 - `RP-061` 之后已接受 2 个批次提交:`03c73a8`、`9ce1fa6` - - 当前默认恢复入口不再继续扩写集;若要继续 analyzer reduction,优先先处理 `GFramework.Core.Tests` 的 net10 build 环境阻塞 + - 当前默认恢复入口不再继续扩写集;若要继续 analyzer reduction,优先先处理 WSL 下 NuGet fallback package folder 指向失效 Windows 路径的构建环境阻塞 ## 当前活跃事实 @@ -27,6 +28,22 @@ - `AsyncExtensionsTests.cs` - `LogContextTests.cs` - `PauseStackManagerTests.cs` +- 本 turn 结合 PR #288 latest-head review 额外收敛了以下仍然成立的问题: + - `AsyncExtensionsTests.cs`:修复 `WithTimeoutAsync` 无返回值测试中错误返回 `ConfiguredTaskAwaitable` 导致的 `CS0029` / `CS1662` + - `AsyncKeyLockManagerTests.cs`:去掉两处不会产生额外价值的 `Assert.DoesNotThrowAsync(() => Task.WhenAll(...))` 包装,并把取消断言改为直接消费 `ValueTask.AsTask()` + - `AsyncArchitectureTests.cs` + - `ArchitectureLifecycleBehaviorTests.cs` + - `StateMachineSystemTests.cs` + - `RegistryInitializationHookBaseTests.cs` + - `NumericExtensions.cs` + - `StringExtensions.cs` + - `StoreBuilder.cs` + - `StoreSelection.cs` +- 当前 PR review 观察: + - PR:`#288` + - latest reviewed commit:`be336b2088b7c283a140add76d5cff30618ad16d` + - `coderabbitai[bot]` 仍有 `7` 个 open threads,`greptile-apps[bot]` 仍有 `2` 个 open threads + - 本 turn 已优先修复 latest-head 中明确指向 `AsyncExtensionsTests.cs:126` 的 critical 编译错误 - 本轮 `Core` runtime 低风险机械型清理已落地到: - `AsyncExtensions.cs` - `CollectionExtensions.cs` @@ -47,8 +64,9 @@ ## 当前风险 -- 当前环境下 `GFramework.Core.Tests` 的默认 net10 build 会在 SDK resolver 阶段命中 `MSB4276`。 - - 缓解措施:继续该 topic 前,优先修复或绕过 `Microsoft.NET.SDK.WorkloadAutoImportPropsLocator` 缺件;不要把该环境失败误判成当前 22 文件批次的代码回归。 +- 当前环境下 `GFramework.Core` / `GFramework.Core.Tests` 的 Release build 会命中 `MSB4018`。 + - 直接原因:`ResolvePackageAssets` 仍从历史 restore 生成的 `obj/*.csproj.nuget.g.props` 读取失效的 Windows fallback package folder `D:\Tool\Development Tools\Microsoft Visual Studio\Shared\NuGetPackages`。 + - 缓解措施:下次恢复时先重建 WSL 原生 restore 元数据,或显式清理并重做 `obj` 下的 NuGet restore 工件,再重新建立可信 build 基线。 - `dotnet clean GFramework.sln -c Release` 与 `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 仍无法稳定提供新的 clean 基线。 - 缓解措施:后续若继续整仓 warning reduction,需要单独定位 clean 失败原因,或明确继续沿用 direct build 观测值作为临时真值。 - 当前 worktree 仍存在未跟踪的 `.codex` 目录。 @@ -71,9 +89,17 @@ ## 验证说明 - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal` - - 结果:成功;`0 Warning(s)`、`0 Error(s)` + - 历史结果:成功;`0 Warning(s)`、`0 Error(s)` - `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-incremental --no-restore -p:RestoreFallbackFolders= -v:diag` - - 结果:失败;`MSB4276`,默认 SDK resolver 无法解析 `Microsoft.NET.SDK.WorkloadAutoImportPropsLocator`,属于当前 WSL / dotnet 10 环境阻塞 + - 历史结果:失败;`MSB4276`,默认 SDK resolver 无法解析 `Microsoft.NET.SDK.WorkloadAutoImportPropsLocator`,属于当前 WSL / dotnet 10 环境阻塞 +- `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;`MSB4018`,`ResolvePackageAssets` 命中失效 Windows fallback package folder `D:\Tool\Development Tools\Microsoft Visual Studio\Shared\NuGetPackages` +- `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net9.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;`MSB4018`,原因同上 +- `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore -p:TargetFramework=net10.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;`MSB4018`,原因同上 +- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json` + - 结果:成功;定位到 PR `#288`,提取 latest-head unresolved AI review threads、MegaLinter 与 Docstring Coverage 信号 - `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:TestTargetFrameworks=net8.0 -p:RestoreFallbackFolders="" -v minimal` - 结果:失败;`NU1201`,`GFramework.Tests.Common` 仅支持 `net10.0`,因此不能用 `net8.0` 旁路验证 `Core.Tests` - `git diff --name-only origin/main...HEAD | wc -l` @@ -83,6 +109,6 @@ ## 下一步建议 -1. 当前 `$gframework-batch-boot 75` 已达到阈值;默认在 `9ce1fa6` 停止,不再继续扩写集。 -2. 若后续要继续 `Core` / `Core.Tests` warning reduction,先解决 `GFramework.Core.Tests` 的 `MSB4276` 环境阻塞,再重新建立可信 warning 基线。 +1. 当前 turn 已先修复 latest-head PR review 中最紧急的编译错误;后续若继续 PR #288 收尾,优先重新抓取 unresolved threads,确认剩余 8 个 open threads 哪些仍成立。 +2. 若后续要继续 `Core` / `Core.Tests` warning reduction,先修复 WSL 下 stale NuGet restore metadata 导致的 `MSB4018`,再重新建立可信 build 基线。 3. 若要开启下一轮批处理,优先选择新的 stop-condition(例如新的 file 阈值、warning 目标或限定到单模块)后再继续。 diff --git a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md index 32e127c9..5a0916d0 100644 --- a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md +++ b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md @@ -1,5 +1,35 @@ # Analyzer Warning Reduction 追踪 +## 2026-04-25 — RP-063 + +### 阶段:先收口 PR #288 latest-head 编译错误,再暂停在环境阻塞点并准备提交 + +- 触发背景: + - 用户显式要求先执行 `$gframework-pr-review`,并指出 `AsyncExtensionsTests.cs(126,23)` 当前存在 `CS0029` / `CS1662` 构建错误 + - 当前 worktree 仍是 `fix/analyzer-warning-reduction-batch`,因此本 turn 继续沿用 `analyzer-warning-reduction` 的 active recovery 文档 +- 主线程实施: + - 运行 PR review 抓取脚本,确认当前分支对应 PR `#288` + - 核对 latest-head unresolved review threads 后,优先修复 `AsyncExtensionsTests.cs` 中 `ct => Task.Delay(...).ConfigureAwait(false)` 错误返回 `ConfiguredTaskAwaitable` 的问题 + - 顺手收敛多处已被 latest review 点名且本地仍成立的低风险残留: + - 测试中的 `async` 无 `await` + - `ValueTask` 断言包装 + - `RegistryInitializationHookBaseTests.cs` 的可空返回签名 + - `NumericExtensions.cs`、`StringExtensions.cs`、`StoreBuilder.cs` 的 Allman 花括号残留 + - `StoreSelection.cs` 在 `net9.0+` 下切到 `System.Threading.Lock`,同时保留 `net8.0` 兼容分支 +- 验证里程碑: + - `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json` + - 结果:成功;确认 PR `#288` 的 latest-head unresolved AI review threads 共 `9` 个,其中 `AsyncExtensionsTests.cs:126` 为 critical 编译错误 + - `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;`MSB4018`,`ResolvePackageAssets` 仍读取失效 Windows fallback package folder `D:\Tool\Development Tools\Microsoft Visual Studio\Shared\NuGetPackages` + - `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net9.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;原因同上 + - `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore -p:TargetFramework=net10.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;原因同上 +- 当前结论: + - 用户点名的 `AsyncExtensionsTests.cs` 编译错误已在源码层修复 + - 本 turn 未能拿到新的可通过 Release build,阻塞点已从先前记录的 `MSB4276` 收敛为当前 `obj/*.csproj.nuget.g.props` 中 stale Windows fallback package folder 导致的 `MSB4018` + - 用户随后要求“先不管这个了,先提交吧”,因此本 turn 在记录环境阻塞后先执行提交收口 + ## 2026-04-25 — RP-062 ### 阶段:触达 `$gframework-batch-boot 75` 停止阈值并收口到 `75 files / 2098 lines`