diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderIfThenElseTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderIfThenElseTests.cs
index f41efd5c..c73c3a56 100644
--- a/GFramework.Game.Tests/Config/YamlConfigLoaderIfThenElseTests.cs
+++ b/GFramework.Game.Tests/Config/YamlConfigLoaderIfThenElseTests.cs
@@ -313,6 +313,47 @@ public sealed class YamlConfigLoaderIfThenElseTests
});
}
+ ///
+ /// 验证缺少 if 却声明 else 时,会在 schema 解析阶段被拒绝。
+ ///
+ [Test]
+ public void LoadAsync_Should_Throw_When_Else_Is_Declared_Without_If()
+ {
+ CreateConfigFile(
+ "monster/slime.yaml",
+ BuildMonsterConfigYaml(
+ """
+ bonus: 1
+ """));
+ CreateSchemaFile(
+ "schemas/monster.schema.json",
+ BuildMonsterSchema(
+ DefaultRewardPropertiesJson,
+ """
+ "else": {
+ "type": "object",
+ "required": ["bonus"],
+ "properties": {
+ "bonus": { "type": "integer" }
+ }
+ }
+ """));
+
+ var loader = CreateMonsterRewardLoader();
+ var registry = CreateRegistry();
+
+ var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry));
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(exception, Is.Not.Null);
+ Assert.That(exception!.Diagnostic.FailureKind, Is.EqualTo(ConfigLoadFailureKind.SchemaUnsupported));
+ Assert.That(exception.Diagnostic.DisplayPath, Is.EqualTo("reward"));
+ Assert.That(exception.Message, Does.Contain("must declare 'if' when using 'then' or 'else'"));
+ Assert.That(registry.Count, Is.EqualTo(0));
+ });
+ }
+
///
/// 验证条件分支不能要求父对象未声明的字段。
///
diff --git a/GFramework.Game/Config/YamlConfigSchemaValidator.ObjectKeywords.cs b/GFramework.Game/Config/YamlConfigSchemaValidator.ObjectKeywords.cs
index e8e5623d..4358d850 100644
--- a/GFramework.Game/Config/YamlConfigSchemaValidator.ObjectKeywords.cs
+++ b/GFramework.Game/Config/YamlConfigSchemaValidator.ObjectKeywords.cs
@@ -441,9 +441,9 @@ internal static partial class YamlConfigSchemaValidator
throw ConfigLoadExceptionFactory.Create(
ConfigLoadFailureKind.SchemaUnsupported,
tableName,
- $"{DescribeObjectSchemaTarget(propertyPath)} in schema file '{schemaPath}' must declare '{keywordName}' as an object-valued schema.",
+ $"{DescribeObjectSchemaTarget(conditionalSchemaPath)} in schema file '{schemaPath}' must declare '{keywordName}' as an object-valued schema.",
schemaPath: schemaPath,
- displayPath: GetDiagnosticPath(propertyPath));
+ displayPath: GetDiagnosticPath(conditionalSchemaPath));
}
ValidateInlineObjectSchemaTargetsAgainstParentObject(
diff --git a/ai-plan/public/ai-first-config-system/todos/ai-first-config-system-tracking.md b/ai-plan/public/ai-first-config-system/todos/ai-first-config-system-tracking.md
index e9115842..0c39d776 100644
--- a/ai-plan/public/ai-first-config-system/todos/ai-first-config-system-tracking.md
+++ b/ai-plan/public/ai-first-config-system/todos/ai-first-config-system-tracking.md
@@ -23,6 +23,8 @@
- 缓解措施:继续为新增共享关键字补齐三端测试覆盖,优先保证 C# Runtime 与 Generator 回归通过,并记录 JS 测试与构建验证
- PR review 信号漂移风险:CodeRabbit 可能把建议折叠在 latest review body,而不是 issue comments
- 缓解措施:`gframework-pr-review` 现已同时解析 latest review body,并输出 declared / parsed 数量以便快速识别解析缺口
+- PR follow-up 残留风险:PR `#262` 最新 review thread 仍有少量 open comments,且 nitpick body 解析仍存在 declared / parsed 缺口
+ - 缓解措施:先以 latest unresolved thread 为准逐条本地核验;已确认并补齐运行时诊断路径与 `else without if` 回归测试,剩余解析缺口单独留在 skill 后续处理
- 非阻塞项回退风险:将 VS Code 功能标为非阻塞但导致主线回退的风险
- 缓解措施:C# 主线补齐新关键字时仍需在 `configValidation.js` 与 `extension.js` 中同步落地,只是不让复杂表单控件阻塞发布
@@ -49,6 +51,12 @@
- `gframework-pr-review` 现已解析 latest CodeRabbit review body 中 folded `Nitpick comments`
- text 输出会显示 `CodeRabbit nitpick comments: X declared, Y parsed`,避免再次静默遗漏
- 已按 5 条 nitpick 更新 VS Code tool hints、shared validation helper,以及对称分支测试覆盖
+- PR `#262` 最新 follow-up:
+ - 最新抓取结果显示仍有 2 条 actionable comments 与 1 条已解析 nitpick 需要本地核验
+ - `SchemaConfigGenerator` 的分支级诊断定位已在当前分支,无需重复修改
+ - `YamlConfigSchemaValidator` 已补齐 `conditionalSchemaPath` 诊断路径,避免 `reward[then]` / `reward[else]` 坏形状误报到父路径
+ - `YamlConfigLoaderIfThenElseTests` 已新增运行时 `else` 缺失 `if` 回归,避免 Runtime / Generator 覆盖漂移
+ - active trace 已将重复的 `### 验证` 标题改为专用 PR follow-up 标题,消除 `MD024`
- 分支同步状态:
- `feat/ai-first-config` 已 rebase 到 `origin/feat/ai-first-config`
- 当前已解决“ahead / behind 同时存在”的分支差异,不再 behind 远端
@@ -74,14 +82,14 @@
- `2026-04-17` 之前的详细实现记录与定向验证命令已归档到历史 tracking / trace
- active 跟踪文件只保留当前恢复点、当前状态和下一步,不再重复堆积已完成阶段的完整历史
- `2026-04-20` 当前恢复点验证:
- - `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py`:通过(`CodeRabbit nitpick comments: 5 declared, 5 parsed`)
+ - `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py`:通过(`CodeRabbit actionable comments: 2`,`CodeRabbit nitpick comments: 2 declared, 1 parsed`)
- `bun run test`(`tools/gframework-config-tool`):通过
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~SchemaConfigGeneratorTests"`:通过
- - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderIfThenElseTests"`:通过
+ - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderIfThenElseTests"`:通过(8 tests;新增 `else without if` 运行时回归)
- `dotnet build GFramework.sln -c Release`:通过(存在仓库既有 analyzer warning,无新增错误)
## 下一步
-1. 先用 `GFramework.Game/Config/YamlConfigSchemaValidator.cs`、`GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs`、`tools/gframework-config-tool/src/configValidation.js` 盘点下一批候选关键字
-2. 优先判断 `oneOf` / `anyOf` 是否存在可接受的 object-focused 子集;如果仍会引入生成类型形状漂移,就直接跳过
-3. 在下一批关键字之前,保持“Runtime / Generator / Tooling 三端一致且不做属性合并”这条筛选线
+1. 提交并推送当前 PR `#262` follow-up 修复后,重新抓取一次 PR review,确认 open thread 是否已清空或只剩 parser gap
+2. 若 PR review 已收口,再回到 `GFramework.Game/Config/YamlConfigSchemaValidator.cs`、`GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs`、`tools/gframework-config-tool/src/configValidation.js` 盘点下一批候选关键字
+3. 优先判断 `oneOf` / `anyOf` 是否存在可接受的 object-focused 子集;若仍会引入生成类型形状漂移,就直接跳过
diff --git a/ai-plan/public/ai-first-config-system/traces/ai-first-config-system-trace.md b/ai-plan/public/ai-first-config-system/traces/ai-first-config-system-trace.md
index 213a8a2e..4693c3c0 100644
--- a/ai-plan/public/ai-first-config-system/traces/ai-first-config-system-trace.md
+++ b/ai-plan/public/ai-first-config-system/traces/ai-first-config-system-trace.md
@@ -78,20 +78,24 @@
- rebase 过程中 Git 跳过了远端已具备的 commit `76488dc`
- 当前分支已不再 behind 远端,仅保留本地领先提交
-### 验证
+### PR `#262` review follow-up 验证
- 2026-04-20:`python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py`
- 结果:通过
- - 备注:输出 `CodeRabbit nitpick comments: 5 declared, 5 parsed`
+ - 备注:输出 `CodeRabbit actionable comments: 2`、`CodeRabbit nitpick comments: 2 declared, 1 parsed`,并暴露剩余 review follow-up
+- 2026-04-20:运行时条件分支 follow-up
+ - 结果:已补齐
+ - 备注:`YamlConfigSchemaValidator` 现对非 object 的 `if` / `then` / `else` 使用分支级诊断路径;运行时测试新增 `else` 缺失 `if` 回归
- 2026-04-20:`bun run test`(`tools/gframework-config-tool`)
- 结果:通过(118 tests)
- 2026-04-20:`dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~SchemaConfigGeneratorTests"`
- 结果:通过(46 tests)
- 2026-04-20:`dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderIfThenElseTests"`
- - 结果:通过(7 tests)
+ - 结果:通过(8 tests)
+ - 备注:新增 `LoadAsync_Should_Throw_When_Else_Is_Declared_Without_If` 后,运行时回归覆盖保持对称
- 2026-04-20:`dotnet build GFramework.sln -c Release`
- - 结果:通过
- - 备注:存在仓库既有 analyzer warning,但无新增错误
+ - 结果:通过(历史记录)
+ - 备注:存在仓库既有 analyzer warning,但无新增错误;本轮只需重新验证受影响测试切片
### 下一步