diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs index ecb44a4f..4b04ae0b 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs @@ -1,4 +1,5 @@ using System.IO; +using GFramework.Core.Abstractions.Events; using GFramework.Game.Abstractions.Config; using GFramework.Game.Config; @@ -2789,83 +2790,15 @@ public class YamlConfigLoaderTests [Test] public async Task EnableHotReload_Should_Keep_Previous_State_When_Contains_Reference_Dependency_Breaks() { - CreateConfigFile( - "item/potion.yaml", - """ - id: potion - name: Potion - """); - CreateConfigFile( - "monster/slime.yaml", - """ - id: 1 - name: Slime - dropItemIds: - - potion - """); - CreateSchemaFile( - "schemas/item.schema.json", - """ - { - "type": "object", - "required": ["id", "name"], - "properties": { - "id": { "type": "string" }, - "name": { "type": "string" } - } - } - """); - CreateSchemaFile( - "schemas/monster.schema.json", - """ - { - "type": "object", - "required": ["id", "name", "dropItemIds"], - "properties": { - "id": { "type": "integer" }, - "name": { "type": "string" }, - "dropItemIds": { - "type": "array", - "minContains": 1, - "contains": { - "type": "string", - "x-gframework-ref-table": "item" - }, - "items": { - "type": "string" - } - } - } - } - """); - - var loader = new YamlConfigLoader(_rootPath) - .RegisterTable("item", "item", "schemas/item.schema.json", - static config => config.Id) - .RegisterTable("monster", "monster", "schemas/monster.schema.json", - static config => config.Id); - var registry = new ConfigRegistry(); - await loader.LoadAsync(registry); - - var reloadFailureTaskSource = - new TaskCompletionSource<(string TableName, Exception Exception)>(TaskCreationOptions - .RunContinuationsAsynchronously); - var hotReload = loader.EnableHotReload( - registry, - onTableReloadFailed: (tableName, exception) => - reloadFailureTaskSource.TrySetResult((tableName, exception)), - debounceDelay: TimeSpan.FromMilliseconds(150)); + var (loader, registry) = await CreateLoadedContainsReferenceHotReloadScenarioAsync().ConfigureAwait(false); + var (reloadFailureTaskSource, hotReload) = EnableHotReloadWithFailureCapture(loader, registry); try { - CreateConfigFile( - "item/potion.yaml", - """ - id: elixir - name: Elixir - """); + CreateConfigFile("item/potion.yaml", UpdatedItemConfigContent); - var failure = await WaitForTaskWithinAsync(reloadFailureTaskSource.Task, TimeSpan.FromSeconds(5)); + var failure = await WaitForTaskWithinAsync(reloadFailureTaskSource.Task, TimeSpan.FromSeconds(5)) + .ConfigureAwait(false); var diagnosticException = failure.Exception as ConfigLoadException; Assert.Multiple(() => @@ -2958,65 +2891,17 @@ public class YamlConfigLoaderTests [Test] public async Task EnableHotReload_Should_Support_Options_Object() { - CreateConfigFile( - "monster/slime.yaml", - """ - id: 1 - name: Slime - hp: 10 - """); - CreateSchemaFile( - "schemas/monster.schema.json", - """ - { - "type": "object", - "required": ["id", "name"], - "properties": { - "id": { "type": "integer" }, - "name": { "type": "string" }, - "hp": { "type": "integer" } - } - } - """); - - var loader = new YamlConfigLoader(_rootPath) - .RegisterTable( - new YamlConfigTableRegistrationOptions( - "monster", - "monster", - static config => config.Id) - { - SchemaRelativePath = "schemas/monster.schema.json" - }); - var registry = new ConfigRegistry(); - await loader.LoadAsync(registry); - - var reloadTaskSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var hotReload = loader.EnableHotReload( - registry, - new YamlConfigHotReloadOptions - { - OnTableReloaded = tableName => reloadTaskSource.TrySetResult(tableName), - DebounceDelay = TimeSpan.FromMilliseconds(150) - }); + var (loader, registry) = await CreateLoadedMonsterHotReloadScenarioAsync(useOptionsObject: true) + .ConfigureAwait(false); + var (reloadTaskSource, hotReload) = EnableHotReloadWithReloadCapture(loader, registry, useOptionsObject: true); try { - CreateConfigFile( - "monster/slime.yaml", - """ - id: 1 - name: Slime - hp: 25 - """); + CreateConfigFile("monster/slime.yaml", UpdatedMonsterConfigContent); - var tableName = await WaitForTaskWithinAsync(reloadTaskSource.Task, TimeSpan.FromSeconds(5)); - - Assert.Multiple(() => - { - Assert.That(tableName, Is.EqualTo("monster")); - Assert.That(registry.GetTable("monster").Get(1).Hp, Is.EqualTo(25)); - }); + var tableName = await WaitForTaskWithinAsync(reloadTaskSource.Task, TimeSpan.FromSeconds(5)) + .ConfigureAwait(false); + AssertMonsterHotReloadUpdated(tableName, registry); } finally { @@ -3050,60 +2935,15 @@ public class YamlConfigLoaderTests [Test] public async Task EnableHotReload_Should_Keep_Previous_Table_When_Schema_Change_Makes_Reload_Fail() { - CreateConfigFile( - "monster/slime.yaml", - """ - id: 1 - name: Slime - hp: 10 - """); - CreateSchemaFile( - "schemas/monster.schema.json", - """ - { - "type": "object", - "required": ["id", "name"], - "properties": { - "id": { "type": "integer" }, - "name": { "type": "string" }, - "hp": { "type": "integer" } - } - } - """); - - var loader = new YamlConfigLoader(_rootPath) - .RegisterTable("monster", "monster", "schemas/monster.schema.json", - static config => config.Id); - var registry = new ConfigRegistry(); - await loader.LoadAsync(registry); - - var reloadFailureTaskSource = - new TaskCompletionSource<(string TableName, Exception Exception)>(TaskCreationOptions - .RunContinuationsAsynchronously); - var hotReload = loader.EnableHotReload( - registry, - onTableReloadFailed: (tableName, exception) => - reloadFailureTaskSource.TrySetResult((tableName, exception)), - debounceDelay: TimeSpan.FromMilliseconds(150)); + var (loader, registry) = await CreateLoadedMonsterHotReloadScenarioAsync().ConfigureAwait(false); + var (reloadFailureTaskSource, hotReload) = EnableHotReloadWithFailureCapture(loader, registry); try { - CreateSchemaFile( - "schemas/monster.schema.json", - """ - { - "type": "object", - "required": ["id", "name", "rarity"], - "properties": { - "id": { "type": "integer" }, - "name": { "type": "string" }, - "hp": { "type": "integer" }, - "rarity": { "type": "string" } - } - } - """); + CreateSchemaFile("schemas/monster.schema.json", MonsterSchemaWithRarityContent); - var failure = await WaitForTaskWithinAsync(reloadFailureTaskSource.Task, TimeSpan.FromSeconds(5)); + var failure = await WaitForTaskWithinAsync(reloadFailureTaskSource.Task, TimeSpan.FromSeconds(5)) + .ConfigureAwait(false); var diagnosticException = failure.Exception as ConfigLoadException; Assert.Multiple(() => @@ -3130,75 +2970,15 @@ public class YamlConfigLoaderTests [Test] public async Task EnableHotReload_Should_Keep_Previous_State_When_Dependency_Table_Breaks_Cross_Table_Reference() { - CreateConfigFile( - "item/potion.yaml", - """ - id: potion - name: Potion - """); - CreateConfigFile( - "monster/slime.yaml", - """ - id: 1 - name: Slime - dropItemId: potion - """); - CreateSchemaFile( - "schemas/item.schema.json", - """ - { - "type": "object", - "required": ["id", "name"], - "properties": { - "id": { "type": "string" }, - "name": { "type": "string" } - } - } - """); - CreateSchemaFile( - "schemas/monster.schema.json", - """ - { - "type": "object", - "required": ["id", "name", "dropItemId"], - "properties": { - "id": { "type": "integer" }, - "name": { "type": "string" }, - "dropItemId": { - "type": "string", - "x-gframework-ref-table": "item" - } - } - } - """); - - var loader = new YamlConfigLoader(_rootPath) - .RegisterTable("item", "item", "schemas/item.schema.json", - static config => config.Id) - .RegisterTable("monster", "monster", "schemas/monster.schema.json", - static config => config.Id); - var registry = new ConfigRegistry(); - await loader.LoadAsync(registry); - - var reloadFailureTaskSource = - new TaskCompletionSource<(string TableName, Exception Exception)>(TaskCreationOptions - .RunContinuationsAsynchronously); - var hotReload = loader.EnableHotReload( - registry, - onTableReloadFailed: (tableName, exception) => - reloadFailureTaskSource.TrySetResult((tableName, exception)), - debounceDelay: TimeSpan.FromMilliseconds(150)); + var (loader, registry) = await CreateLoadedCrossTableReferenceHotReloadScenarioAsync().ConfigureAwait(false); + var (reloadFailureTaskSource, hotReload) = EnableHotReloadWithFailureCapture(loader, registry); try { - CreateConfigFile( - "item/potion.yaml", - """ - id: elixir - name: Elixir - """); + CreateConfigFile("item/potion.yaml", UpdatedItemConfigContent); - var failure = await WaitForTaskWithinAsync(reloadFailureTaskSource.Task, TimeSpan.FromSeconds(5)); + var failure = await WaitForTaskWithinAsync(reloadFailureTaskSource.Task, TimeSpan.FromSeconds(5)) + .ConfigureAwait(false); var diagnosticException = failure.Exception as ConfigLoadException; Assert.Multiple(() => @@ -3223,6 +3003,243 @@ public class YamlConfigLoaderTests } } + private const string ItemSchemaContent = + """ + { + "type": "object", + "required": ["id", "name"], + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" } + } + } + """; + + private const string InitialMonsterConfigContent = + """ + id: 1 + name: Slime + hp: 10 + """; + + private const string UpdatedMonsterConfigContent = + """ + id: 1 + name: Slime + hp: 25 + """; + + private const string MonsterSchemaContent = + """ + { + "type": "object", + "required": ["id", "name"], + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "hp": { "type": "integer" } + } + } + """; + + private const string MonsterSchemaWithRarityContent = + """ + { + "type": "object", + "required": ["id", "name", "rarity"], + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "hp": { "type": "integer" }, + "rarity": { "type": "string" } + } + } + """; + + private const string UpdatedItemConfigContent = + """ + id: elixir + name: Elixir + """; + + private const string MonsterDropArrayConfigContent = + """ + id: 1 + name: Slime + dropItemIds: + - potion + """; + + private const string MonsterDropArraySchemaContent = + """ + { + "type": "object", + "required": ["id", "name", "dropItemIds"], + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "dropItemIds": { + "type": "array", + "minContains": 1, + "contains": { + "type": "string", + "x-gframework-ref-table": "item" + }, + "items": { + "type": "string" + } + } + } + } + """; + + private const string MonsterDropConfigContent = + """ + id: 1 + name: Slime + dropItemId: potion + """; + + private const string MonsterDropSchemaContent = + """ + { + "type": "object", + "required": ["id", "name", "dropItemId"], + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "dropItemId": { + "type": "string", + "x-gframework-ref-table": "item" + } + } + } + """; + + /// + /// 创建并加载标准 monster 热重载夹具,供重载成功与 schema 失败场景复用。 + /// + /// 是否通过选项对象注册表。 + /// 已完成首次加载的加载器与注册表。 + private async Task<(YamlConfigLoader Loader, ConfigRegistry Registry)> CreateLoadedMonsterHotReloadScenarioAsync( + bool useOptionsObject = false) + { + CreateConfigFile("monster/slime.yaml", InitialMonsterConfigContent); + CreateSchemaFile("schemas/monster.schema.json", MonsterSchemaContent); + + var loader = useOptionsObject + ? new YamlConfigLoader(_rootPath).RegisterTable( + new YamlConfigTableRegistrationOptions( + "monster", + "monster", + static config => config.Id) + { + SchemaRelativePath = "schemas/monster.schema.json" + }) + : new YamlConfigLoader(_rootPath) + .RegisterTable("monster", "monster", "schemas/monster.schema.json", + static config => config.Id); + var registry = new ConfigRegistry(); + await loader.LoadAsync(registry).ConfigureAwait(false); + return (loader, registry); + } + + /// + /// 创建并加载 contains 子 schema 引用场景,供热重载依赖回滚测试复用。 + /// + /// 已完成首次加载的加载器与注册表。 + private async Task<(YamlConfigLoader Loader, ConfigRegistry Registry)> + CreateLoadedContainsReferenceHotReloadScenarioAsync() + { + var loader = CreateItemBackedMonsterLoader( + MonsterDropArrayConfigContent, + MonsterDropArraySchemaContent, + static config => config.Id, + ("item/potion.yaml", "potion", "Potion")); + var registry = new ConfigRegistry(); + await loader.LoadAsync(registry).ConfigureAwait(false); + return (loader, registry); + } + + /// + /// 创建并加载跨表单值引用场景,供热重载依赖回滚测试复用。 + /// + /// 已完成首次加载的加载器与注册表。 + private async Task<(YamlConfigLoader Loader, ConfigRegistry Registry)> + CreateLoadedCrossTableReferenceHotReloadScenarioAsync() + { + var loader = CreateItemBackedMonsterLoader( + MonsterDropConfigContent, + MonsterDropSchemaContent, + static config => config.Id, + ("item/potion.yaml", "potion", "Potion")); + var registry = new ConfigRegistry(); + await loader.LoadAsync(registry).ConfigureAwait(false); + return (loader, registry); + } + + /// + /// 以统一的失败回调配置启用热重载,避免每个测试重复接线相同的通知逻辑。 + /// + /// 已完成首次加载的加载器。 + /// 要复用的配置注册表。 + /// 失败通知任务源与取消注册句柄。 + private static (TaskCompletionSource<(string TableName, Exception Exception)> TaskSource, IUnRegister Registration) + EnableHotReloadWithFailureCapture(YamlConfigLoader loader, ConfigRegistry registry) + { + var reloadFailureTaskSource = + new TaskCompletionSource<(string TableName, Exception Exception)>(TaskCreationOptions + .RunContinuationsAsynchronously); + var hotReload = loader.EnableHotReload( + registry, + onTableReloadFailed: (tableName, exception) => + reloadFailureTaskSource.TrySetResult((tableName, exception)), + debounceDelay: TimeSpan.FromMilliseconds(150)); + return (reloadFailureTaskSource, hotReload); + } + + /// + /// 以统一的成功回调配置启用热重载,避免相同的防抖与回调装配在测试中重复出现。 + /// + /// 已完成首次加载的加载器。 + /// 要复用的配置注册表。 + /// 是否通过选项对象启用热重载。 + /// 成功通知任务源与取消注册句柄。 + private static (TaskCompletionSource TaskSource, IUnRegister Registration) EnableHotReloadWithReloadCapture( + YamlConfigLoader loader, + ConfigRegistry registry, + bool useOptionsObject = false) + { + var reloadTaskSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var hotReload = useOptionsObject + ? loader.EnableHotReload( + registry, + new YamlConfigHotReloadOptions + { + OnTableReloaded = tableName => reloadTaskSource.TrySetResult(tableName), + DebounceDelay = TimeSpan.FromMilliseconds(150) + }) + : loader.EnableHotReload( + registry, + onTableReloaded: tableName => reloadTaskSource.TrySetResult(tableName), + debounceDelay: TimeSpan.FromMilliseconds(150)); + return (reloadTaskSource, hotReload); + } + + /// + /// 断言标准 monster 热重载成功后,通知表名与刷新后的生命值都符合预期。 + /// + /// 热重载回调返回的表名。 + /// 承载刷新结果的注册表。 + private static void AssertMonsterHotReloadUpdated(string tableName, ConfigRegistry registry) + { + Assert.Multiple(() => + { + Assert.That(tableName, Is.EqualTo("monster")); + Assert.That(registry.GetTable("monster").Get(1).Hp, Is.EqualTo(25)); + }); + } + /// /// 为对象数组 contains 子集匹配场景创建加载器,避免测试方法体被大段固定 schema 稀释。 /// 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 5cdb06be..9787b506 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,37 +6,39 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-065` -- 当前阶段:`Phase 65` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-066` +- 当前阶段:`Phase 66` - 当前焦点: - - `2026-04-25` 已确认此前一批 `dotnet clean` / `dotnet build` / `dotnet test` 异常主要来自 agent 沙箱环境,而不是仓库或 WSL 默认 shell 本身 - - 已用提权后的直接命令重新建立仓库根基线:`dotnet clean` 成功,`dotnet build` 结果为 `656 Warning(s)`、`0 Error(s)` - - 当前 `HEAD` 与 `origin/main` 对齐;基于 `$gframework-batch-boot 50` 的 committed branch diff 现为 `0 files / 0 lines` -- 当前活跃写集是 4 个测试噪音清理文件,属于新的低风险 warning-reduction 批次;提交前需由主线程在沙箱外重新验证 - - 主线程已完成该批次的沙箱外复核,当前可安全并入本轮提交 - - 已决定把“沙箱内 .NET 验证失败时必须申请沙箱外重跑并以该结果为准”写入 `AGENTS.md`,避免后续继续扩散伪环境阻塞 + - `2026-04-25` 已先提交 `6a704f3` `fix(analyzer): 固化沙箱外验证并清理测试噪音`,把 AGENTS / active ai-plan 真值修正与 4 文件测试噪音批次一起收口 + - 当前单文件批次聚焦 `GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs`,已收敛 4 个根构建直接确认的 `MA0051` + - 提权后的直接仓库根基线已从 `656 Warning(s)` 降至 `652 Warning(s)`,说明本轮单文件批次有效 + - `GFramework.Game.Tests` 的直接受影响 Release build 当前为 `0 Warning(s)`、`0 Error(s)` + - 本批次落地后,branch diff 相对 `origin/main` 将从 `7 files` 推进到 `8 files`,仍显著低于 `$gframework-batch-boot 50` 阈值 ## 当前活跃事实 - 当前 `origin/main` 基线提交为 `4ad880c`(`2026-04-25T14:35:38+08:00`)。 -- `fix/analyzer-warning-reduction-batch` 当前 `HEAD` 等于 `origin/main`;因此 shorthand 阈值 `$gframework-batch-boot 50` 的当前 committed 规模为: - - `git diff --name-only origin/main...HEAD | wc -l`:`0` - - `git diff --numstat origin/main...HEAD`:`0 added / 0 deleted` -- 提权后的直接仓库根验证已经确认: +- `6a704f3` 落地后,当前 committed branch diff 相对 `origin/main` 为: + - `git diff --name-only origin/main...HEAD | wc -l`:`7` + - `git diff --numstat origin/main...HEAD`:累计 `104` added、`123` deleted +- 提权后的直接仓库根验证当前确认为: - `dotnet clean` - 结果:成功;此前沙箱内 “Build FAILED but 0 errors” 的 clean 结果不是仓库真值 - `dotnet build` - - 结果:成功;`656 Warning(s)`、`0 Error(s)`,当前 warning reduction 应以此为总基线 -- 当前待集成的低风险批次文件: + - 最新结果:成功;`652 Warning(s)`、`0 Error(s)` +- 已提交的低风险批次文件: + - `AGENTS.md` + - `GFramework.Ecs.Arch.Tests/Ecs/EcsAdvancedTests.cs` - `GFramework.Game.Tests/Config/GameConfigBootstrapTests.cs` - `GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs` - `GFramework.Game.Tests/Config/YamlConfigTextValidatorTests.cs` - - `GFramework.Ecs.Arch.Tests/Ecs/EcsAdvancedTests.cs` -- 上述批次的 worker 侧验证结果: + - `ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md` + - `ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md` +- 当前待提交批次文件: + - `GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs` +- 当前批次验证结果: - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` - 最新主线程结果:成功;`0 Warning(s)`、`0 Error(s)` - - `dotnet build GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj -c Release` - - 最新主线程结果:成功;`0 Warning(s)`、`0 Error(s)` ## 当前风险 @@ -64,18 +66,16 @@ - `dotnet clean` - 当前结果:成功;在提权后的直接 shell 中可正常完成仓库根 clean - `dotnet build` - - 当前结果:成功;`656 Warning(s)`、`0 Error(s)` + - 当前结果:成功;`652 Warning(s)`、`0 Error(s)` - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` - 当前结果:成功;`0 Warning(s)`、`0 Error(s)` -- `dotnet build GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj -c Release` - - 当前结果:成功;`0 Warning(s)`、`0 Error(s)` - `git diff --name-only origin/main...HEAD | wc -l` - - 当前结果:`0` + - 当前结果:`7` - `git diff --numstat origin/main...HEAD` - - 当前结果:`0 added / 0 deleted` + - 当前结果:累计 `104` added、`123` deleted ## 下一步建议 -1. 集成并复核当前 4 文件测试噪音批次后,用沙箱外 `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 与 `dotnet build GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj -c Release` 重新确认结果。 -2. 下一轮 warning reduction 继续优先处理 `GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs` 的长方法切片;它仍是已确认的单文件低风险热点,适合继续用 worker 独立推进。 +1. 提交当前 `YamlConfigLoaderTests.cs` 单文件批次后,继续按 `$gframework-batch-boot 50` 规则重算 branch diff,并挑选下一个 1-3 文件的低风险热点。 +2. 下一轮优先从 `GFramework.Cqrs.Tests` 或 `GFramework.Game` 中继续选择单文件 `MA0051` / 测试噪音切片,避免过早推高 review 范围。 3. 后续凡是沙箱内 `.NET` 验证再次出现无诊断失败、pipe/socket 权限问题或与普通 shell 不一致的结果,直接申请沙箱外重跑同一命令,不再扩散 workaround 型命令噪音。 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 2faf0967..2e3dd9c8 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,32 @@ # Analyzer Warning Reduction 追踪 +## 2026-04-25 — RP-066 + +### 阶段:主线程回收停滞的单文件批次,并继续压低根构建 warning 基线 + +- 触发背景: + - `RP-065` 收尾后,`fix/analyzer-warning-reduction-batch` 已通过 `6a704f3` 把 AGENTS / active ai-plan 真值修正和 4 文件测试噪音批次提交到分支 + - 原先负责 `YamlConfigLoaderTests.cs` 的 worker 长时间无结果,主线程收回该单文件批次以避免继续阻塞 +- 主线程实施: + - 关闭停滞 worker,直接重构 `GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs` + - 通过提取固定夹具内容、热重载接线 helper 与共享断言,收敛以下 4 个长方法 warning: + - `EnableHotReload_Should_Keep_Previous_State_When_Contains_Reference_Dependency_Breaks` + - `EnableHotReload_Should_Support_Options_Object` + - `EnableHotReload_Should_Keep_Previous_Table_When_Schema_Change_Makes_Reload_Fail` + - `EnableHotReload_Should_Keep_Previous_State_When_Dependency_Table_Breaks_Cross_Table_Reference` + - 在第一次仓库根重建中命中了两个 `CS0411` 泛型推断错误,主线程随即补上显式类型参数并重新建立 clean/build 基线 +- 验证里程碑: + - `dotnet clean` + - 结果:成功 + - `dotnet build` + - 结果:成功;`652 Warning(s)`、`0 Error(s)`,相较 `RP-065` 的 `656` 再下降 `4` + - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` + - 结果:成功;`0 Warning(s)`、`0 Error(s)` +- 当前结论: + - `YamlConfigLoaderTests.cs` 这 4 个根构建直接确认的 `MA0051` 已被消化 + - 当前分支在 `6a704f3` 之后的下一提交只会新增 1 个唯一文件,因此 branch diff 仍明显低于 `$gframework-batch-boot 50` 阈值 + - 下一轮可继续选择新的单文件或小写集热点,而不必暂停当前 batch loop + ## 2026-04-25 — RP-065 ### 阶段:确认 .NET 验证噪音来自沙箱,并把无沙箱直跑写成仓库规则