fix(game): 修复空对象配置比较键并归档 warning reduction 主题

- 修复 YamlConfigAllowedValue 与 YamlConfigConstantValue 对空对象 const 或 enum 比较键的误判,同时继续拒绝非空纯空白输入
- 补充 YamlConfigModelContractTests 对空比较键与纯空白比较键的回归覆盖,并验证空对象 const 场景
- 更新 ai-plan 公共索引并归档 analyzer-warning-reduction 主题,保留最终 PR review 结论与验证记录
This commit is contained in:
gewuyou 2026-04-29 10:27:01 +08:00
parent 590f2cb516
commit 0ad2ed1761
16 changed files with 89 additions and 33 deletions

View File

@ -19,6 +19,17 @@ public sealed class YamlConfigModelContractTests
Assert.Throws<ArgumentException>(() => new YamlConfigAllowedValue(" ", "visible")); Assert.Throws<ArgumentException>(() => new YamlConfigAllowedValue(" ", "visible"));
} }
/// <summary>
/// 验证枚举允许值模型会保留空对象等合法结构产生的空比较键。
/// </summary>
[Test]
public void AllowedValue_Should_Accept_Empty_Comparable_Value()
{
var allowedValue = new YamlConfigAllowedValue(string.Empty, "{}");
Assert.That(allowedValue.ComparableValue, Is.Empty);
}
/// <summary> /// <summary>
/// 验证常量约束模型会拒绝空白比较键。 /// 验证常量约束模型会拒绝空白比较键。
/// </summary> /// </summary>
@ -28,6 +39,17 @@ public sealed class YamlConfigModelContractTests
Assert.Throws<ArgumentException>(() => new YamlConfigConstantValue(" ", "\"visible\"")); Assert.Throws<ArgumentException>(() => new YamlConfigConstantValue(" ", "\"visible\""));
} }
/// <summary>
/// 验证常量约束模型会保留空对象等合法结构产生的空比较键。
/// </summary>
[Test]
public void ConstantValue_Should_Accept_Empty_Comparable_Value()
{
var constantValue = new YamlConfigConstantValue(string.Empty, "{}");
Assert.That(constantValue.ComparableValue, Is.Empty);
}
/// <summary> /// <summary>
/// 验证 contains 约束模型会在构造阶段拦截负值和反向区间。 /// 验证 contains 约束模型会在构造阶段拦截负值和反向区间。
/// </summary> /// </summary>

View File

@ -12,11 +12,16 @@ internal sealed class YamlConfigAllowedValue
/// <param name="comparableValue">用于与 YAML 节点比较的稳定键。</param> /// <param name="comparableValue">用于与 YAML 节点比较的稳定键。</param>
/// <param name="displayValue">用于诊断输出的原始 JSON 文本。</param> /// <param name="displayValue">用于诊断输出的原始 JSON 文本。</param>
/// <exception cref="ArgumentNullException">当 <paramref name="comparableValue"/> 或 <paramref name="displayValue"/> 为 <see langword="null" /> 时抛出。</exception> /// <exception cref="ArgumentNullException">当 <paramref name="comparableValue"/> 或 <paramref name="displayValue"/> 为 <see langword="null" /> 时抛出。</exception>
/// <exception cref="ArgumentException">当 <paramref name="comparableValue"/> 或 <paramref name="displayValue"/> 为空或仅包含空白字符时抛出。</exception> /// <exception cref="ArgumentException">当 <paramref name="comparableValue"/> 虽然非空但仅包含空白字符,或 <paramref name="displayValue"/> 为空或仅包含空白字符时抛出。</exception>
public YamlConfigAllowedValue(string comparableValue, string displayValue) public YamlConfigAllowedValue(string comparableValue, string displayValue)
{ {
ArgumentException.ThrowIfNullOrWhiteSpace(comparableValue); ArgumentNullException.ThrowIfNull(comparableValue);
ArgumentException.ThrowIfNullOrWhiteSpace(displayValue); ArgumentException.ThrowIfNullOrWhiteSpace(displayValue);
if (comparableValue.Length > 0 &&
string.IsNullOrWhiteSpace(comparableValue))
{
throw new ArgumentException("The value cannot be composed entirely of whitespace.", nameof(comparableValue));
}
ComparableValue = comparableValue; ComparableValue = comparableValue;
DisplayValue = displayValue; DisplayValue = displayValue;

View File

@ -12,11 +12,16 @@ internal sealed class YamlConfigConstantValue
/// <param name="comparableValue">用于与 YAML 节点比较的稳定键。</param> /// <param name="comparableValue">用于与 YAML 节点比较的稳定键。</param>
/// <param name="displayValue">用于诊断输出的原始常量文本。</param> /// <param name="displayValue">用于诊断输出的原始常量文本。</param>
/// <exception cref="ArgumentNullException">当 <paramref name="comparableValue"/> 或 <paramref name="displayValue"/> 为 <see langword="null" /> 时抛出。</exception> /// <exception cref="ArgumentNullException">当 <paramref name="comparableValue"/> 或 <paramref name="displayValue"/> 为 <see langword="null" /> 时抛出。</exception>
/// <exception cref="ArgumentException">当 <paramref name="comparableValue"/> 或 <paramref name="displayValue"/> 为空或仅包含空白字符时抛出。</exception> /// <exception cref="ArgumentException">当 <paramref name="comparableValue"/> 虽然非空但仅包含空白字符,或 <paramref name="displayValue"/> 为空或仅包含空白字符时抛出。</exception>
public YamlConfigConstantValue(string comparableValue, string displayValue) public YamlConfigConstantValue(string comparableValue, string displayValue)
{ {
ArgumentException.ThrowIfNullOrWhiteSpace(comparableValue); ArgumentNullException.ThrowIfNull(comparableValue);
ArgumentException.ThrowIfNullOrWhiteSpace(displayValue); ArgumentException.ThrowIfNullOrWhiteSpace(displayValue);
if (comparableValue.Length > 0 &&
string.IsNullOrWhiteSpace(comparableValue))
{
throw new ArgumentException("The value cannot be composed entirely of whitespace.", nameof(comparableValue));
}
ComparableValue = comparableValue; ComparableValue = comparableValue;
DisplayValue = displayValue; DisplayValue = displayValue;

View File

@ -12,11 +12,6 @@ help the current worktree land on the right recovery documents without scanning
## Active Topics ## Active Topics
- `analyzer-warning-reduction`
- Purpose: track the analyzer warning reduction branch, including the current recovery point, remaining warning
hotspots, and the next safe warning-reduction slice.
- Tracking: `ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md`
- Trace: `ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md`
- `ai-plan-governance` - `ai-plan-governance`
- Purpose: govern the `ai-plan/` directory model, startup index, and archive policy. - Purpose: govern the `ai-plan/` directory model, startup index, and archive policy.
- Tracking: `ai-plan/public/ai-plan-governance/todos/ai-plan-governance-tracking.md` - Tracking: `ai-plan/public/ai-plan-governance/todos/ai-plan-governance-tracking.md`
@ -50,9 +45,6 @@ help the current worktree land on the right recovery documents without scanning
## Worktree To Active Topic Map ## Worktree To Active Topic Map
- Branch: `fix/analyzer-warning-reduction-batch`
- Worktree hint: `GFramework-analyzer`
- Priority 1: `analyzer-warning-reduction`
- Branch: `feat/ai-first-config` - Branch: `feat/ai-first-config`
- Worktree hint: `GFramework-Ai-First-Config` - Worktree hint: `GFramework-Ai-First-Config`
- Priority 1: `ai-first-config-system` - Priority 1: `ai-first-config-system`
@ -75,6 +67,9 @@ help the current worktree land on the right recovery documents without scanning
- Priority 1: `documentation-full-coverage-governance` - Priority 1: `documentation-full-coverage-governance`
## Archived Topics ## Archived Topics
- `analyzer-warning-reduction`
- Archive root: `ai-plan/public/archive/analyzer-warning-reduction/`
- Note: 长期 warning-reduction 分支已收尾PR #301 的最终 review follow-up 已本地闭环,后续仅作为历史恢复材料保留。
- `cqrs-cache-docs-hardening` - `cqrs-cache-docs-hardening`
- Archive root: `ai-plan/public/archive/cqrs-cache-docs-hardening/` - Archive root: `ai-plan/public/archive/cqrs-cache-docs-hardening/`
- Note: archived topics stay outside the default `boot` context until a user explicitly requests historical review. - Note: archived topics stay outside the default `boot` context until a user explicitly requests historical review.

View File

@ -6,13 +6,12 @@
## 当前恢复点 ## 当前恢复点
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-095` - 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-096`
- 当前阶段:`Phase 95` - 当前阶段:`Completed`
- 当前焦点: - 当前焦点:
- `2026-04-29` 继续处理 `PR #301` 的 latest-head review threads只修复当前工作树上仍然成立的问题 - `2026-04-29` 已完成 `PR #301` latest-head review threads 的最终本地复核,并修复仍然成立的空对象 `const` 比较键回归
- 已修复 `MediatorArchitectureIntegrationTests` 中仍然成立的并发与阻塞问题:移除冗余分支、把 `Task.Delay().Wait()` 改为 `await`、把静态缓存换成 `ConcurrentDictionary`、并把共享计数更新改成原子操作 - 当前 topic 已达到归档条件:长期 warning-reduction 分支的实现、PR review follow-up 与最小验证均已完成
- 已补 `GFramework.Game/Config` 运行时 schema 模型的构造期契约校验与 `<exception>` XML 文档,并新增 `YamlConfigModelContractTests` 锁定这些无效状态保护 - 当前目录已迁入 `ai-plan/public/archive/analyzer-warning-reduction/`,后续仅保留历史恢复价值
- 本轮明确暂不接受两个误报方向:`YamlConfigReferenceUsage.DisplayPath` 别名删除建议,以及两个本地枚举补 `[GenerateEnumExtensions]` 的泛化建议
## 当前活跃事实 ## 当前活跃事实
@ -20,26 +19,27 @@
- 当前直接验证结果: - 当前直接验证结果:
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release -clp:Summary` - `dotnet build GFramework.Game/GFramework.Game.csproj -c Release -clp:Summary`
- 最新结果:成功;`0 Warning(s)``0 Error(s)` - 最新结果:成功;`0 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigSchemaValidatorTests|FullyQualifiedName~YamlConfigModelContractTests"` - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~LoadAsync_Should_Accept_Empty_Object_Schema_Const|FullyQualifiedName~YamlConfigModelContractTests"`
- 最新结果:成功;`10` 通过、`0` 失败 - 最新结果:成功;`10` 通过、`0` 失败
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~MediatorArchitectureIntegrationTests|FullyQualifiedName~MediatorAdvancedFeaturesTests"` - `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~MediatorArchitectureIntegrationTests|FullyQualifiedName~MediatorAdvancedFeaturesTests"`
- 最新结果:成功;`25` 通过、`0` 失败 - 最新结果:成功;`25` 通过、`0` 失败
- `dotnet format GFramework.sln --verify-no-changes --include GFramework.Game/Config/YamlConfigAllowedValue.cs GFramework.Game/Config/YamlConfigConstantValue.cs GFramework.Game.Tests/Config/YamlConfigModelContractTests.cs`
- 最新结果:成功;当前修复范围内无格式漂移
- `git diff --check` - `git diff --check`
- 最新结果:成功;无新增 whitespace / conflict-marker 问题 - 最新结果:成功;无新增 whitespace / conflict-marker 问题
- 当前批次摘要: - 当前批次摘要:
- 当前切片直接修改 `12` 个已有文件,并新增 `YamlConfigModelContractTests.cs` 作为模型契约回归覆盖 - 当前最终收尾切片直接修改 `3` 个已有文件,不再扩写 warning-batch 的多文件清理范围
- 本轮修复集中在 `GFramework.Cqrs.Tests``GFramework.Game` 两个最新 review thread 热区,没有再扩写回 warning-batch 的多文件并发清理范围 - 这次收尾把 `YamlConfigAllowedValue` / `YamlConfigConstantValue``comparableValue` 契约收窄为“允许空字符串,但拒绝非空纯空白”,恢复空对象 `const` / `enum` 的合法比较键语义
- PR review triage 结论: - PR review triage 结论:
- 接受:并发共享状态、阻塞等待、无效约束状态、缺失 `<exception>` 文档 - 接受并完成:并发共享状态、阻塞等待、无效约束状态、缺失 `<exception>` 文档、空对象比较键回归
- 延后:`DisplayPath` 诊断别名删除建议 - 归档前剩余 open threads 只包含两类:尚未推送折叠的 stale 线程,以及已明确延后 / 驳回的建议(`DisplayPath` 与枚举特性泛化)
- 驳回:两个枚举补 `[GenerateEnumExtensions]` 的泛化建议
## 当前风险 ## 当前风险
- 当前 GitHub PR 仍会保留尚未推送折叠的 open threads以及被明确延后 / 驳回的机器人建议 - 当前 GitHub PR 在本地提交并推送前仍可能显示旧的 open threads
- 缓解措施:提交并推送后重新执行 `$gframework-pr-review`,只保留仍有真实依据的剩余线程 - 缓解措施:以本文件中的本地验证结果为 archive 真值;若未来需要复查 PR 页面,应从 archive 恢复而不是重新激活 topic
- 本轮未重跑仓库根 `dotnet clean` + `dotnet build`,因此 RP-094 的仓库级 warning 真值不能直接外推到这次 PR-review follow-up 之后 - 本轮仅对 `GFramework.Game` 收尾回归做了受影响模块验证,没有重新建立新的仓库根 clean build 基线
- 缓解措施:若下一轮重新回到 analyzer warning reduction 主线,先按仓库规则重新采样仓库根 clean build - 缓解措施:后续若有新的 warning-reduction 任务,应创建新 topic并重新执行仓库根 `dotnet clean` + `dotnet build` 采样
## 活跃文档 ## 活跃文档
@ -60,13 +60,13 @@
## 验证说明 ## 验证说明
- 权威验证结果统一维护在“当前活跃事实”。 - 权威验证结果统一维护在“当前活跃事实”。
- `GFramework.Game` 当前 Release 构建已清零,并通过 config 定向测试。 - `GFramework.Game` 当前 Release 构建已清零,并通过空对象 `const` 回归与模型契约定向测试。
- `GFramework.Cqrs.Tests` 当前 PR-review follow-up 定向测试通过,说明并发/缓存测试辅助实现的行为修正没有破坏现有集成断言。 - `GFramework.Cqrs.Tests` 当前 PR-review follow-up 定向测试通过,说明并发/缓存测试辅助实现的行为修正没有破坏现有集成断言。
- `dotnet format --verify-no-changes` 已确认当前收尾改动未引入新的格式化偏差。
- `git diff --check` 结果为空,说明本轮新增改动没有引入新的尾随空格或冲突标记。 - `git diff --check` 结果为空,说明本轮新增改动没有引入新的尾随空格或冲突标记。
- 本轮以受影响项目的 Release build / tests 为完成条件;若下轮恢复 warning reduction 仓库级真值,需要重新执行仓库根 `dotnet clean` + `dotnet build` - 本 topic 已进入 archive若未来重启 warning reduction应以新 topic 和新的仓库级 clean build 基线继续
## 下一步建议 ## 下一步建议
1. 提交当前 PR-review follow-up 与本轮 `ai-plan` 同步。 1. 保持当前 archive 状态,不要再把该 topic 作为默认 boot 入口。
2. 推送分支后重新执行 `$gframework-pr-review`,确认剩余 open threads 是否只剩延后 / 误报项。 2. 若未来需要继续 warning reduction创建新的 active topic并重新建立仓库根 clean build 真值。
3. 若下一轮恢复 warning reduction 主线,先重新执行仓库根 `dotnet clean` + `dotnet build` 建立新的权威基线。

View File

@ -1,5 +1,34 @@
# Analyzer Warning Reduction 追踪 # Analyzer Warning Reduction 追踪
## 2026-04-29 — RP-096
### 阶段:完成 `PR #301` 最终收尾并归档长期 warning-reduction 主题
- 触发背景:
- 用户要求先用 `$gframework-pr-review` 解决当前 PR review 的剩余问题,然后把整个长期分支主题归档
- 本轮 triage 结论:
- `MediatorArchitectureIntegrationTests` 并发更新、`YamlConfigConditionalSchemas` / `YamlConfigStringFormatConstraint``<exception>` 文档,以及两个枚举的 `[GenerateEnumExtensions]` 在当前工作树上均已存在,对应 open threads 判定为 stale
- `YamlConfigReferenceUsage.DisplayPath` 删除建议继续判定为不成立,因为 loader 诊断、引用索引和测试断言仍把它作为稳定语义标签使用
- `LoadAsync_Should_Accept_Empty_Object_Schema_Const` 失败仍然成立:上轮把 `YamlConfigAllowedValue` / `YamlConfigConstantValue``comparableValue` 收紧成 `ThrowIfNullOrWhiteSpace(...)` 后,误伤了空对象常量的合法空比较键
- 主线程实施:
- 将 `YamlConfigAllowedValue``YamlConfigConstantValue` 的比较键契约调整为:
- 允许 `string.Empty`
- 继续拒绝非空纯空白字符串
- 保留 `displayValue` 的非空白要求
- 扩充 `YamlConfigModelContractTests`,新增空比较键的正向覆盖,同时保留纯空白比较键的回归保护
- 验证里程碑:
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
- 结果:成功;`0 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~LoadAsync_Should_Accept_Empty_Object_Schema_Const|FullyQualifiedName~YamlConfigModelContractTests"`
- 结果:成功;`10` 通过、`0` 失败
- `dotnet format GFramework.sln --verify-no-changes --include GFramework.Game/Config/YamlConfigAllowedValue.cs GFramework.Game/Config/YamlConfigConstantValue.cs GFramework.Game.Tests/Config/YamlConfigModelContractTests.cs`
- 结果:成功
- `git diff --check`
- 结果:成功;无新增 whitespace / conflict-marker 问题
- 归档结论:
- `analyzer-warning-reduction` 当前 topic 已满足归档条件:长期 warning-reduction 主线已收尾PR #301 的本地 follow-up 闭环完成
- 整个 topic 目录已迁入 `ai-plan/public/archive/analyzer-warning-reduction/`,不再作为 active 默认入口
## 2026-04-29 — RP-095 ## 2026-04-29 — RP-095
### 阶段:复核 `PR #301` latest-head review threads并只修复当前工作树上仍然成立的问题 ### 阶段:复核 `PR #301` latest-head review threads并只修复当前工作树上仍然成立的问题