Compare commits

...

13 Commits

Author SHA1 Message Date
gewuyou
1e5ca14620 fix(godot): 修复场景行为格式问题
- 修复 SceneBehaviorBase 中暂停、恢复、卸载方法的缩进,消除 PR review 指向的格式问题
- 更新 analyzer-warning-reduction 的 tracking 与 trace,记录 PR #286 latest-head review 跟进和验证结果
- 补充 GFramework.Godot 的 Release build 与 dotnet format verify 结论,保留后续 warning reduction 恢复点
2026-04-24 22:18:23 +08:00
gewuyou
2b70734357 fix(game-tests): 清理持久化测试残余告警
- 修复 PersistenceTests 中统一设置仓库失败场景测试的剩余 ConfigureAwait 告警\n- 验证 PersistenceTests 不再出现在非增量 GFramework.Game.Tests 构建告警输出中\n- 更新 analyzer warning reduction 的 tracking 与 trace,记录 RP-057 验证结果和当前分支体积
2026-04-24 20:05:14 +08:00
gewuyou
56ed66976b fix(game-tests): 清理生成配置消费者测试告警
- 修复 GeneratedConfigConsumerIntegrationTests 的 raw string 缩进和方法边界,恢复编译通过\n- 重构生成配置消费者集成测试的断言辅助方法,清理该文件剩余 warning\n- 更新 analyzer warning reduction 的 tracking 与 trace,记录 RP-056 验证结果和当前分支体积
2026-04-24 19:31:37 +08:00
gewuyou
5aefd77ad0 fix(game-tests): 收敛配置与序列化测试告警
- 修复架构配置、启动流程与序列化测试中的异步等待和 invariant 解析告警

- 补充 AllOf 与 persistence 测试的残余状态校验与 ConfigureAwait 修正,继续压低 Game.Tests warning

- 更新 analyzer-warning-reduction 跟踪与 trace,纠正 RP-054 的 stop-condition 口径并记录 RP-055 指标
2026-04-24 18:29:17 +08:00
gewuyou
36507bbc52 fix(game-tests): 收敛 Game.Tests 测试告警
- 修复多组 YAML 与 persistence 测试中的 ConfigureAwait 使用与状态校验,清理低风险 analyzer 告警

- 重构 PersistenceTestUtilities 为单类型文件,消除测试辅助模型的文件命名告警

- 更新 analyzer-warning-reduction 跟踪与 trace,记录 RP-054 批次结果与 75 文件阈值停止点
2026-04-24 18:17:21 +08:00
gewuyou
6ff07ad3d9 fix(godot): 清理 Godot 模块与测试项目告警
- 优化 GodotYamlConfigEnvironment 目录枚举逻辑,拆分 helper 以消除 MA0051

- 修复 Godot 生命周期 await 的上下文声明,显式保留主线程同步上下文

- 更新 Godot.Tests 异步断言与字符串 comparer,用例项目构建收敛到 0 warning(s)

- 补充 analyzer-warning-reduction 跟踪与 trace,记录 RP-053 的批次结果与验证
2026-04-24 17:04:53 +08:00
gewuyou
63f563cd49
Merge pull request #283 from GeWuYou/fix/analyzer-warning-reduction-batch
Fix/analyzer warning reduction batch
2026-04-24 16:51:03 +08:00
gewuyou
2187f179c3 fix(pr-review): 修复设置快照比较器契约
- 修复 UnifiedSettingsFile 与 UnifiedSettingsDataRepository 的 comparer 契约,在无法恢复原比较器时显式回退到 StringComparer.Ordinal
- 统一 AutoRegisterExportedCollectionsGeneratorTests 中剩余的 RunAsync 异步等待写法,并补齐 ConfigureAwait(false)
- 更新 analyzer-warning-reduction 跟踪文档,记录 PR follow-up 的验证结果与恢复点
2026-04-24 16:39:25 +08:00
gewuyou
a8447a68a4
Merge pull request #282 from GeWuYou/docs/sdk-update-documentation
Docs/sdk update documentation
2026-04-24 12:53:39 +08:00
gewuyou
5b9c879320 docs(pr-review): 收口文档评审遗留问题
- 更新 active tracking / trace 为当前恢复入口,并归档 RP-023 到 RP-025 的阶段细节
- 修复 zh-CN context 页面标题本地化与 troubleshooting 绝对链接后缀不一致问题
- 补充 PR #282 follow-up 的验证记录并确认 docs 站点构建通过
2026-04-24 11:44:22 +08:00
gewuyou
982249151e docs(zh-cn): 补齐文档元数据缺口
- 补齐 docs/zh-CN 多个栏目页面的 title 与 description frontmatter,清空完全缺 frontmatter 的历史页面
- 修复 multiplayer、source-generators 与 troubleshooting 触达页面暴露的 Markdown 结构和站内链接问题
- 更新 documentation-full-coverage-governance 的恢复点、验证结果与下一批 metadata 热点
2026-04-24 09:19:36 +08:00
gewuyou
5b7c555472 docs(core): 补齐 Core 文档 frontmatter
- 补充 docs/zh-CN/core 目录 21 个专题页的 frontmatter 与 description

- 修复 core/ioc.md 的 ReaderWriterLockSlim 坏链和 core/state-management.md 的站内链接

- 更新 documentation-full-coverage-governance tracking 与 trace,记录本轮批处理指标、验证结果和停止点
2026-04-24 08:38:51 +08:00
gewuyou
66395739dc docs(governance): 收口公开文档治理口径
- 更新 AGENTS.md、DOCUMENTATION_STANDARDS.md 与 gframework-doc-refresh 规则,禁止在公开文档中暴露 inventory、覆盖基线与恢复点

- 修复 Core、Game、Ecs.Arch 与 abstractions 栏目中的 XML 覆盖表述,改为面向使用者的源码阅读入口

- 补充 contributor 页面 frontmatter,并统一 landing page、验证基线等内部术语为读者导向表达

- 更新 documentation-full-coverage-governance tracking 与 trace,记录 -batch-boot 75 基线、验证结果和下一步
2026-04-24 08:31:23 +08:00
93 changed files with 1409 additions and 584 deletions

View File

@ -40,6 +40,18 @@
如果 `ai-libs/` 与当前源码或测试冲突,以当前仓库实现为准,并在文档里写明迁移或兼容边界。
## 公开文档边界
- `README.md``docs/**` 面向框架使用者,不面向治理执行者。
- 不要把 inventory、覆盖基线、恢复点、批处理阈值、review 线程、待补审计波次等内部治理信息写进公开页面。
- XML、README、测试与 `ai-libs/` 证据可以驱动文档决策,但公开页面只能输出读者真正需要的内容:
- 模块边界
- 最小接入路径
- 推荐阅读顺序
- 源码 / XML / API 的入口提示
- 如果确实需要保留治理基线、盘点结果或后续治理计划,把它写到 `ai-plan/**` 或其他 contributor-only 记录里。
- 当页面需要引用 XML 文档时,写“应优先查看哪些类型、命名空间或契约,以及为什么”,不要写覆盖数量、盘点日期或“已覆盖 / 未覆盖”状态。
## Markdown 规则
### 泛型与 HTML 转义
@ -83,6 +95,12 @@ description: 1-2 句话描述当前页面解决什么问题
3. 再补 API reference
4. 最后才补教程
## 用户页检查点
- 用户读完页面后,应知道怎么采用、该看哪几个入口,而不是知道当前治理批次做到哪一轮。
- 如果一段内容删掉日期、数量、基线、治理术语后就失去价值,它大概率不该出现在公开文档里。
- 表格优先表达“何时看什么、解决什么问题”,不要表达“当前盘点覆盖到哪里”。
## 验证清单
- [ ] frontmatter 正确

View File

@ -173,6 +173,10 @@ Use this exact priority:
- Prefer correcting the adoption path over expanding page count.
- Do not copy wording from outdated docs just to keep page volume.
- Public docs must stay reader-facing. Do not write inventory, coverage baseline, recovery-point, batch-metric, review
backlog, or audit-wave wording into `README.md` or `docs/**`.
- If XML or audit evidence is relevant, translate it into reader guidance such as “which types to inspect first” or
“which entry points define the contract”, instead of exposing counts, dates, or governance status.
- Escape generics outside code blocks.
- Keep internal links real and current.
- Mark code blocks with explicit languages.

View File

@ -320,6 +320,11 @@ bash scripts/validate-csharp-naming.sh
- Update the relevant `README.md` or `docs/` page when behavior, setup steps, architecture guidance, or user-facing
examples change.
- Public documentation under `README.md` and `docs/**` MUST stay reader-facing. Do not publish governance-only content
such as inventory tables, coverage baselines, review queues, batch metrics, recovery points, trace summaries, or
“this still needs a later audit wave” notes in those user-facing pages.
- Governance-only material such as XML audit snapshots, documentation remediation baselines, backlog status, and
recovery metadata belongs in `ai-plan/**` or other contributor-only artifacts, not in public docs.
- Treat `ai-libs/` as a read-only third-party source reference area.
- Code under `ai-libs/**` exists for comparison, tracing, design study, and behavior verification; do not modify it
unless the user explicitly asks to sync or update that third-party snapshot.
@ -331,6 +336,9 @@ bash scripts/validate-csharp-naming.sh
- The main documentation site lives under `docs/`, with Chinese content under `docs/zh-CN/`.
- Keep code samples, package names, and command examples aligned with the current repository state.
- Prefer documenting behavior and design intent, not only API surface.
- When a public page references XML docs or API coverage, convert that evidence into reader-facing guidance: explain
which types, namespaces, or entry points readers should inspect and why, instead of exposing audit counts or
governance terminology.
- When a feature is added, removed, renamed, or substantially refactored, contributors MUST update or create the
corresponding user-facing integration documentation in `docs/zh-CN/` in the same change.
- For integration-oriented features such as the AI-First config system, documentation MUST cover:

View File

@ -39,7 +39,7 @@ public class ArchitectureConfigIntegrationTests
try
{
architecture = new ConsumerArchitecture(rootPath);
await architecture.InitializeAsync();
await architecture.InitializeAsync().ConfigureAwait(false);
initialized = true;
var table = architecture.MonsterTable;
@ -63,7 +63,7 @@ public class ArchitectureConfigIntegrationTests
{
if (architecture is not null && initialized)
{
await architecture.DestroyAsync();
await architecture.DestroyAsync().ConfigureAwait(false);
}
DeleteDirectoryIfExists(rootPath);
@ -83,7 +83,7 @@ public class ArchitectureConfigIntegrationTests
try
{
architecture = new ConsumerArchitecture(rootPath);
await architecture.InitializeAsync();
await architecture.InitializeAsync().ConfigureAwait(false);
initialized = true;
Assert.Multiple(() =>
@ -97,7 +97,7 @@ public class ArchitectureConfigIntegrationTests
{
if (architecture is not null && initialized)
{
await architecture.DestroyAsync();
await architecture.DestroyAsync().ConfigureAwait(false);
}
DeleteDirectoryIfExists(rootPath);
@ -119,16 +119,16 @@ public class ArchitectureConfigIntegrationTests
var module = CreateModule(rootPath);
firstArchitecture = new ModuleOnlyArchitecture(module);
await firstArchitecture.InitializeAsync();
await firstArchitecture.InitializeAsync().ConfigureAwait(false);
var wasInitializedBeforeDestroy = module.IsInitialized;
await firstArchitecture.DestroyAsync();
await firstArchitecture.DestroyAsync().ConfigureAwait(false);
firstDestroyed = true;
firstArchitecture = null;
GameContext.Clear();
var secondArchitecture = new ModuleOnlyArchitecture(module);
var exception =
Assert.ThrowsAsync<InvalidOperationException>(async () => await secondArchitecture.InitializeAsync());
Assert.ThrowsAsync<InvalidOperationException>(async () => await secondArchitecture.InitializeAsync().ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -141,7 +141,7 @@ public class ArchitectureConfigIntegrationTests
{
if (firstArchitecture is not null && !firstDestroyed)
{
await firstArchitecture.DestroyAsync();
await firstArchitecture.DestroyAsync().ConfigureAwait(false);
}
DeleteDirectoryIfExists(rootPath);
@ -203,7 +203,7 @@ public class ArchitectureConfigIntegrationTests
var module = CreateModule(rootPath);
readyArchitecture = new ReadyOnlyArchitecture();
await readyArchitecture.InitializeAsync();
await readyArchitecture.InitializeAsync().ConfigureAwait(false);
readyArchitectureInitialized = true;
var exception = Assert.Throws<InvalidOperationException>(() => readyArchitecture.InstallModule(module));
@ -216,13 +216,13 @@ public class ArchitectureConfigIntegrationTests
Assert.That(module.IsInitialized, Is.False);
});
await readyArchitecture.DestroyAsync();
await readyArchitecture.DestroyAsync().ConfigureAwait(false);
readyArchitectureInitialized = false;
readyArchitecture = null;
GameContext.Clear();
retryArchitecture = new ModuleOnlyArchitecture(module);
await retryArchitecture.InitializeAsync();
await retryArchitecture.InitializeAsync().ConfigureAwait(false);
retryArchitectureInitialized = true;
Assert.Multiple(() =>
@ -235,12 +235,12 @@ public class ArchitectureConfigIntegrationTests
{
if (retryArchitecture is not null && retryArchitectureInitialized)
{
await retryArchitecture.DestroyAsync();
await retryArchitecture.DestroyAsync().ConfigureAwait(false);
}
if (readyArchitecture is not null && readyArchitectureInitialized)
{
await readyArchitecture.DestroyAsync();
await readyArchitecture.DestroyAsync().ConfigureAwait(false);
}
DeleteDirectoryIfExists(rootPath);

View File

@ -50,7 +50,7 @@ public class GameConfigBootstrapTests
var registry = new ConfigRegistry();
using var bootstrap = CreateBootstrap(registry);
await bootstrap.InitializeAsync();
await bootstrap.InitializeAsync().ConfigureAwait(false);
var monsterTable = registry.GetMonsterTable();
@ -74,7 +74,7 @@ public class GameConfigBootstrapTests
CreateMonsterFiles();
using var bootstrap = CreateBootstrap();
await bootstrap.InitializeAsync();
await bootstrap.InitializeAsync().ConfigureAwait(false);
var reloadTaskSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
bootstrap.StartHotReload(
@ -95,7 +95,7 @@ public class GameConfigBootstrapTests
faction: dungeon
""");
var tableName = await WaitForTaskWithinAsync(reloadTaskSource.Task, TimeSpan.FromSeconds(5));
var tableName = await WaitForTaskWithinAsync(reloadTaskSource.Task, TimeSpan.FromSeconds(5)).ConfigureAwait(false);
var monsterTable = bootstrap.Registry.GetMonsterTable();
Assert.Multiple(() =>
@ -163,11 +163,11 @@ public class GameConfigBootstrapTests
Is.True,
"The first initialization attempt did not reach the guarded lifecycle section.");
var secondCallerException = Assert.ThrowsAsync<InvalidOperationException>(async () => await bootstrap.InitializeAsync());
var secondCallerException = Assert.ThrowsAsync<InvalidOperationException>(async () => await bootstrap.InitializeAsync().ConfigureAwait(false));
continueInitialization.Set();
Assert.DoesNotThrowAsync(async () => await firstInitializeTask);
Assert.DoesNotThrowAsync(async () => await firstInitializeTask.ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -202,7 +202,7 @@ public class GameConfigBootstrapTests
})
});
var exception = Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await bootstrap.InitializeAsync());
var exception = Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await bootstrap.InitializeAsync().ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -311,12 +311,12 @@ public class GameConfigBootstrapTests
/// <returns>任务结果。</returns>
private static async Task<T> WaitForTaskWithinAsync<T>(Task<T> task, TimeSpan timeout)
{
var completedTask = await Task.WhenAny(task, Task.Delay(timeout));
var completedTask = await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false);
if (!ReferenceEquals(completedTask, task))
{
Assert.Fail($"Timed out after {timeout} while waiting for file watcher notification.");
}
return await task;
return await task.ConfigureAwait(false);
}
}

View File

@ -50,65 +50,12 @@ public class GeneratedConfigConsumerIntegrationTests
var loader = new YamlConfigLoader(_rootPath)
.RegisterAllGeneratedConfigTables();
await loader.LoadAsync(registry);
await loader.LoadAsync(registry).ConfigureAwait(false);
var monsterTable = registry.GetMonsterTable();
var dungeonMonsters = monsterTable.FindByFaction("dungeon");
var itemTable = registry.GetItemTable();
Assert.Multiple(() =>
{
Assert.That(
GeneratedConfigCatalog.Tables.Select(static metadata => metadata.TableName),
Is.SupersetOf(new[] { "item", "monster" }));
Assert.That(GeneratedConfigCatalog.TryGetByTableName("item", out var itemCatalogEntry), Is.True);
Assert.That(itemCatalogEntry.ConfigDomain, Is.EqualTo("item"));
Assert.That(itemCatalogEntry.ConfigRelativePath, Is.EqualTo("item"));
Assert.That(itemCatalogEntry.SchemaRelativePath, Is.EqualTo("schemas/item.schema.json"));
Assert.That(GeneratedConfigCatalog.TryGetByTableName("monster", out var catalogEntry), Is.True);
Assert.That(catalogEntry.ConfigDomain, Is.EqualTo("monster"));
Assert.That(catalogEntry.ConfigRelativePath, Is.EqualTo("monster"));
Assert.That(catalogEntry.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json"));
Assert.That(ItemConfigBindings.ConfigDomain, Is.EqualTo("item"));
Assert.That(ItemConfigBindings.Metadata.TableName, Is.EqualTo("item"));
Assert.That(MonsterConfigBindings.ConfigDomain, Is.EqualTo("monster"));
Assert.That(MonsterConfigBindings.TableName, Is.EqualTo("monster"));
Assert.That(MonsterConfigBindings.ConfigRelativePath, Is.EqualTo("monster"));
Assert.That(MonsterConfigBindings.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json"));
Assert.That(MonsterConfigBindings.Metadata.ConfigDomain, Is.EqualTo(MonsterConfigBindings.ConfigDomain));
Assert.That(MonsterConfigBindings.Metadata.TableName, Is.EqualTo(MonsterConfigBindings.TableName));
Assert.That(MonsterConfigBindings.Metadata.ConfigRelativePath,
Is.EqualTo(MonsterConfigBindings.ConfigRelativePath));
Assert.That(MonsterConfigBindings.Metadata.SchemaRelativePath,
Is.EqualTo(MonsterConfigBindings.SchemaRelativePath));
Assert.That(MonsterConfigBindings.References.All, Is.Empty);
Assert.That(MonsterConfigBindings.References.TryGetByDisplayPath("dropItems", out _), Is.False);
Assert.That(monsterTable.Count, Is.EqualTo(2));
Assert.That(monsterTable.Get(1).Name, Is.EqualTo("Slime"));
Assert.That(monsterTable.Get(2).Hp, Is.EqualTo(30));
Assert.That(monsterTable.FindByName("Slime").Select(static config => config.Id), Is.EqualTo(new[] { 1 }));
Assert.That(dungeonMonsters.Select(static config => config.Name),
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
Assert.That(monsterTable.TryFindFirstByName("Goblin", out var goblin), Is.True);
Assert.That(goblin, Is.Not.Null);
Assert.That(goblin!.Id, Is.EqualTo(2));
Assert.That(monsterTable.TryFindFirstByFaction("dungeon", out var firstDungeonMonster), Is.True);
Assert.That(firstDungeonMonster, Is.Not.Null);
Assert.That(firstDungeonMonster!.Name, Is.AnyOf("Slime", "Goblin"));
Assert.That(monsterTable.TryFindFirstByFaction("forest", out var missingMonster), Is.False);
Assert.That(missingMonster, Is.Null);
Assert.That(registry.TryGetMonsterTable(out var generatedTable), Is.True);
Assert.That(generatedTable, Is.Not.Null);
Assert.That(generatedTable!.All().Select(static config => config.Name),
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
Assert.That(itemTable.Count, Is.EqualTo(2));
Assert.That(itemTable.Get("potion").Name, Is.EqualTo("Potion"));
Assert.That(itemTable.FindByCategory("consumable").Select(static config => config.Id),
Is.EquivalentTo(new[] { "potion", "ether" }));
Assert.That(registry.TryGetItemTable(out var generatedItemTable), Is.True);
Assert.That(generatedItemTable, Is.Not.Null);
Assert.That(generatedItemTable!.Get("ether").Name, Is.EqualTo("Ether"));
});
AssertGeneratedBindingsLoadResults(registry, monsterTable, itemTable);
}
/// <summary>
@ -181,7 +128,7 @@ public class GeneratedConfigConsumerIntegrationTests
{
IncludedConfigDomains = new[] { MonsterConfigBindings.ConfigDomain }
});
await domainLoader.LoadAsync(domainRegistry);
await domainLoader.LoadAsync(domainRegistry).ConfigureAwait(false);
var tableNameRegistry = new ConfigRegistry();
var tableNameLoader = new YamlConfigLoader(_rootPath)
@ -190,7 +137,7 @@ public class GeneratedConfigConsumerIntegrationTests
{
IncludedTableNames = new[] { ItemConfigBindings.TableName }
});
await tableNameLoader.LoadAsync(tableNameRegistry);
await tableNameLoader.LoadAsync(tableNameRegistry).ConfigureAwait(false);
var emptyAllowListRegistry = new ConfigRegistry();
var emptyAllowListLoader = new YamlConfigLoader(_rootPath)
@ -200,7 +147,7 @@ public class GeneratedConfigConsumerIntegrationTests
IncludedConfigDomains = Array.Empty<string>(),
IncludedTableNames = Array.Empty<string>()
});
await emptyAllowListLoader.LoadAsync(emptyAllowListRegistry);
await emptyAllowListLoader.LoadAsync(emptyAllowListRegistry).ConfigureAwait(false);
var monsterDomain = MonsterConfigBindings.ConfigDomain;
var predicateRegistry = new ConfigRegistry();
@ -211,28 +158,13 @@ public class GeneratedConfigConsumerIntegrationTests
TableFilter = metadata =>
string.Equals(metadata.ConfigDomain, monsterDomain, StringComparison.Ordinal)
});
await predicateLoader.LoadAsync(predicateRegistry);
await predicateLoader.LoadAsync(predicateRegistry).ConfigureAwait(false);
Assert.Multiple(() =>
{
Assert.That(emptyAllowListRegistry.TryGetMonsterTable(out var emptyAllowListMonsterTable), Is.True);
Assert.That(emptyAllowListMonsterTable, Is.Not.Null);
Assert.That(emptyAllowListRegistry.TryGetItemTable(out var emptyAllowListItemTable), Is.True);
Assert.That(emptyAllowListItemTable, Is.Not.Null);
Assert.That(domainRegistry.TryGetMonsterTable(out var domainMonsterTable), Is.True);
Assert.That(domainMonsterTable, Is.Not.Null);
Assert.That(domainRegistry.TryGetItemTable(out _), Is.False);
Assert.That(tableNameRegistry.TryGetMonsterTable(out _), Is.False);
Assert.That(tableNameRegistry.TryGetItemTable(out var tableNameItemTable), Is.True);
Assert.That(tableNameItemTable, Is.Not.Null);
Assert.That(tableNameItemTable!.Get("potion").Name, Is.EqualTo("Potion"));
Assert.That(predicateRegistry.TryGetMonsterTable(out var predicateMonsterTable), Is.True);
Assert.That(predicateMonsterTable, Is.Not.Null);
Assert.That(predicateRegistry.TryGetItemTable(out _), Is.False);
});
AssertGeneratedRegistrationFilteringResults(
domainRegistry,
tableNameRegistry,
emptyAllowListRegistry,
predicateRegistry);
}
/// <summary>
@ -270,7 +202,7 @@ public class GeneratedConfigConsumerIntegrationTests
MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", yaml));
Assert.DoesNotThrowAsync(async () =>
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", yaml));
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", yaml).ConfigureAwait(false));
var invalidYaml = """
id: 3
@ -282,7 +214,7 @@ public class GeneratedConfigConsumerIntegrationTests
var exception = Assert.Throws<ConfigLoadException>(() =>
MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", invalidYaml));
var asyncException = Assert.ThrowsAsync<ConfigLoadException>(async () =>
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", invalidYaml));
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", invalidYaml).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -367,6 +299,138 @@ public class GeneratedConfigConsumerIntegrationTests
""");
}
/// <summary>
/// 统一断言生成绑定加载后的目录元数据、查询入口与强类型表包装结果,
/// 以便缩短端到端测试主体并降低分析器对方法长度的告警。
/// </summary>
private static void AssertGeneratedBindingsLoadResults(
ConfigRegistry registry,
MonsterTable monsterTable,
ItemTable itemTable)
{
AssertGeneratedCatalogMetadata();
AssertGeneratedMonsterTableResults(registry, monsterTable);
AssertGeneratedItemTableResults(registry, itemTable);
}
/// <summary>
/// 断言消费者项目的生成目录元数据与静态绑定常量保持一致。
/// </summary>
private static void AssertGeneratedCatalogMetadata()
{
Assert.Multiple(() =>
{
Assert.That(
GeneratedConfigCatalog.Tables.Select(static metadata => metadata.TableName),
Is.SupersetOf(new[] { "item", "monster" }));
Assert.That(GeneratedConfigCatalog.TryGetByTableName("item", out var itemCatalogEntry), Is.True);
Assert.That(itemCatalogEntry.ConfigDomain, Is.EqualTo("item"));
Assert.That(itemCatalogEntry.ConfigRelativePath, Is.EqualTo("item"));
Assert.That(itemCatalogEntry.SchemaRelativePath, Is.EqualTo("schemas/item.schema.json"));
Assert.That(GeneratedConfigCatalog.TryGetByTableName("monster", out var catalogEntry), Is.True);
Assert.That(catalogEntry.ConfigDomain, Is.EqualTo("monster"));
Assert.That(catalogEntry.ConfigRelativePath, Is.EqualTo("monster"));
Assert.That(catalogEntry.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json"));
Assert.That(ItemConfigBindings.ConfigDomain, Is.EqualTo("item"));
Assert.That(ItemConfigBindings.Metadata.TableName, Is.EqualTo("item"));
Assert.That(MonsterConfigBindings.ConfigDomain, Is.EqualTo("monster"));
Assert.That(MonsterConfigBindings.TableName, Is.EqualTo("monster"));
Assert.That(MonsterConfigBindings.ConfigRelativePath, Is.EqualTo("monster"));
Assert.That(MonsterConfigBindings.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json"));
Assert.That(MonsterConfigBindings.Metadata.ConfigDomain, Is.EqualTo(MonsterConfigBindings.ConfigDomain));
Assert.That(MonsterConfigBindings.Metadata.TableName, Is.EqualTo(MonsterConfigBindings.TableName));
Assert.That(MonsterConfigBindings.Metadata.ConfigRelativePath,
Is.EqualTo(MonsterConfigBindings.ConfigRelativePath));
Assert.That(MonsterConfigBindings.Metadata.SchemaRelativePath,
Is.EqualTo(MonsterConfigBindings.SchemaRelativePath));
Assert.That(MonsterConfigBindings.References.All, Is.Empty);
Assert.That(MonsterConfigBindings.References.TryGetByDisplayPath("dropItems", out _), Is.False);
});
}
/// <summary>
/// 断言 monster 绑定在注册表中的查询辅助、索引查询与强类型访问入口都可用。
/// </summary>
private static void AssertGeneratedMonsterTableResults(
ConfigRegistry registry,
MonsterTable monsterTable)
{
var dungeonMonsters = monsterTable.FindByFaction("dungeon");
Assert.Multiple(() =>
{
Assert.That(monsterTable.Count, Is.EqualTo(2));
Assert.That(monsterTable.Get(1).Name, Is.EqualTo("Slime"));
Assert.That(monsterTable.Get(2).Hp, Is.EqualTo(30));
Assert.That(monsterTable.FindByName("Slime").Select(static config => config.Id), Is.EqualTo(new[] { 1 }));
Assert.That(dungeonMonsters.Select(static config => config.Name),
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
Assert.That(monsterTable.TryFindFirstByName("Goblin", out var goblin), Is.True);
Assert.That(goblin, Is.Not.Null);
Assert.That(goblin!.Id, Is.EqualTo(2));
Assert.That(monsterTable.TryFindFirstByFaction("dungeon", out var firstDungeonMonster), Is.True);
Assert.That(firstDungeonMonster, Is.Not.Null);
Assert.That(firstDungeonMonster!.Name, Is.AnyOf("Slime", "Goblin"));
Assert.That(monsterTable.TryFindFirstByFaction("forest", out var missingMonster), Is.False);
Assert.That(missingMonster, Is.Null);
Assert.That(registry.TryGetMonsterTable(out var generatedTable), Is.True);
Assert.That(generatedTable, Is.Not.Null);
Assert.That(generatedTable!.All().Select(static config => config.Name),
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
});
}
/// <summary>
/// 断言 item 绑定的强类型表包装与按分类查询在聚合注册路径下可正常工作。
/// </summary>
private static void AssertGeneratedItemTableResults(
ConfigRegistry registry,
ItemTable itemTable)
{
Assert.Multiple(() =>
{
Assert.That(itemTable.Count, Is.EqualTo(2));
Assert.That(itemTable.Get("potion").Name, Is.EqualTo("Potion"));
Assert.That(itemTable.FindByCategory("consumable").Select(static config => config.Id),
Is.EquivalentTo(new[] { "potion", "ether" }));
Assert.That(registry.TryGetItemTable(out var generatedItemTable), Is.True);
Assert.That(generatedItemTable, Is.Not.Null);
Assert.That(generatedItemTable!.Get("ether").Name, Is.EqualTo("Ether"));
});
}
/// <summary>
/// 汇总断言不同聚合注册筛选条件下的装载结果,
/// 让测试主体聚焦于注册参数本身而不是展开大量重复断言。
/// </summary>
private static void AssertGeneratedRegistrationFilteringResults(
ConfigRegistry domainRegistry,
ConfigRegistry tableNameRegistry,
ConfigRegistry emptyAllowListRegistry,
ConfigRegistry predicateRegistry)
{
Assert.Multiple(() =>
{
Assert.That(emptyAllowListRegistry.TryGetMonsterTable(out var emptyAllowListMonsterTable), Is.True);
Assert.That(emptyAllowListMonsterTable, Is.Not.Null);
Assert.That(emptyAllowListRegistry.TryGetItemTable(out var emptyAllowListItemTable), Is.True);
Assert.That(emptyAllowListItemTable, Is.Not.Null);
Assert.That(domainRegistry.TryGetMonsterTable(out var domainMonsterTable), Is.True);
Assert.That(domainMonsterTable, Is.Not.Null);
Assert.That(domainRegistry.TryGetItemTable(out _), Is.False);
Assert.That(tableNameRegistry.TryGetMonsterTable(out _), Is.False);
Assert.That(tableNameRegistry.TryGetItemTable(out var tableNameItemTable), Is.True);
Assert.That(tableNameItemTable, Is.Not.Null);
Assert.That(tableNameItemTable!.Get("potion").Name, Is.EqualTo("Potion"));
Assert.That(predicateRegistry.TryGetMonsterTable(out var predicateMonsterTable), Is.True);
Assert.That(predicateMonsterTable, Is.Not.Null);
Assert.That(predicateRegistry.TryGetItemTable(out _), Is.False);
});
}
/// <summary>
/// 在临时消费者目录中创建 item schema 与 YAML 测试数据,用于验证多表聚合注册和筛选行为。
/// </summary>

View File

@ -90,7 +90,7 @@ public sealed class YamlConfigLoaderAllOfTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -124,7 +124,7 @@ public sealed class YamlConfigLoaderAllOfTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
await loader.LoadAsync(registry);
await loader.LoadAsync(registry).ConfigureAwait(false);
var table = registry.GetTable<int, MonsterAllOfConfigStub>("monster");
var reward = table.Get(1).Reward;
@ -165,7 +165,7 @@ public sealed class YamlConfigLoaderAllOfTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -210,7 +210,10 @@ public sealed class YamlConfigLoaderAllOfTests
}
""");
ArgumentNullException.ThrowIfNull(_rootPath);
if (_rootPath is null)
{
throw new InvalidOperationException("Root path is not initialized.");
}
var loader = new YamlConfigLoader(_rootPath)
.RegisterTable<int, MonsterTagConfigStub>(
@ -220,7 +223,7 @@ public sealed class YamlConfigLoaderAllOfTests
static config => config.Id);
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -255,7 +258,7 @@ public sealed class YamlConfigLoaderAllOfTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -295,7 +298,7 @@ public sealed class YamlConfigLoaderAllOfTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -335,7 +338,7 @@ public sealed class YamlConfigLoaderAllOfTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -375,7 +378,7 @@ public sealed class YamlConfigLoaderAllOfTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -415,7 +418,7 @@ public sealed class YamlConfigLoaderAllOfTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -455,7 +458,7 @@ public sealed class YamlConfigLoaderAllOfTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -499,7 +502,7 @@ public sealed class YamlConfigLoaderAllOfTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -519,7 +522,10 @@ public sealed class YamlConfigLoaderAllOfTests
/// <param name="content">要写入的 YAML 或 schema 内容。</param>
private void CreateConfigFile(string relativePath, string content)
{
ArgumentNullException.ThrowIfNull(_rootPath);
if (_rootPath is null)
{
throw new InvalidOperationException("Root path is not initialized.");
}
var filePath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
var directoryPath = Path.GetDirectoryName(filePath);
@ -606,7 +612,10 @@ public sealed class YamlConfigLoaderAllOfTests
/// <returns>已注册测试表与 schema 路径的加载器。</returns>
private YamlConfigLoader CreateMonsterRewardLoader()
{
ArgumentNullException.ThrowIfNull(_rootPath);
if (_rootPath is null)
{
throw new InvalidOperationException("Root path is not initialized.");
}
return new YamlConfigLoader(_rootPath)
.RegisterTable<int, MonsterAllOfConfigStub>(

View File

@ -75,7 +75,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -124,7 +124,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
await loader.LoadAsync(registry);
await loader.LoadAsync(registry).ConfigureAwait(false);
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
Assert.That(table.Count, Is.EqualTo(1));
@ -169,7 +169,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
await loader.LoadAsync(registry);
await loader.LoadAsync(registry).ConfigureAwait(false);
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
var reward = table.Get(1).Reward;
@ -217,7 +217,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -267,7 +267,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
var loader = CreateCaseSensitiveRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -317,7 +317,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{

View File

@ -74,7 +74,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -106,7 +106,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
await loader.LoadAsync(registry);
await loader.LoadAsync(registry).ConfigureAwait(false);
var table = registry.GetTable<int, MonsterDependentSchemasConfigStub>("monster");
Assert.That(table.Count, Is.EqualTo(1));
@ -133,7 +133,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
await loader.LoadAsync(registry);
await loader.LoadAsync(registry).ConfigureAwait(false);
var table = registry.GetTable<int, MonsterDependentSchemasConfigStub>("monster");
var reward = table.Get(1).Reward;
@ -174,7 +174,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -220,7 +220,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -264,7 +264,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -283,7 +283,10 @@ public sealed class YamlConfigLoaderDependentSchemasTests
/// <param name="content">要写入的 YAML 或 schema 内容。</param>
private void CreateConfigFile(string relativePath, string content)
{
ArgumentNullException.ThrowIfNull(_rootPath);
if (_rootPath is null)
{
throw new InvalidOperationException("Root path is not initialized.");
}
var filePath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
var directoryPath = Path.GetDirectoryName(filePath);
@ -353,7 +356,10 @@ public sealed class YamlConfigLoaderDependentSchemasTests
/// <returns>已注册测试表与 schema 路径的加载器。</returns>
private YamlConfigLoader CreateMonsterRewardLoader()
{
ArgumentNullException.ThrowIfNull(_rootPath);
if (_rootPath is null)
{
throw new InvalidOperationException("Root path is not initialized.");
}
return new YamlConfigLoader(_rootPath)
.RegisterTable<int, MonsterDependentSchemasConfigStub>(

View File

@ -75,7 +75,7 @@ public class YamlConfigLoaderEnumTests
var loader = CreateLoader<MonsterRewardConfigStub>();
var registry = new ConfigRegistry();
await loader.LoadAsync(registry);
await loader.LoadAsync(registry).ConfigureAwait(false);
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
Assert.Multiple(() =>
@ -127,7 +127,7 @@ public class YamlConfigLoaderEnumTests
var loader = CreateLoader<MonsterRewardConfigStub>();
var registry = new ConfigRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -176,7 +176,7 @@ public class YamlConfigLoaderEnumTests
var loader = CreateLoader<MonsterDropItemIdsConfigStub>();
var registry = new ConfigRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{

View File

@ -105,7 +105,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -137,7 +137,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
await loader.LoadAsync(registry);
await loader.LoadAsync(registry).ConfigureAwait(false);
var table = registry.GetTable<int, MonsterConditionalConfigStub>("monster");
var reward = table.Get(1).Reward;
@ -170,7 +170,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -202,7 +202,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
await loader.LoadAsync(registry);
await loader.LoadAsync(registry).ConfigureAwait(false);
var table = registry.GetTable<int, MonsterConditionalConfigStub>("monster");
var reward = table.Get(1).Reward;
@ -250,7 +250,10 @@ public sealed class YamlConfigLoaderIfThenElseTests
}
""");
ArgumentNullException.ThrowIfNull(_rootPath);
if (_rootPath is null)
{
throw new InvalidOperationException("Root path is not initialized.");
}
var loader = new YamlConfigLoader(_rootPath)
.RegisterTable<int, MonsterTagConfigStub>(
@ -260,7 +263,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
static config => config.Id);
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -301,7 +304,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -342,7 +345,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -390,7 +393,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -409,7 +412,10 @@ public sealed class YamlConfigLoaderIfThenElseTests
/// <param name="content">配置文件内容。</param>
private void CreateConfigFile(string relativePath, string content)
{
ArgumentNullException.ThrowIfNull(_rootPath);
if (_rootPath is null)
{
throw new InvalidOperationException("Root path is not initialized.");
}
var filePath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
var directoryPath = Path.GetDirectoryName(filePath);
@ -496,7 +502,10 @@ public sealed class YamlConfigLoaderIfThenElseTests
/// <returns>已注册测试表与 schema 路径的加载器。</returns>
private YamlConfigLoader CreateMonsterRewardLoader()
{
ArgumentNullException.ThrowIfNull(_rootPath);
if (_rootPath is null)
{
throw new InvalidOperationException("Root path is not initialized.");
}
return new YamlConfigLoader(_rootPath)
.RegisterTable<int, MonsterConditionalConfigStub>(

View File

@ -70,7 +70,7 @@ public sealed class YamlConfigLoaderNegationTests
var loader = CreateMonsterLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -118,7 +118,7 @@ public sealed class YamlConfigLoaderNegationTests
var loader = CreateMonsterLoader();
var registry = CreateRegistry();
await loader.LoadAsync(registry);
await loader.LoadAsync(registry).ConfigureAwait(false);
var table = registry.GetTable<int, MonsterConfigStub>("monster");
@ -172,7 +172,7 @@ public sealed class YamlConfigLoaderNegationTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -227,7 +227,7 @@ public sealed class YamlConfigLoaderNegationTests
var loader = CreateMonsterRewardLoader();
var registry = CreateRegistry();
await loader.LoadAsync(registry);
await loader.LoadAsync(registry).ConfigureAwait(false);
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
@ -272,7 +272,7 @@ public sealed class YamlConfigLoaderNegationTests
var loader = CreateMonsterLoader();
var registry = CreateRegistry();
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
Assert.Multiple(() =>
{

View File

@ -191,7 +191,10 @@ public sealed class YamlConfigSchemaValidatorTests
string relativePath,
string content)
{
ArgumentNullException.ThrowIfNull(_rootPath);
if (_rootPath is null)
{
throw new InvalidOperationException("Root path is not initialized.");
}
var fullPath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
var directoryPath = Path.GetDirectoryName(fullPath);

View File

@ -132,7 +132,7 @@ public sealed class YamlConfigTextValidatorTests
"monster/generated.yaml",
"""
id: 1
"""));
""").ConfigureAwait(false));
Assert.Multiple(() =>
{

View File

@ -1,114 +0,0 @@
using System;
using GFramework.Game.Abstractions.Data;
using GFramework.Game.Abstractions.Enums;
namespace GFramework.Game.Tests.Data;
/// <summary>
/// 为持久化测试提供稳定的测试数据位置实现。
/// </summary>
internal sealed class TestDataLocation : IDataLocation
{
/// <summary>
/// 初始化测试数据位置。
/// </summary>
/// <param name="key">测试使用的存储键。</param>
/// <param name="kinds">测试使用的存储类型。</param>
/// <param name="namespaceValue">测试使用的命名空间。</param>
/// <param name="metadata">附加测试元数据。</param>
public TestDataLocation(
string key,
StorageKinds kinds = StorageKinds.Local,
string? namespaceValue = null,
IReadOnlyDictionary<string, string>? metadata = null)
{
Key = key;
Kinds = kinds;
Namespace = namespaceValue;
Metadata = metadata;
}
/// <summary>
/// 获取测试数据对应的存储键。
/// </summary>
public string Key { get; }
/// <summary>
/// 获取测试数据使用的存储类型。
/// </summary>
public StorageKinds Kinds { get; }
/// <summary>
/// 获取测试数据使用的命名空间。
/// </summary>
public string? Namespace { get; }
/// <summary>
/// 获取附加到测试位置上的元数据。
/// </summary>
public IReadOnlyDictionary<string, string>? Metadata { get; }
}
/// <summary>
/// 为基础存档仓库测试提供的简单存档模型。
/// </summary>
internal sealed class TestSaveData : IData
{
/// <summary>
/// 获取或设置测试存档中的名称字段。
/// </summary>
public string Name { get; set; } = string.Empty;
}
/// <summary>
/// 为存档迁移测试提供的版本化存档模型。
/// </summary>
internal sealed class TestVersionedSaveData : IVersionedData
{
/// <summary>
/// 获取或设置测试存档中的名称字段。
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 获取或设置测试存档中的等级字段。
/// </summary>
public int Level { get; set; }
/// <summary>
/// 获取或设置测试存档中的经验字段。
/// </summary>
public int Experience { get; set; }
/// <summary>
/// 获取或设置当前测试存档的版本号。
/// </summary>
public int Version { get; set; } = 3;
/// <summary>
/// 获取或设置测试存档的最后修改时间。
/// </summary>
public DateTime LastModified { get; set; } = DateTime.UtcNow;
}
/// <summary>
/// 为通用持久化测试提供的简单数据模型。
/// </summary>
internal sealed class TestSimpleData : IData
{
/// <summary>
/// 获取或设置测试数据中的整数值。
/// </summary>
public int Value { get; set; }
}
/// <summary>
/// 为批量持久化测试提供的另一种数据模型,用于验证运行时类型不会在接口路径上退化。
/// </summary>
internal sealed class TestNamedData : IData
{
/// <summary>
/// 获取或设置测试数据中的名称值。
/// </summary>
public string Name { get; set; } = string.Empty;
}

View File

@ -38,12 +38,12 @@ public class PersistenceTests
using var storage = new FileStorage(root, new JsonSerializer(), ".json");
var saved = new TestSimpleData { Value = 5 };
await storage.WriteAsync("folder/item", saved);
await storage.WriteAsync("folder/item", saved).ConfigureAwait(false);
var loaded = await storage.ReadAsync<TestSimpleData>("folder/item");
var loaded = await storage.ReadAsync<TestSimpleData>("folder/item").ConfigureAwait(false);
Assert.That(loaded.Value, Is.EqualTo(saved.Value));
Assert.ThrowsAsync<ArgumentException>(async () => await storage.WriteAsync("../escape", new TestSimpleData()));
Assert.ThrowsAsync<ArgumentException>(async () => await storage.WriteAsync("../escape", new TestSimpleData()).ConfigureAwait(false));
}
/// <summary>
@ -108,7 +108,7 @@ public class PersistenceTests
.RegisterMigration(new TestSaveMigrationV2ToV3());
var loaded = await repository.LoadAsync(1);
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save");
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save").ConfigureAwait(false);
Assert.Multiple(() =>
{
@ -185,7 +185,7 @@ public class PersistenceTests
var repository = new SaveRepository<TestVersionedSaveData>(storage, config)
.RegisterMigration(new TestSaveMigrationV1ToV2());
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.LoadAsync(1));
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.LoadAsync(1).ConfigureAwait(false));
Assert.That(exception!.Message, Does.Contain("from version 2"));
}
@ -218,8 +218,8 @@ public class PersistenceTests
var repository = new SaveRepository<TestVersionedSaveData>(storage, config)
.RegisterMigration(new TestSaveMigrationV1ToV2ReturningV3());
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.LoadAsync(1));
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save");
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.LoadAsync(1).ConfigureAwait(false));
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save").ConfigureAwait(false);
Assert.Multiple(() =>
{
@ -270,8 +270,8 @@ public class PersistenceTests
repository.RegisterMigration(new TestSaveMigrationV2ToV3());
continueMigration.Set();
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await loadTask);
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save");
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await loadTask.ConfigureAwait(false));
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save").ConfigureAwait(false);
Assert.Multiple(() =>
{
@ -593,13 +593,13 @@ public class PersistenceTests
throwingStorage.ThrowOnWrite = true;
Assert.ThrowsAsync<InvalidOperationException>(
async () => await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 99 }));
async () => await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 99 }).ConfigureAwait(false));
var cachedAfterFailure = await repository.LoadAsync<TestSimpleData>(primaryLocation);
var cachedAfterFailure = await repository.LoadAsync<TestSimpleData>(primaryLocation).ConfigureAwait(false);
Assert.That(cachedAfterFailure.Value, Is.EqualTo(1));
throwingStorage.ThrowOnWrite = false;
await repository.SaveAsync(secondaryLocation, new TestSimpleData { Value = 7 });
await repository.SaveAsync(secondaryLocation, new TestSimpleData { Value = 7 }).ConfigureAwait(false);
using var verifyStorage = new FileStorage(root, new JsonSerializer(), ".json");
var verifyRepository = new UnifiedSettingsDataRepository(
@ -609,8 +609,8 @@ public class PersistenceTests
verifyRepository.RegisterDataType(primaryLocation, typeof(TestSimpleData));
verifyRepository.RegisterDataType(secondaryLocation, typeof(TestSimpleData));
var persistedPrimary = await verifyRepository.LoadAsync<TestSimpleData>(primaryLocation);
var persistedSecondary = await verifyRepository.LoadAsync<TestSimpleData>(secondaryLocation);
var persistedPrimary = await verifyRepository.LoadAsync<TestSimpleData>(primaryLocation).ConfigureAwait(false);
var persistedSecondary = await verifyRepository.LoadAsync<TestSimpleData>(secondaryLocation).ConfigureAwait(false);
Assert.Multiple(() =>
{
@ -655,12 +655,13 @@ public class PersistenceTests
repository.RegisterDataType(secondaryLocation, typeof(TestSimpleData));
throwingStorage.ThrowOnWrite = true;
Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.DeleteAsync(secondaryLocation));
Assert.ThrowsAsync<InvalidOperationException>(
async () => await repository.DeleteAsync(secondaryLocation).ConfigureAwait(false));
Assert.That(await repository.ExistsAsync(secondaryLocation), Is.True);
Assert.That(await repository.ExistsAsync(secondaryLocation).ConfigureAwait(false), Is.True);
throwingStorage.ThrowOnWrite = false;
await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 9 });
await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 9 }).ConfigureAwait(false);
using var verifyStorage = new FileStorage(root, new JsonSerializer(), ".json");
var verifyRepository = new UnifiedSettingsDataRepository(
@ -670,8 +671,8 @@ public class PersistenceTests
verifyRepository.RegisterDataType(primaryLocation, typeof(TestSimpleData));
verifyRepository.RegisterDataType(secondaryLocation, typeof(TestSimpleData));
var persistedPrimary = await verifyRepository.LoadAsync<TestSimpleData>(primaryLocation);
var persistedSecondary = await verifyRepository.LoadAsync<TestSimpleData>(secondaryLocation);
var persistedPrimary = await verifyRepository.LoadAsync<TestSimpleData>(primaryLocation).ConfigureAwait(false);
var persistedSecondary = await verifyRepository.LoadAsync<TestSimpleData>(secondaryLocation).ConfigureAwait(false);
Assert.Multiple(() =>
{

View File

@ -0,0 +1,49 @@
using GFramework.Game.Abstractions.Data;
using GFramework.Game.Abstractions.Enums;
namespace GFramework.Game.Tests.Data;
/// <summary>
/// 为持久化测试提供稳定的测试数据位置实现。
/// </summary>
internal sealed class TestDataLocation : IDataLocation
{
/// <summary>
/// 初始化测试数据位置。
/// </summary>
/// <param name="key">测试使用的存储键。</param>
/// <param name="kinds">测试使用的存储类型。</param>
/// <param name="namespaceValue">测试使用的命名空间。</param>
/// <param name="metadata">附加测试元数据。</param>
public TestDataLocation(
string key,
StorageKinds kinds = StorageKinds.Local,
string? namespaceValue = null,
IReadOnlyDictionary<string, string>? metadata = null)
{
Key = key;
Kinds = kinds;
Namespace = namespaceValue;
Metadata = metadata;
}
/// <summary>
/// 获取测试数据对应的存储键。
/// </summary>
public string Key { get; }
/// <summary>
/// 获取测试数据使用的存储类型。
/// </summary>
public StorageKinds Kinds { get; }
/// <summary>
/// 获取测试数据使用的命名空间。
/// </summary>
public string? Namespace { get; }
/// <summary>
/// 获取附加到测试位置上的元数据。
/// </summary>
public IReadOnlyDictionary<string, string>? Metadata { get; }
}

View File

@ -0,0 +1,14 @@
using GFramework.Game.Abstractions.Data;
namespace GFramework.Game.Tests.Data;
/// <summary>
/// 为批量持久化测试提供的另一种数据模型,用于验证运行时类型不会在接口路径上退化。
/// </summary>
internal sealed class TestNamedData : IData
{
/// <summary>
/// 获取或设置测试数据中的名称值。
/// </summary>
public string Name { get; set; } = string.Empty;
}

View File

@ -0,0 +1,14 @@
using GFramework.Game.Abstractions.Data;
namespace GFramework.Game.Tests.Data;
/// <summary>
/// 为基础存档仓库测试提供的简单存档模型。
/// </summary>
internal sealed class TestSaveData : IData
{
/// <summary>
/// 获取或设置测试存档中的名称字段。
/// </summary>
public string Name { get; set; } = string.Empty;
}

View File

@ -0,0 +1,14 @@
using GFramework.Game.Abstractions.Data;
namespace GFramework.Game.Tests.Data;
/// <summary>
/// 为通用持久化测试提供的简单数据模型。
/// </summary>
internal sealed class TestSimpleData : IData
{
/// <summary>
/// 获取或设置测试数据中的整数值。
/// </summary>
public int Value { get; set; }
}

View File

@ -0,0 +1,35 @@
using System;
using GFramework.Game.Abstractions.Data;
namespace GFramework.Game.Tests.Data;
/// <summary>
/// 为存档迁移测试提供的版本化存档模型。
/// </summary>
internal sealed class TestVersionedSaveData : IVersionedData
{
/// <summary>
/// 获取或设置测试存档中的名称字段。
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 获取或设置测试存档中的等级字段。
/// </summary>
public int Level { get; set; }
/// <summary>
/// 获取或设置测试存档中的经验字段。
/// </summary>
public int Experience { get; set; }
/// <summary>
/// 获取或设置当前测试存档的版本号。
/// </summary>
public int Version { get; set; } = 3;
/// <summary>
/// 获取或设置测试存档的最后修改时间。
/// </summary>
public DateTime LastModified { get; set; } = DateTime.UtcNow;
}

View File

@ -1,3 +1,4 @@
using System.Globalization;
using Newtonsoft.Json;
using GameJsonSerializer = GFramework.Game.Serializer.JsonSerializer;
@ -182,8 +183,8 @@ public sealed class JsonSerializerTests
var parts = raw.Split(':');
return new CoordinateStub
{
X = int.Parse(parts[0]),
Y = int.Parse(parts[1])
X = int.Parse(parts[0], CultureInfo.InvariantCulture),
Y = int.Parse(parts[1], CultureInfo.InvariantCulture)
};
}
}

View File

@ -288,11 +288,11 @@ public class UnifiedSettingsDataRepository(
{
ArgumentNullException.ThrowIfNull(source);
// 反序列化后的运行时类型可能只是 IDictionary 实现;若底层仍是 Dictionary则保留其 comparer
// 否则退回到按当前内容复制,避免因为 API 抽象化而改变持久化前后的键比较语义
// 反序列化后的运行时类型可能只是 IDictionary 实现;若底层仍是 Dictionary则保留其 comparer
// 若 comparer 已因接口抽象而不可恢复,则显式回退到 Ordinal避免让默认 comparer 语义继续隐式存在
var sections = source.Sections is Dictionary<string, string> dictionary
? new Dictionary<string, string>(dictionary, dictionary.Comparer)
: new Dictionary<string, string>(source.Sections);
: new Dictionary<string, string>(source.Sections, StringComparer.Ordinal);
return new UnifiedSettingsFile
{

View File

@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Collections.Generic;
using GFramework.Core.Abstractions.Versioning;
@ -28,8 +29,11 @@ internal sealed class UnifiedSettingsFile : IVersioned
/// <remarks>
/// 这里公开为 <see cref="IDictionary{TKey,TValue}" /> 而不是具体的 <see cref="Dictionary{TKey,TValue}" />
/// 以避免暴露可替换的具体集合实现,同时继续兼容 Newtonsoft.Json 对字典对象的序列化与反序列化。
/// 默认实例使用 <see cref="StringComparer.Ordinal" />;若调用方提供其他实现,仓库在可以识别底层
/// <see cref="Dictionary{TKey,TValue}" /> comparer 时会保留原语义,否则克隆快照时会显式回退到
/// <see cref="StringComparer.Ordinal" />。
/// </remarks>
public IDictionary<string, string> Sections { get; set; } = new Dictionary<string, string>();
public IDictionary<string, string> Sections { get; set; } = new Dictionary<string, string>(StringComparer.Ordinal);
/// <summary>
/// 配置文件版本号,用于版本控制和兼容性检查

View File

@ -135,7 +135,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
.WithLocation(0)
.WithArguments("Values"));
await test.RunAsync();
await test.RunAsync().ConfigureAwait(false);
}
[Test]
@ -302,7 +302,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
.WithLocation(0)
.WithArguments("Register", "_registry", "Values"));
await test.RunAsync();
await test.RunAsync().ConfigureAwait(false);
}
[Test]
@ -500,7 +500,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
.WithLocation(0)
.WithArguments("_registry", "Values"));
await test.RunAsync();
await test.RunAsync().ConfigureAwait(false);
}
[Test]
@ -555,7 +555,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
.WithLocation(0)
.WithArguments("Register", "_registry", "Values"));
await test.RunAsync();
await test.RunAsync().ConfigureAwait(false);
}
[Test]
@ -610,7 +610,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
.WithLocation(0)
.WithArguments("Values"));
await test.RunAsync();
await test.RunAsync().ConfigureAwait(false);
}
[Test]
@ -694,7 +694,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
""";
}
private static Task VerifyDiagnosticsAsync(
private static async Task VerifyDiagnosticsAsync(
string source,
bool skipGeneratedSourcesCheck = false,
params DiagnosticResult[] expectedDiagnostics)
@ -718,6 +718,6 @@ public class AutoRegisterExportedCollectionsGeneratorTests
test.ExpectedDiagnostics.Add(expectedDiagnostic);
}
return test.RunAsync();
await test.RunAsync().ConfigureAwait(false);
}
}

View File

@ -20,7 +20,7 @@ public sealed class AbstractArchitectureModuleInstallationTests
var module = new RecordingGodotModule();
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () =>
await architecture.InstallGodotModuleForTestAsync(module));
await architecture.InstallGodotModuleForTestAsync(module).ConfigureAwait(false));
Assert.Multiple(() =>
{

View File

@ -197,7 +197,7 @@ public sealed class GodotYamlConfigLoaderTests
var loader = CreateLoader(isEditor: false);
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () =>
await loader.LoadAsync(new ConfigRegistry()));
await loader.LoadAsync(new ConfigRegistry()).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -225,7 +225,7 @@ public sealed class GodotYamlConfigLoaderTests
configureLoader: static _ => { });
var exception = Assert.ThrowsAsync<ArgumentException>(async () =>
await loader.LoadAsync(new ConfigRegistry()));
await loader.LoadAsync(new ConfigRegistry()).ConfigureAwait(false));
Assert.That(exception!.ParamName, Is.EqualTo("relativePath"));
}
@ -254,7 +254,7 @@ public sealed class GodotYamlConfigLoaderTests
configureLoader: static _ => { });
var exception = Assert.ThrowsAsync<ArgumentException>(async () =>
await loader.LoadAsync(new ConfigRegistry()));
await loader.LoadAsync(new ConfigRegistry()).ConfigureAwait(false));
Assert.That(exception!.ParamName, Is.EqualTo("relativePath"));
}

View File

@ -1,3 +1,4 @@
using System;
using GFramework.Godot.Text;
namespace GFramework.Godot.Tests.Text;
@ -25,7 +26,7 @@ public sealed class RichTextMarkupTests
[Test]
public void Effect_Should_Sort_Environment_Parameters_By_Key()
{
var env = new Dictionary<string, object?>
var env = new Dictionary<string, object?>(StringComparer.Ordinal)
{
["tick"] = 0.1f,
["speed"] = 4
@ -53,7 +54,7 @@ public sealed class RichTextMarkupTests
[Test]
public void Effect_Should_Reject_Invalid_Environment_Key_Tokens()
{
var env = new Dictionary<string, object?>
var env = new Dictionary<string, object?>(StringComparer.Ordinal)
{
["bad key"] = 1
};

View File

@ -112,8 +112,8 @@ public abstract class AbstractArchitecture(
// 在附加流程完成前先登记模块,保证后续任一步失败时仍能参与架构销毁阶段的清理。
_extensions.Add(module);
// 等待锚点准备就绪,并保持 Godot 同步上下文,以便后续附加逻辑安全访问节点 API
await anchor.WaitUntilReadyAsync();
// 显式保留 Godot 同步上下文,确保后续 AddChild 和 OnAttach 仍在节点可访问的主线程执行
await anchor.WaitUntilReadyAsync().ConfigureAwait(true);
// 延迟调用将扩展节点添加为锚点的子节点
anchor.CallDeferred(Node.MethodName.AddChild, module.Node);

View File

@ -104,41 +104,36 @@ internal sealed class GodotYamlConfigEnvironment
private static IReadOnlyList<GodotYamlConfigDirectoryEntry>? EnumerateDirectoryCore(string path)
{
if (!path.IsGodotPath())
return path.IsGodotPath()
? EnumerateGodotDirectory(path)
: EnumerateFileSystemDirectory(path);
}
private static IReadOnlyList<GodotYamlConfigDirectoryEntry>? EnumerateFileSystemDirectory(string path)
{
try
{
try
if (!Directory.Exists(path))
{
if (!Directory.Exists(path))
{
return null;
}
return null;
}
return Directory
.EnumerateFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly)
.Select(static entryPath => new GodotYamlConfigDirectoryEntry(
Path.GetFileName(entryPath),
Directory.Exists(entryPath)))
.ToArray();
}
catch (IOException)
{
// 非 Godot 路径分支与公开契约保持一致:宿主无法访问目录时返回 null而不是泄漏底层异常。
return null;
}
catch (UnauthorizedAccessException)
{
return null;
}
catch (ArgumentException)
{
return null;
}
catch (NotSupportedException)
{
return null;
}
return Directory
.EnumerateFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly)
.Select(static entryPath => new GodotYamlConfigDirectoryEntry(
Path.GetFileName(entryPath),
Directory.Exists(entryPath)))
.ToArray();
}
catch (Exception ex) when (IsExpectedDirectoryEnumerationException(ex))
{
// 非 Godot 路径分支与公开契约保持一致:宿主无法访问目录时返回 null而不是泄漏底层异常。
return null;
}
}
private static IReadOnlyList<GodotYamlConfigDirectoryEntry>? EnumerateGodotDirectory(string path)
{
using var directory = DirAccess.Open(path);
if (directory == null)
{
@ -170,9 +165,15 @@ internal sealed class GodotYamlConfigEnvironment
// 目录枚举句柄必须成对结束,避免未来循环体扩展后在异常路径上遗留引擎状态。
directory.ListDirEnd();
}
return entries;
}
private static bool IsExpectedDirectoryEnumerationException(Exception exception)
{
return exception is IOException or UnauthorizedAccessException or ArgumentException or NotSupportedException;
}
private static bool FileExistsCore(string path)
{
return path.IsGodotPath()

View File

@ -144,7 +144,8 @@ public abstract class SceneBehaviorBase<T> : ISceneBehavior
public virtual async ValueTask OnPauseAsync()
{
if (_scene != null)
await _scene.OnPauseAsync();
// 暂停后紧接着会修改 Owner 的处理开关,必须回到 Godot 主线程继续执行。
await _scene.OnPauseAsync().ConfigureAwait(true);
// 暂停处理
Owner.SetProcess(false);
@ -165,7 +166,8 @@ public abstract class SceneBehaviorBase<T> : ISceneBehavior
return;
if (_scene != null)
await _scene.OnResumeAsync();
// 恢复完成后要立刻重新启用节点处理流程,因此显式保留当前同步上下文。
await _scene.OnResumeAsync().ConfigureAwait(true);
// 恢复处理
Owner.SetProcess(true);
@ -198,7 +200,8 @@ public abstract class SceneBehaviorBase<T> : ISceneBehavior
public virtual async ValueTask OnUnloadAsync()
{
if (_scene != null)
await _scene.OnUnloadAsync();
// 卸载后的 QueueFreeX 必须在 Godot 节点线程上调用,不能切走同步上下文。
await _scene.OnUnloadAsync().ConfigureAwait(true);
// 释放节点
Owner.QueueFreeX();

View File

@ -6,32 +6,41 @@
## 当前恢复点
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-051`
- 当前阶段:`Phase 51`
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-058`
- 当前阶段:`Phase 58`
- 当前焦点:
- `2026-04-24` 本轮已完成 `GFramework.Godot.SourceGenerators.Tests` warning 清理
- 当前主线程切片从生成器实现转到对应测试项目,并已把 `GFramework.Godot.SourceGenerators.Tests``24` 个 warning 降到 `0`
- 当前批次按 `origin/main` merge-base 计算的累计分支 diff 预计为 `23` 个文件,仍低于 `$gframework-batch-boot 75` 的主阈值
- 当前工作树除未跟踪的 `.codex` 目录外,还存在与本批次无关的既有文档 / 跟踪文件修改;提交当前批次时必须只包含本 topic 相关文件
- `2026-04-24` 使用 `$gframework-pr-review` 复核当前分支 PR #286 的 latest-head review threads、MegaLinter 与测试状态
- 已确认最新 head 上唯一未解决的实质代码线程指向 `GFramework.Godot/Scene/SceneBehaviorBase.cs``OnPauseAsync` 的缩进异常,并顺带对齐 `OnResumeAsync``OnUnloadAsync`
- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release` 通过,结果为 `565 Warning(s)``0 Error(s)`;当前跟进只处理 PR review 指向的格式问题,不扩散到既有 warning 基线
- `dotnet format GFramework.Godot/GFramework.Godot.csproj --verify-no-changes --no-restore --include GFramework.Godot/Scene/SceneBehaviorBase.cs` 已通过,当前文件不再残留格式差异
## 当前活跃事实
- 之前记录的 plain `dotnet build` `0 Warning(s)` 属于增量构建假阴性,不能再作为 warning 检查真值
- 本轮已完成 `GFramework.Godot.SourceGenerators` warning 清理clean `Release` build 从 9 个 warning 降至 0 个 warning
- 当前已确认解决的文件包括 `BindNodeSignalGenerator.cs``GetNodeGenerator.cs``GodotProjectMetadataGenerator.cs``Registration/AutoRegisterExportedCollectionsGenerator.cs`
- 本轮直接执行仓库根目录 `dotnet clean` 仍在 `ValidateSolutionConfiguration` 阶段失败,输出未提供具体 error 文本
- 本轮直接执行仓库根目录 `dotnet build` 成功,并给出 `1184 warning(s)` 的真实输出
- `GFramework.Godot.SourceGenerators.Tests` 已通过测试辅助模板抽取与 `ConfigureAwait(false)` 修正,当前 `Debug` / `Release` 构建均为 `0 Warning(s)`
- 本轮已验证 `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build`,结果为 `Passed: 48`
- 仓库根目录 `dotnet clean GFramework.sln -c Release` 仍在 `ValidateSolutionConfiguration` 阶段失败,项目级 `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 也未能稳定提供 clean 基线
- 当前整仓最近一次直接观测值仍是 `dotnet build GFramework.sln -c Release``116 warning(s)`
- `RP-056` 已验证 `GeneratedConfigConsumerIntegrationTests.cs` 不再出现在项目 build warning 输出中
- `RP-057` 已验证 `PersistenceTests.cs` 不再出现在 `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental` 的 warning 输出中
- 本轮已验证 `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"`,结果为 `Passed: 2`
- `GFramework.Game.Tests` 当前剩余热点已经几乎完全集中到 `YamlConfigLoaderTests.cs` 这一高上下文文件
- PR #286 当前标题为 `Fix/analyzer warning reduction batch`;最新抓取时间点的 PR 状态仍为 `OPEN`
- 最新 reviewed commit 为 `2b707343577193fc9904517e6078149653e95698`CodeRabbit 于 `2026-04-24T12:44:12Z` 给出 `CHANGES_REQUESTED`
- latest-head review threads 中只有 `1` 个未解决线程,内容是 `SceneBehaviorBase.OnPauseAsync` 的缩进不一致;本地源码已修复并扩展到同段的 `OnResumeAsync` / `OnUnloadAsync`
- MegaLinter 的 `dotnet-format` 详细问题与上述格式异常一致;本地 `dotnet format --verify-no-changes` 已通过
- PR 上其余 nitpick 仅为可选建议或已明确留待后续批次处理,当前没有额外需要立即修复的 latest-head 代码线程
## 当前风险
- 如果后续继续依赖增量 `dotnet build`,容易再次把 warning 数量误判为 0
- 缓解措施:每轮 warning 检查前先执行 `dotnet clean`,再执行目标 `dotnet build`
- 仓库根目录 `dotnet clean` 目前仍然无法给出新的 clean 基线
- 缓解措施:若下一轮继续做整仓 warning reduction先定位 `dotnet clean` 的 solution-level 失败原因,或明确继续沿用用户确认的 `1193 warning(s)` clean 基线与本轮 `1184 warning(s)` direct build 观测值
- 当前 worktree 已存在与本批次无关的未提交改动
- 缓解措施:提交当前批次时只暂存 `GFramework.Godot.SourceGenerators.Tests` 与对应 `ai-plan` 文件,避免混入其他 topic 变更
- 仓库根目录与 `GFramework.Game.Tests``dotnet clean` 目前都无法给出新的 clean 基线
- 缓解措施:后续若继续整仓 warning reduction需要单独定位 clean 失败原因,或明确继续沿用 direct build 观测值作为临时真值
- 当前 worktree 仍存在未跟踪的 `.codex` 目录
- 缓解措施:提交当前批次时只暂存 analyzer-warning-reduction 相关源码与 `ai-plan` 文件,避免把工作目录辅助文件混入提交
- 下一轮若继续深入 `GFramework.Game.Tests`,很可能需要进入 `YamlConfigLoaderTests.cs` 这种高上下文大文件
- 缓解措施:把它单独作为一个明确的新批次处理,不与其它 warning family 混批
- PR 标题检查当前仍显示 `Inconclusive`
- 缓解措施:如需让该检查转绿,需要单独更新 GitHub PR 标题;这不属于本地代码修改范围
## 活跃文档
@ -47,19 +56,31 @@
## 验证说明
- `dotnet clean`
- `dotnet clean GFramework.sln -c Release`
- 结果:失败;停在 solution `ValidateSolutionConfiguration``0 Warning(s)``0 Error(s)`,未输出更具体的 error 文本
- `dotnet build`
- 结果:成功;`1184 Warning(s)``0 Error(s)`
- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj`
- 初始结果:成功;`24 Warning(s)``0 Error(s)`
- 本轮收尾结果:成功;`0 Warning(s)``0 Error(s)`
- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release`
- 结果:成功;`0 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build`
- 结果:成功;`Passed: 48``Failed: 0`
- `dotnet build GFramework.sln -c Release`
- 结果:成功;`116 Warning(s)``0 Error(s)`
- `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
- 结果失败clean 阶段在 MSBuild 清理路径结束前返回 `0 Warning(s)``0 Error(s)`,未输出额外错误文本
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
- `RP-055` 收尾结果:成功;`63 Warning(s)``0 Error(s)`
- `RP-056` 当前结果:成功;`59 Warning(s)``0 Error(s)`
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental`
- `RP-057` 热点重排前:成功;`253 Warning(s)``0 Error(s)`
- `RP-057` 当前结果:成功;`249 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"`
- 结果:成功;`Passed: 19``Failed: 0`
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"`
- 结果:成功;`Passed: 4``Failed: 0`
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"`
- 结果:成功;`Passed: 2``Failed: 0`
- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release`
- 结果:成功;`565 Warning(s)``0 Error(s)`
- `dotnet format GFramework.Godot/GFramework.Godot.csproj --verify-no-changes --no-restore --include GFramework.Godot/Scene/SceneBehaviorBase.cs`
- 首次运行失败restore 阶段异常退出,未进入格式验证
- 第二次运行(同命令追加 sandbox 提权成功workspace 仅提示加载 warning无格式差异
## 下一步建议
1. 提交当前 `GFramework.Godot.SourceGenerators.Tests` 清理批次,并确认提交只包含本 topic 相关文件
2. 如果继续 warning reduction优先重新评估仓库根目录 `dotnet clean` 的 solution-level 失败,再决定是继续从整仓 `dotnet build` 输出挑热点,还是先修复 clean 基线采集问题
1. 提交 `SceneBehaviorBase.cs``RP-058` tracking/trace 更新,清掉 PR #286 当前 latest-head 上的格式类 review thread
2. 若继续 warning reduction 主线,应回到 `GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs`,把它作为独立高上下文批次处理

View File

@ -1,5 +1,189 @@
# Analyzer Warning Reduction 追踪
# Analyzer Warning Reduction 追踪
## 2026-04-24 — RP-058
### 阶段PR #286 latest-head review 格式跟进
- 触发背景:
- 用户要求执行 `$gframework-pr-review`,需要以当前分支 PR 页面而不是本地记忆为准,重新核对 CodeRabbit、MegaLinter 和测试状态
- 抓取脚本当前解析到的 PR 是 `#286`,最新 reviewed commit 为 `2b707343577193fc9904517e6078149653e95698`
- 最新 head 上真正未解决的代码线程只剩 `GFramework.Godot/Scene/SceneBehaviorBase.cs:148` 的缩进问题;其余 nitpick 为可选建议或已留待后续批次
- 主线程实施:
- 运行 `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json`,确认 PR `OPEN`、测试 `2156/2156` 通过、MegaLinter 仅剩 `dotnet-format` 警告
- 复核 `SceneBehaviorBase.cs` 后确认 `OnPauseAsync` 的方法签名与方法体缩进异常仍存在于本地源码;同段的 `OnResumeAsync``OnUnloadAsync` 也有同类偏差
- 在不改变行为的前提下统一修正三个方法的缩进,保持现有 XML 注释、`ConfigureAwait(true)` 语义与 Godot 主线程说明不变
- 更新 active tracking / trace记录当前 PR review follow-up 已完成,本地剩余外部信号只剩 PR 标题检查
- 验证里程碑:
- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release`
- 结果:成功;`565 Warning(s)``0 Error(s)`
- 结论:当前格式修复未引入编译错误;模块既有 warning 基线仍存在,但不属于本次 PR review 跟进范围
- `dotnet format GFramework.Godot/GFramework.Godot.csproj --verify-no-changes --no-restore --include GFramework.Godot/Scene/SceneBehaviorBase.cs`
- 首次运行失败sandbox 环境下在 build host / pipe 建立阶段报错,未进入真实格式比较
- 提权复验:成功;仅提示 workspace load warning无格式差异
- 当前结论:
- PR #286 当前 latest-head 上唯一未解决的实质代码 review thread 已在本地修复
- MegaLinter 暴露的 `dotnet-format` 问题已被本地 `verify-no-changes` 复验覆盖
- `Title check: Inconclusive` 仍然存在,但属于 GitHub PR 标题元数据问题,不能通过本地代码提交直接消除
## 2026-04-24 — RP-057
### 阶段:清理 `PersistenceTests.cs` 残余 `MA0004`
- 触发背景:
- `RP-056` 提交后重新做非增量热点排序时,`GFramework.Game.Tests` 的剩余测试项目 warning 已明显收敛,只剩 `PersistenceTests.cs` 少量 `MA0004``YamlConfigLoaderTests.cs` 大量 warning
- 为避免在同一轮直接进入 `YamlConfigLoaderTests.cs` 的大文件高上下文批次,先吃掉 `PersistenceTests.cs` 这个独立小切片
- 主线程实施:
- 在 `PersistenceTests.cs` 中为统一设置仓库失败缓存一致性相关测试补齐剩余 `.ConfigureAwait(false)`
- 覆盖保存失败与删除失败两个测试场景中的缓存读取、存在性检查、后续保存和最终验证读取
- 更新 active tracking / trace明确下一批若继续推进应单独进入 `YamlConfigLoaderTests.cs`
- 验证里程碑:
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental`
- 热点重排前:成功;`253 Warning(s)``0 Error(s)`
- 修复后:成功;`249 Warning(s)``0 Error(s)`
- 结论:`PersistenceTests.cs` 不再出现在 warning 输出中
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"`
- 结果:成功;`Passed: 2``Failed: 0`
- 当前结论:
- `PersistenceTests.cs` 的残余 warning 已清零,`GFramework.Game.Tests` 剩余热点几乎全部压缩到了 `YamlConfigLoaderTests.cs`
- 当前工作树投影下,分支体积为 `27` 个文件、`991` 行,仍低于 `$gframework-batch-boot 75`
- 按 batch skill 的低风险边界,这一轮应在提交后收口;下一轮再把 `YamlConfigLoaderTests.cs` 作为单独批次处理
## 2026-04-24 — RP-056
### 阶段:修复 `GeneratedConfigConsumerIntegrationTests` 编译错误并清零该文件 warning
- 触发背景:
- `RP-055` 继续推进时,`GeneratedConfigConsumerIntegrationTests.cs` 在 raw string `invalidYaml` 段落附近出现 `CS8999`,导致 `GFramework.Game.Tests` 暂时无法编译
- 该文件同时仍是项目内少数残留 warning 热点之一,因此适合作为同一批次中的单文件收尾
- 主线程实施:
- 修复 `GeneratedConfigConsumerIntegrationTests.cs` 中损坏的 `CreateMonsterFiles` raw string 与方法边界,恢复文件可编译状态
- 保留并整理上一轮已开始的 `.ConfigureAwait(false)` 与断言 helper 抽取
- 继续将 `AssertGeneratedBindingsLoadResults` 再拆分为 catalog / monster / item 三个辅助方法,清除该文件剩余 `MA0051`
- 更新 active tracking / trace沿用 `merge-base(origin/main, HEAD)` 作为 `$gframework-batch-boot 75` 的唯一 stop-condition 口径
- 验证里程碑:
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
- 结果:成功;`59 Warning(s)``0 Error(s)`
- 结论:`GeneratedConfigConsumerIntegrationTests.cs` 不再出现在 warning 输出中
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"`
- 结果:成功;`Passed: 4``Failed: 0`
- 当前结论:
- `GFramework.Game.Tests` 已从 `RP-055` 收尾时的 `63 warning(s)` 进一步收敛到 `59 warning(s)`
- 当前工作树投影下,分支体积为 `27` 个文件、`943` 行,仍低于 `$gframework-batch-boot 75`
- 后续若继续自动推进,最自然的下一批将进入 `YamlConfigLoaderTests.cs` 这类高上下文大文件
## 2026-04-24 — RP-055
### 阶段:修正 stop-condition 口径并继续 `GFramework.Game.Tests` 小热点
- 触发背景:
- `RP-054` 之后复核 batch stop-condition 时,发现之前一度把工作树 diff 错当成了 skill 要求的 branch diff
- 按正确口径 `merge-base(origin/main, HEAD)` 计算,`RP-054` 提交后的真实分支体积是 `23` 个文件、`603` 行,因此仍可继续下一批
- 当前剩余 warning 里,`ArchitectureConfigIntegrationTests``GameConfigBootstrapTests``JsonSerializerTests` 属于独立且低风险的小切片
- 主线程实施:
- 在 `ArchitectureConfigIntegrationTests.cs` 中补齐异步架构初始化 / 销毁和异常断言的 `.ConfigureAwait(false)`
- 在 `GameConfigBootstrapTests.cs` 中补齐启动流程、并发初始化断言与 `WaitForTaskWithinAsync``.ConfigureAwait(false)`
- 在 `JsonSerializerTests.cs` 中将坐标解析改为 `CultureInfo.InvariantCulture`
- 顺手清理 `YamlConfigLoaderAllOfTests.cs``PersistenceTests.cs` 中上一批遗漏的字段态状态检查和异步等待 warning
- 纠正 active tracking明确 stop-condition 必须使用 `origin/main...HEAD` 的 merge-base 分支 diff而不是工作树 diff
- 验证里程碑:
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
- 并行误用 build/test 时:出现 `MSB3026` / `CS2012` 文件占用噪声,不计入代码结论
- 串行复验:成功;`63 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"`
- 结果:成功;`Passed: 19``Failed: 0`
- 当前结论:
- `GFramework.Game.Tests` 已从上一批收尾时的 `71 warning(s)` 进一步降到 `63 warning(s)`
- 这次提交后的分支体积投影为 `26` 个文件、`691` 行,仍低于 `$gframework-batch-boot 75`
- 剩余热点越来越集中到 `YamlConfigLoaderTests.cs``GeneratedConfigConsumerIntegrationTests.cs`,后续继续时应把它们视为高上下文批次
## 2026-04-24 — RP-054
### 阶段:`GFramework.Game.Tests` 低风险测试 warning 批次(触发文件数停止阈值)
- 触发背景:
- 用户要求“直接进入下一批”,继续沿 `$gframework-batch-boot 75` 自动推进 warning reduction
- 以 `origin/main` 为基线时,上一批提交后分支累计 diff 仍只有 `8` 个文件,足够再落一个独立批次
- 重新执行 `dotnet clean GFramework.sln -c Release` 仍停在 `ValidateSolutionConfiguration`,因此继续以直接 `dotnet build GFramework.sln -c Release` 的输出挑选低风险热点
- 主线程实施:
- 从整仓 `Release build``116 warning(s)` 入口观测值中,选择 `GFramework.Game.Tests` 的小型测试文件和 `PersistenceTestUtilities.cs` 作为当前批次,刻意避开 `YamlConfigLoaderTests.cs` 这类高上下文大文件
- 在 `YamlConfigLoaderIfThenElseTests.cs``YamlConfigLoaderDependentSchemasTests.cs``YamlConfigLoaderDependentRequiredTests.cs``YamlConfigLoaderNegationTests.cs``YamlConfigLoaderAllOfTests.cs``YamlConfigLoaderEnumTests.cs``YamlConfigTextValidatorTests.cs``PersistenceTests.cs` 中补齐 `.ConfigureAwait(false)`,并把字段态 `_rootPath``ThrowIfNull` 改为显式 `InvalidOperationException`
- 将 `PersistenceTestUtilities.cs` 拆分为 `TestDataLocation.cs``TestSaveData.cs``TestVersionedSaveData.cs``TestSimpleData.cs``TestNamedData.cs`,消除 `MA0048` 并对齐仓库的一文件一主类型风格
- 在 `YamlConfigSchemaValidatorTests.cs` 中把字段态 `_rootPath` 的校验改成显式状态异常,避免继续触发 `MA0015`
- 验证里程碑:
- `dotnet clean GFramework.sln -c Release`
- 结果:失败;停在 `ValidateSolutionConfiguration``0 Warning(s)``0 Error(s)`
- `dotnet build GFramework.sln -c Release`
- 结果:成功;`116 Warning(s)``0 Error(s)`
- `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
- 结果失败clean 阶段提前结束,`0 Warning(s)``0 Error(s)`
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
- 第一轮批次后:成功;`80 Warning(s)``0 Error(s)`
- 收尾修正后:成功;`71 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderIfThenElseTests|FullyQualifiedName~YamlConfigLoaderDependentSchemasTests|FullyQualifiedName~YamlConfigLoaderDependentRequiredTests|FullyQualifiedName~YamlConfigLoaderNegationTests|FullyQualifiedName~YamlConfigLoaderAllOfTests|FullyQualifiedName~YamlConfigLoaderEnumTests|FullyQualifiedName~YamlConfigTextValidatorTests|FullyQualifiedName~YamlConfigSchemaValidatorTests|FullyQualifiedName~PersistenceTests"`
- 结果:成功;`Passed: 63``Failed: 0`
- 当前结论:
- `GFramework.Game.Tests` 本轮入口热点已从 `116 warning(s)` 收敛到 `71 warning(s)`,且本轮 touched files 不再出现在 warning 输出中
- 当前工作树相对 `origin/main` 的累计 diff 已达到 `76` 个文件、`986` 行,超过 `$gframework-batch-boot 75` 的主停止阈值
- 按批处理技能规则,本轮必须在提交当前批次后停止;剩余候选应在新一轮里单独评估,尤其是 `YamlConfigLoaderTests.cs`
## 2026-04-24 — RP-053
### 阶段:`GFramework.Godot` / `GFramework.Godot.Tests` 小批次 warning 清理
- 触发背景:
- 用户以 `$gframework-batch-boot 75` 要求继续按批次推进 analyzer warning reduction并以 `origin/main` 作为累计分支 diff 基线
- 当前 worktree `fix/analyzer-warning-reduction-batch` 相对 `origin/main` 的已提交分支 diff 为 `0` 个文件,具备继续落一个低风险 warning batch 的空间
- solution-level `dotnet clean GFramework.sln -c Release` 仍在 `ValidateSolutionConfiguration` 阶段失败,因此本轮继续用直接 `dotnet build GFramework.sln -c Release` 建立热点观察值
- 主线程实施:
- 运行 `dotnet build GFramework.sln -c Release`,确认当前整仓观测值为 `1122 warning(s)`,并从输出中挑选 `GFramework.Godot` 的小范围热点作为本轮批次
- 在 `GodotYamlConfigEnvironment.cs` 中按“普通文件系统 / Godot 路径”拆分目录枚举 helper消除 `MA0051`
- 在 `AbstractArchitecture.cs``SceneBehaviorBase.cs` 中将必须保留 Godot 主线程上下文的 await 显式改为 `.ConfigureAwait(true)`,清理 `MA0004` 并把线程意图写入注释
- 在 `GFramework.Godot.Tests` 中补齐异步断言的 `.ConfigureAwait(false)`,并让 `RichTextMarkupTests` 的测试字典显式指定 `StringComparer.Ordinal`
- 验证里程碑:
- `dotnet clean GFramework.sln -c Release`
- 结果:失败;停在 `ValidateSolutionConfiguration``0 Warning(s)``0 Error(s)`
- `dotnet build GFramework.sln -c Release`
- 结果:成功;`1122 Warning(s)``0 Error(s)`
- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release`
- 第一轮修复后:成功;`12 Warning(s)``0 Error(s)`,仅剩 `MA0004`
- 第二轮修复后:成功;`0 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~AbstractArchitectureModuleInstallationTests|FullyQualifiedName~GodotYamlConfigLoaderTests|FullyQualifiedName~RichTextMarkupTests"`
- 结果:成功;`Passed: 15``Failed: 0`
- `dotnet build GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release`
- 并行验证时:成功;`1 Warning(s)``0 Error(s)``MSB3026` 为与并行 `dotnet test` 竞争输出 DLL 的文件占用
- 串行复验:成功;`0 Warning(s)``0 Error(s)`
- 当前结论:
- `GFramework.Godot``GFramework.Godot.Tests` 本轮直接涉及的 warning 已全部清零
- 当前待提交代码批次相对 `origin/main` 的源码 diff 为 `6` 个文件、`107` 行,距离 `$gframework-batch-boot 75` 主停止阈值仍有充足余量
- 继续推进的下一批候选将主要落在 `GFramework.Game` 等高 warning 基线模块,已不再属于当前同等级低风险切片,因此本轮在这里收口并进入提交
## 2026-04-24 — RP-052
### 阶段PR review follow-upcomparer 契约 + `ConfigureAwait(false)` 收尾)
- 触发背景:
- 当前分支 PR #283 的最新 review 中,`greptile-apps[bot]` 仍有一个未解决线程,指出 `UnifiedSettingsDataRepository.CloneFile` fallback 会静默丢失原 comparer
- CodeRabbit 另指出 `AutoRegisterExportedCollectionsGeneratorTests.cs` 中还残留 5 处 `await test.RunAsync();`,与同项目其他测试文件的 `.ConfigureAwait(false)` 风格不一致
- 主线程实施:
- 复核 PR review JSON、`UnifiedSettingsDataRepository.cs``UnifiedSettingsFile.cs``AutoRegisterExportedCollectionsGeneratorTests.cs` 的当前代码,确认只有 comparer 契约线程仍属最新 head 上的实质问题
- 将 `UnifiedSettingsFile.Sections` 的 XML 注释补充为显式 comparer 契约,并把默认字典初始化改为 `StringComparer.Ordinal`
- 将 `CloneFile` fallback 从隐式默认 comparer 改为显式 `StringComparer.Ordinal`,并同步修正文档注释,避免继续暗含“保留原语义”的错误表述
- 把 `AutoRegisterExportedCollectionsGeneratorTests` 中剩余的 5 处 `await test.RunAsync();` 统一为 `.ConfigureAwait(false)`,同时让 `VerifyDiagnosticsAsync` 内部也消费 `ConfigureAwait(false)`
- 验证里程碑:
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
- 结果:成功;`533 Warning(s)``0 Error(s)``GFramework.Game` 仍有既有 warning 基线,本轮 follow-up 仅处理 PR review 指向的 comparer 契约与测试异步等待一致性
- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release`
- 结果:成功;`0 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build`
- 首次并行复验:失败;`FileNotFoundException`,原因是 `--no-build` 测试在 Release DLL 落盘前启动
- 串行复验:成功;`Passed: 48``Failed: 0`
- 当前结论:
- PR #283 当前仍打开的 comparer review thread 已在本地代码与 XML 注释层面得到对应修复
- `AutoRegisterExportedCollectionsGeneratorTests` 的异步等待风格已与同项目其他测试保持一致
- 当前改动已通过直接受影响测试项目的 Release build 与串行 Release test 复验,可进入提交阶段
## 2026-04-24 — RP-051
### 阶段:`GFramework.Godot.SourceGenerators.Tests` warning 清零

View File

@ -0,0 +1,133 @@
# Documentation Full Coverage Governance Status History RP-023 To RP-025
以下内容从 active tracking 中迁出,用于保留 `DOCUMENTATION-FULL-COVERAGE-GOV-RP-023`
`DOCUMENTATION-FULL-COVERAGE-GOV-RP-025` 的批处理状态、验证细节与恢复背景。默认 `boot` 只需要读取 active
tracking 中的最新摘要;若需要追溯 `2026-04-23``2026-04-24` 的治理细节,再回到本归档文件。
## 迁出的状态摘要
- `2026-04-23` 基于 PR `#272` 的 review follow-up 已完成:
- 为 `docs/zh-CN/game/data.md` 补充 `UnifiedSettingsDataRepository` 的统一文件布局示例
- 为 `GFramework.Godot.SourceGenerators/README.md` 补充手写 `_Ready()` / `_ExitTree()` 时显式调用生成方法的最小样例
- 将过长的 active tracking / trace 瘦身,并把历史摘要迁回 `archive/`
- `2026-04-23` 使用 `$gframework-pr-review` 重新抓取 PR `#272` 后,确认 latest-head review 当前仍有 1 条
Greptile open thread定位到 `docs/zh-CN/godot/setting.md:75` 的 inline code 误写成
`SettingsModel&lt;ISettingsDataRepository&gt;`
- 结合当前 PR 已改动的 `docs/zh-CN/godot/storage.md` 做同类巡检后,确认 `SaveRepository&lt;TSaveData&gt;`
也会在 VitePress code span 中按字面量渲染;两处现已在本地统一改为真实泛型写法。
- `2026-04-23``origin/main``aa879d2``2026-04-23T17:51:41+08:00`)为批处理基线,对
`README.md``GFramework.*``docs/zh-CN/**` 执行同类模式巡检,确认剩余热点仅位于
`docs/zh-CN/core/functional.md``docs/zh-CN/tutorials/functional-programming.md` 共 8 处。
- 上述 8 处 inline code 中的 `Option&lt;T&gt;``Result&lt;T&gt;``Nullable&lt;T&gt;` 已统一改为真实
泛型写法,避免在 VitePress 中显示字面量 HTML entity。
- `2026-04-23` 根据本轮使用反馈,已为 `.agents/skills/gframework-batch-boot/SKILL.md`
`.agents/skills/README.md` 补充数字速记阈值语义:
- `$gframework-batch-boot 75` 默认表示“当前分支全部提交相对远程 `origin/main` 接近 75 个分支 diff 文件时停止”
- `$gframework-batch-boot 75 2000` 默认表示“当前分支全部提交相对远程 `origin/main` 接近 75 个文件或 2000 行变更时停止”
- `75 | 2000` 仅作为可理解的 OR 写法保留,不再作为推荐写法,以避免与 shell pipe 混淆
- `2026-04-23``origin/main``aa879d2``2026-04-23T17:51:41+08:00`)为批处理基线,对
`docs/zh-CN/getting-started/index.md``core/index.md``game/index.md``source-generators/index.md`
`api-reference/index.md``abstractions/core-abstractions.md``abstractions/game-abstractions.md`
做导航可达性修复,把仓库 README / 根 README 裸路径统一改为指向 GitHub `main` 分支的可点击链接。
- 该批次不改变文档语义,只收口 docs 站点中的入口可达性;适合继续作为小步快跑的低风险治理模式。
- `2026-04-23` 在同一基线下继续收口第二批专题页导航热点,已将 `core/cqrs.md``ecs/arch.md`
`abstractions/ecs-arch-abstractions.md``game/scene.md``game/ui.md` 和 6 个
`source-generators/*.md` 专题页里的 README 裸路径统一改为 GitHub `main` blob 外链。
- 截至提交 `8a11720``2026-04-23T21:01:28+08:00`),当前分支相对 `origin/main``aa879d2`)的累计 diff
`24` 个文件、`264` 行,仍低于 `$gframework-batch-boot 75` 的停止阈值;但剩余命中已主要是正文语义性提及,不再适合作为同类批处理。
- 当前剩余的托管侧信号是 GitHub `Title check` 对 PR 标题过泛的 inconclusive 提示;这属于 PR 元数据,不是本地
文件缺陷。
- `2026-04-24` 根据用户反馈完成一轮“公开文档边界”治理,并继续按 `$gframework-batch-boot 75` 向前推进:
- 在 `AGENTS.md``.agents/skills/_shared/DOCUMENTATION_STANDARDS.md`
`.agents/skills/gframework-doc-refresh/SKILL.md` 中新增硬约束,明确禁止把 inventory、覆盖基线、恢复点、
review backlog、治理批次等 contributor-only 内容写进 `README.md``docs/**`
- 将 `docs/zh-CN/core/index.md``core/cqrs.md``game/index.md`
`abstractions/core-abstractions.md``abstractions/game-abstractions.md``ecs/index.md`
`ecs/arch.md``abstractions/ecs-arch-abstractions.md` 中的 XML 覆盖表述改写为面向读者的“源码阅读入口”
- 顺手收口 `api-reference/index.md``contributor/development-environment.md`
`source-generators/*.md` 中的内部口吻用语,例如 `landing page``验证基线``目标类型基线`
- focused validator 已覆盖本轮触达的 `13` 个公开文档页面并全部通过;站点构建 `cd docs && bun run build`
已通过,仅保留既有大 chunk warning
- 当前分支 `HEAD` 仍与 `origin/main``2de57f5``2026-04-23T23:03:40+08:00`)对齐;在提交本轮工作前,
工作树待提交范围为 `16` 个文件、`224` changed lines距离 `$gframework-batch-boot 75` 的停止阈值仍很远。
- `2026-04-24` 继续在同一 stop condition 下执行第二个低风险批次,集中修复 `docs/zh-CN/core/*.md` 的历史 frontmatter 缺口:
- 已为 `architecture.md``async-initialization.md``command.md``configuration.md``context.md`
`environment.md``events.md``extensions.md``functional.md``ioc.md``localization.md`
`logging.md``model.md``pause.md``pool.md``property.md``query.md``rule.md`
`state-management.md``system.md``utility.md` 补齐 frontmatter
- 顺手修复 `core/ioc.md``xref:System.Threading.ReaderWriterLockSlim` 坏链,以及
`core/state-management.md` 中 4 处缺少 `.md` 后缀的站内链接
- 当前 `docs/zh-CN/core/*.md` 已全部具备 frontmatterfocused validator 对这 `21` 个页面全部通过,`bun run build`
再次通过,仅保留既有大 chunk warning
- 截至当前未提交工作树,`HEAD` 相对 `origin/main` 的累计 branch diff 仍为 `18` 个文件;新增待提交批次为 `21` 个文件、
`126` changed lines合并后仍显著低于 `$gframework-batch-boot 75` 的停止阈值。
- `2026-04-24` 继续在同一 stop condition 下执行第三个低风险批次,集中清理 `docs/zh-CN` 其余“完全缺 frontmatter”的页面
- 已为 `best-practices/*.md` 中缺口页、`contributing.md``faq.md``game/config-system.md`
`getting-started/*.md``godot/coroutine.md`、6 个 `source-generators/*.md``troubleshooting.md`
`tutorials/advanced-patterns.md``tutorials/basic/index.md``tutorials/index.md` 补齐 frontmatter
- 在同批次内修复 `best-practices/multiplayer.md` 的未闭合代码块、`source-generators/*.md` 中缺少 `.md`
后缀的相对链接,以及 `troubleshooting.md` 里 3 处目录索引死链
- 当前 `docs/zh-CN` 已不存在“完全缺 frontmatter”的页面剩余 metadata 热点只剩
`docs/zh-CN/index.md``docs/zh-CN/tutorials/basic/01-07.md``8` 个“已有 frontmatter 但缺 title /
description”的页面
- 本批次落地前,当前分支相对 `origin/main` 的累计 branch diff 为 `39` 个文件;连同本轮工作和 tracking / trace
更新后,预计提交后累计 diff 约为 `63` 个文件,仍低于 `$gframework-batch-boot 75` 的停止阈值
## 迁出的验证记录
- `2026-04-24` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN`
- 结果:失败;暴露出仓库既有的 `53` 个历史文档问题(大量缺少 frontmatter、既有坏链与未标语言代码块不由本轮改动引入因此本轮改用 focused validator 证明任务级结果。
- `2026-04-24` `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Core`
- 结果:通过;确认 `Core` 模块的 README、landing、topic 与 fallback docs 入口仍可解析。
- `2026-04-24` `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Game`
- 结果:通过;确认 `Game` 模块的 README、landing、topic 与 fallback docs 入口仍可解析。
- `2026-04-24` focused validator逐个校验本轮触达页面
- 结果:通过;`docs/zh-CN/abstractions/core-abstractions.md`
`abstractions/ecs-arch-abstractions.md``abstractions/game-abstractions.md`
`api-reference/index.md``contributor/development-environment.md``core/cqrs.md``core/index.md`
`ecs/arch.md``ecs/index.md``game/index.md`
`source-generators/bind-node-signal-generator.md`
`source-generators/cqrs-handler-registry-generator.md`
`source-generators/get-node-generator.md` 的 frontmatter / links / code blocks 全部通过。
- `2026-04-24` `bun run build`(工作目录:`docs/`
- 结果:通过;仅保留既有大 chunk warning。
- `2026-04-24` `python3 - <<'PY' ...`(检查 `docs/zh-CN/core/*.md` frontmatter
- 结果:通过;`docs/zh-CN/core/` 当前所有 Markdown 页面均已带 frontmatter。
- `2026-04-24` focused validator逐个校验 `docs/zh-CN/core/*.md``21` 个页面)
- 结果:通过;过程中暴露并已修复 `core/ioc.md``ReaderWriterLockSlim` 坏链与
`core/state-management.md` 的 4 处站内坏链;剩余仅为既有代码块语言 warning不影响任务级通过。
- `2026-04-24` `bun run build`(工作目录:`docs/`,第二次)
- 结果通过frontmatter 与坏链修复后站点仍可正常构建,仅保留既有大 chunk warning。
- `2026-04-24` focused validator逐个校验第三批触达的 `22` 个页面)
- 结果通过frontmatter、真实坏链与未闭合代码块问题均已修复剩余仅为 `best-practices/architecture-patterns.md`
`best-practices/index.md``contributing.md``troubleshooting.md``tutorials/index.md` 的既有代码块语言 warning。
- `2026-04-24` `bun run build`(工作目录:`docs/`,第三次)
- 结果:通过;第三批 frontmatter 与链接修复后站点仍可正常构建,仅保留既有大 chunk warning。
- `2026-04-23` `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
- 结果通过PR `#272` 处于 `OPEN`latest head commit 存在 1 条 Greptile open thread定位到
`docs/zh-CN/godot/setting.md:75` 的 inline code HTML entity 渲染问题。
- `2026-04-23` `rg -n '`[^`]*&lt;[^`]*`|`[^`]*&gt;[^`]*`' GFramework.Godot.SourceGenerators/README.md GFramework.Godot/README.md README.md docs/zh-CN/api-reference/index.md docs/zh-CN/game/data.md docs/zh-CN/game/serialization.md docs/zh-CN/game/setting.md docs/zh-CN/game/storage.md docs/zh-CN/godot/setting.md docs/zh-CN/godot/storage.md docs/zh-CN/source-generators/index.md`
- 结果:命中 `docs/zh-CN/godot/setting.md:75``docs/zh-CN/godot/storage.md:102` 两处同类写法,均已修正。
- `2026-04-23` `rg -n '`[^`]*&lt;[^`]*`|`[^`]*&gt;[^`]*`' README.md GFramework.* docs/zh-CN -g '*.md'`
- 结果:命中 `docs/zh-CN/core/functional.md``docs/zh-CN/tutorials/functional-programming.md` 共 8 处,已全部修正。
- `2026-04-23` `sed -n '1,260p' .agents/skills/gframework-batch-boot/SKILL.md``sed -n '1,220p' .agents/skills/README.md`
- 结果:确认原文仅描述自然语言 stop condition没有定义数字速记或多阈值 OR 语义;现已补齐。
- `2026-04-23` `rg -n '`GFramework\\.[^`]+/README\\.md`|`docs/zh-CN/[^`]+\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
- 结果:确认 landing / API 导航页仍有一批裸路径仓库入口;本轮已先修复 `getting-started``core``game`
`source-generators``api-reference` 与两个 abstractions 页面。
- `2026-04-23` `rg -n '`GFramework\\.[^`]+/README\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
- 结果:定位第二批专题页导航热点,已修复 `core/cqrs.md``ecs/arch.md``abstractions/ecs-arch-abstractions.md`
`game/scene.md``game/ui.md` 以及 6 个 `source-generators/*.md` 页面。
- `2026-04-23` `bun run build`(工作目录:`docs/`
- 结果:通过;仓库 README 外链改为 GitHub `main` blob 后,不再触发 VitePress dead link仅保留既有大 chunk warning。
## 迁出的下一步
1. 若继续执行 `$gframework-batch-boot 75`,优先处理 `docs/zh-CN/index.md``tutorials/basic/01-07.md``8`
个“已有 frontmatter 但缺 `title` / `description`”的 metadata 缺口。
2. 若后续继续扩展批处理 skill可考虑再补充显式单位写法例如 `75 files 2000 lines`,但当前默认速记已足够覆盖
常见分支阈值场景。
3. 若后续分支继续调整 `Game` persistence runtime、README 或公共 API优先复核 `docs/zh-CN/game/data.md`
`storage.md``serialization.md``setting.md` 与 landing page 是否仍保持同一套职责边界。
4. 若后续分支继续调整 `Godot` generator 接法,优先复核 `GFramework.Godot.SourceGenerators/README.md`
`docs/zh-CN/tutorials/godot-integration.md` 与相关专题页是否仍保持一致。

View File

@ -0,0 +1,167 @@
# Documentation Full Coverage Governance Trace History RP-023 To RP-025
以下内容从 active trace 中迁出,用于保留 `DOCUMENTATION-FULL-COVERAGE-GOV-RP-023`
`DOCUMENTATION-FULL-COVERAGE-GOV-RP-025` 的执行时间线、关键决策与主要验证结果。默认 `boot` 只需要读取
active trace 中的最新恢复点;若需要追溯 `2026-04-23``2026-04-24` 的批处理执行顺序,再回到本归档文件。
## 2026-04-24
### 当前恢复点RP-025
- 继续沿用 `$gframework-batch-boot 75`,基线保持 `origin/main``2de57f5``2026-04-23T23:03:40+08:00`)。
- 本轮目标从“继续治理公开文档边界”切换为“清空 `docs/zh-CN` 中仍然完全缺 frontmatter 的页面,同时把触达页暴露的真实格式错误一并收口”。
- 本轮执行的修复:
- 为 `best-practices``getting-started``source-generators``tutorials` 等目录下共 `22` 个页面补齐
`title` / `description` frontmatter
- 修复 `docs/zh-CN/best-practices/multiplayer.md` 末尾缺失的代码块闭合符
- 修复 `docs/zh-CN/source-generators/*.md``docs/zh-CN/troubleshooting.md` 中一组缺少 `.md` 后缀或目录索引写法不兼容当前 validator 的站内链接
### 当前决策RP-025
- 对文档批处理,优先选择“元数据缺口 + 顺手修复真实结构错误”的组合,而不扩成正文语义刷新或大规模 code fence language 治理。
- 当 focused validator 暴露的是触达页上的真实错误(如坏链、未闭合代码块)时,同批次直接收口;仅把纯 warning 留给下一轮专门治理。
- 本轮结束时,`docs/zh-CN` 已没有“完全缺 frontmatter”的页面下一批最稳定的切片是 `docs/zh-CN/index.md`
`docs/zh-CN/tutorials/basic/01-07.md``8` 个“已有 frontmatter 但缺 `title` / `description`”的页面。
- 当前已提交分支 diff 仍为 `39` 个文件;将本轮工作连同 tracking / trace 提交后,预计累计 branch diff 约为 `63`
个文件,仍低于 `$gframework-batch-boot 75` 的停止阈值。
### 当前验证RP-025
- frontmatter 缺口巡检:
- `for f in $(find docs/zh-CN -type f -name '*.md' | sort); do if ! head -n 5 "$f" | grep -q '^---$'; then echo "$f"; fi; done`
- 结果:本轮前命中 `22` 个页面,当前已全部补齐。
- focused validator
- 逐个校验本轮触达的 `22` 个页面
- 结果:通过;只剩 `best-practices/architecture-patterns.md``best-practices/index.md``contributing.md`
`troubleshooting.md``tutorials/index.md` 的既有代码块语言 warning。
- 站点构建:
- `bun run build`(工作目录:`docs/`
- 结果:通过;仅保留既有大 chunk warning。
- 后续候选扫描:
- `python3 - <<'PY' ...`(扫描已有 frontmatter 但缺 `title` / `description` 的页面)
- 结果:命中 `docs/zh-CN/index.md``docs/zh-CN/tutorials/basic/01-07.md``8` 个页面,可作为下一批 metadata 修复入口。
### 下一步
1. 继续执行 `$gframework-batch-boot 75` 时,优先补齐 `docs/zh-CN/index.md``tutorials/basic/01-07.md`
`title` / `description` 缺口。
2. 若仍有余量,再按目录收口 `best-practices``contributing``troubleshooting``tutorials/index.md`
的代码块语言 warning而不是跨全站混做。
### 当前恢复点RP-024
- 根据用户反馈,将本轮目标重定义为“清理公开文档中的治理盘点式内容,并把同类约束补进仓库规范与 doc-refresh skill”。
- 用户随后补充明确使用 `$gframework-batch-boot 75`,因此继续沿用 `origin/main` 作为固定基线,并把 `75` changed
files 作为主停止条件。
- 本轮执行的修复:
- 在 `AGENTS.md``.agents/skills/_shared/DOCUMENTATION_STANDARDS.md`
`.agents/skills/gframework-doc-refresh/SKILL.md` 中新增公开文档边界规则,禁止把 inventory、覆盖基线、
恢复点、review backlog 和治理批次写入 `README.md``docs/**`
- 将 `docs/zh-CN/core/index.md``core/cqrs.md``game/index.md`
`abstractions/core-abstractions.md``abstractions/game-abstractions.md``ecs/index.md`
`ecs/arch.md``abstractions/ecs-arch-abstractions.md` 的 XML 覆盖 / inventory 段落改写成读者导向的源码阅读入口
- 继续收口 `api-reference/index.md``contributor/development-environment.md`
`source-generators/*.md` 中的内部术语,例如 `landing page``验证基线``目标类型基线`
- 为 `docs/zh-CN/contributor/development-environment.md` 补齐 frontmatter使其满足当前文档规范
### 当前决策RP-024
- 公开文档只承载采用路径、阅读入口、模块边界和可验证示例;治理盘点、覆盖状态和恢复点一律留在 `ai-plan/**`
- 当 XML 治理结果需要体现在公开文档里时,只输出“优先看哪些类型 / 命名空间 / 契约以及为什么”,不输出计数、日期或状态表。
- `$gframework-batch-boot 75` 的基线采用 `origin/main``2de57f5``2026-04-23T23:03:40+08:00`)。
- 由于当前 `HEAD` 仍与 `origin/main` 对齐,分支级 diff 暂时仍为 `0`;提交前工作树待提交范围为 `16` 个文件、
`224` changed lines因此本轮仍远低于 `75` 文件阈值。
- 在完成“公开文档边界”收口后,继续沿同一阈值推进一个新的低风险批次:为 `docs/zh-CN/core/*.md` 历史页面补齐 frontmatter。
- 当 validator 因本轮触达页面暴露真实坏链时,直接在同批次内修复;当只剩历史 warning如缺少代码块语言标记本轮停止扩张。
### 当前验证RP-024
- 同类治理内容巡检:
- `rg -n 'XML Inventory|XML 覆盖基线|XML 状态|基线状态|盘点|治理优先级|审计入口|覆盖基线|恢复点|验证基线|目标类型基线|目标字段基线|类型审计|契约审计|源码 / API' docs/zh-CN README.md -g '*.md'`
- 结果:公开页已无同类命中;剩余 `inventory` 命中仅来自正常代码示例变量名。
- skill 自检:
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Core`
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Game`
- 结果:均通过;代表模块的 README / docs 入口映射仍有效。
- 全量 docs 校验:
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN`
- 结果:失败;暴露 `53` 个仓库既有历史问题(缺少 frontmatter、坏链、未标语言代码块不属于本轮改动。
- focused validator
- 逐个校验本轮触达的 `13` 个公开文档页面
- 结果:全部通过。
- 站点构建:
- `bun run build`(工作目录:`docs/`
- 结果:通过;仅保留既有大 chunk warning。
- `core` frontmatter 波次:
- 已补齐 `docs/zh-CN/core/*.md``21` 个历史页面的 frontmatter。
- 过程中修复 `docs/zh-CN/core/ioc.md``ReaderWriterLockSlim` 坏链,以及
`docs/zh-CN/core/state-management.md` 的 4 处缺少 `.md` 后缀的站内链接。
- `python3 - <<'PY' ...` 检查结果为 `ALL_HAVE_FRONTMATTER`,说明 `docs/zh-CN/core/` 目录当前已无 frontmatter 缺口。
- focused validator 对这 `21` 个页面全部通过;剩余输出仅为既有代码块语言 warning。
- `bun run build` 在修复后再次通过。
- 当前阈值状态:
- `git diff --name-only origin/main...HEAD | wc -l` => `18`
- `git diff --name-only | wc -l` => `21`
- `git diff --numstat` 汇总 => `126` changed lines
- 结论:当前已提交分支 diff 仍为 `18` 个文件,待提交新批次再增加 `21` 个文件;即使提交后也仍低于 `75` 文件阈值。
### 下一步
1. 继续执行 `$gframework-batch-boot 75` 时,优先按目录做历史 frontmatter / code fence language / 坏链修复,而不是把不同风格的文档语义刷新混成一批。
2. 当前批次在 `core` 目录已经不再是“同样机械”的模式,后续若继续应转向其他目录或专门做代码块语言标记治理。
## 2026-04-23
### 当前恢复点RP-023
- 按当前使用反馈继续执行 `documentation-full-coverage-governance` 下的 skill 文档治理。
- 本轮目标定义为“继续沿用上一批的 GitHub 外链策略,收口专题页里的裸路径 README 入口”。
- 本轮执行的修复:
- 将 `docs/zh-CN/core/cqrs.md``ecs/arch.md` 的仓库 README 入口改为 GitHub `main` blob 外链
- 将 `docs/zh-CN/abstractions/ecs-arch-abstractions.md``game/scene.md``game/ui.md` 的回跳 README 入口改为可点击链接
- 将 `docs/zh-CN/source-generators/priority-generator.md``context-aware-generator.md`
`bind-node-signal-generator.md``godot-project-generator.md``get-node-generator.md`
`auto-register-exported-collections-generator.md` 的推荐阅读 README 入口改为可点击链接
- 同步更新 active tracking / trace记录第二批导航治理与新的恢复点
### 当前决策RP-023
- 继续使用 `origin/main` 作为 `$gframework-batch-boot 75` 的固定基线,并以“分支累计 diff 文件数”作为主状态指标。
- 对文档治理类批次,优先选择“导航可达性 / 渲染一致性”这类不改变产品语义的低风险切片。
- 在 docs 页面里出现仓库内 README 路径时,优先使用可点击的相对链接,而不是裸路径代码片段。
- 当 docs 页需要跳转到 `docs/` 外部的 README 时,使用 GitHub `main` 分支 blob 外链,而不是跨出 `docs/` 根目录的相对路径。
- 第二批继续沿用同一外链策略,避免在同一 docs surface 中混用“裸路径 / 相对死链 / GitHub 外链”三套入口风格。
### 当前验证RP-023
- 导航热点巡检:
- `rg -n '`GFramework\\.[^`]+/README\\.md`|`docs/zh-CN/[^`]+\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
- 结果:命中 landing / API 导航页中的裸路径仓库入口,已按本轮批次收口 7 个页面。
- 第二批专题页巡检:
- `rg -n '`GFramework\\.[^`]+/README\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
- 结果:命中 `core/cqrs.md``ecs/arch.md``abstractions/ecs-arch-abstractions.md``game/scene.md`
`game/ui.md` 与 6 个 `source-generators/*.md` 专题页,均已修复。
- 构建校验:
- `bun run build`(工作目录:`docs/`
- 结果:通过;将仓库 README 跳转改为 GitHub `main` blob 外链后,不再触发 VitePress dead link仅保留既有大 chunk warning。
- 当前阈值状态:
- `git diff --name-only origin/main...HEAD | wc -l` => `24`
- `git diff --numstat origin/main...HEAD` 汇总 => `264` changed lines
- 结论:尚未接近 `75` 文件阈值,但剩余命中主要是正文语义性提及,当前批次在低风险模板化导航治理上可先收口。
### 归档摘要RP-022
- 为 `.agents/skills/gframework-batch-boot/SKILL.md``.agents/skills/README.md` 补齐数字速记 stop condition 语义。
- 明确 `$gframework-batch-boot 75` / `75 2000` 默认绑定 `origin/main` 累计 diff 口径。
- 完成第一批 landing / API 导航页 README 外链治理,并通过 `docs/` 站点构建。
### 归档指针
- `ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-validation-history-through-rp-007.md`
- `ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-status-history-through-rp-016.md`
- `ai-plan/public/documentation-full-coverage-governance/archive/traces/documentation-full-coverage-governance-trace-history-through-rp-016.md`
### 下一步
1. 提交并推送本地修正后,再次抓取 PR `#272`,确认 Greptile open thread 是否已在新 head commit 上消失。
2. 若继续执行文档治理批处理,优先排查剩余的非导航型裸路径引用、标题锚点与站内链接热点,而不是扩成跨模块大波次。

View File

@ -12,11 +12,12 @@
## 当前恢复点
- 恢复点编号:`DOCUMENTATION-FULL-COVERAGE-GOV-RP-023`
- 恢复点编号:`DOCUMENTATION-FULL-COVERAGE-GOV-RP-026`
- 当前阶段:`Phase 5 - Governance Maintenance`
- 当前焦点:
- 保持 landing page / API 导航页中的仓库 README 入口可点击,避免读者在 docs 站点里遇到裸路径文本
- 继续按 `origin/main` 分支 diff 阈值做小批量文档治理,优先处理低风险导航 / 渲染热点
- 收口 PR `#282` 的 latest-head review follow-up保持 active tracking / trace 只承载当前恢复入口
- 保持 `README.md``docs/**` 公开页面只承载读者需要的采用信息,不再混入 XML inventory、覆盖基线、恢复点或治理批次说明
- 继续按 `$gframework-batch-boot 75``origin/main` 分支 diff 阈值做小批量文档治理,优先处理低风险 metadata 缺口、坏链与 Markdown 结构问题
- 保持 `Game` persistence docs surface 与当前 `README`、源码、`PersistenceTests` 使用同一套 owner / adoption path 叙述
- 保持 `GFramework.Godot.SourceGenerators/README.md``docs/zh-CN/tutorials/godot-integration.md` 在生命周期接法上的一致性
- 保持 active tracking / trace 只承载当前恢复入口,把阶段细节留在 `archive/`
@ -24,49 +25,27 @@
## 当前状态摘要
- `Core``Ecs.Arch``Cqrs``Game``Godot` 五个模块族当前都已有 README / landing / topic / API 参考层级的已验证入口。
- `2026-04-24` 使用 `$gframework-pr-review` 抓取 PR `#282` 后,确认 latest head commit
`982249151ecf8acdff3e62e664034bf95dfacd75` 当前仍有 `3` 条 CodeRabbit 与 `1` 条 Greptile open thread4 条建议均已在本地复核并纳入当前恢复点。
- 本轮 PR follow-up 仅收口仍然成立的 review 项:
- 将过长的 active tracking / trace 瘦身,并把 `RP-023``RP-025` 的细节迁入 `archive/`
- 将 `docs/zh-CN/core/context.md` 的标题本地化为中文读者友好的写法
- 统一 `docs/zh-CN/troubleshooting.md``/zh-CN/core/architecture``/zh-CN/faq``.md` 链接写法
- `Game` persistence docs surface 当前以 `docs/zh-CN/game/data.md``storage.md``serialization.md``setting.md`
作为最小巡检集合;若后续 README、runtime public API 或 `PersistenceTests` 变动,应优先复核这一组页面。
- `Godot` runtime 与 generator 入口当前以 `GFramework.Godot/README.md`
`GFramework.Godot.SourceGenerators/README.md``docs/zh-CN/godot/index.md`
`docs/zh-CN/source-generators/index.md``docs/zh-CN/tutorials/godot-integration.md` 维持统一 owner / adoption path。
- `2026-04-23` 基于 PR `#272` 的 review follow-up 已完成:
- 为 `docs/zh-CN/game/data.md` 补充 `UnifiedSettingsDataRepository` 的统一文件布局示例
- 为 `GFramework.Godot.SourceGenerators/README.md` 补充手写 `_Ready()` / `_ExitTree()` 时显式调用生成方法的最小样例
- 将过长的 active tracking / trace 瘦身,并把历史摘要迁回 `archive/`
- `2026-04-23` 使用 `$gframework-pr-review` 重新抓取 PR `#272` 后,确认 latest-head review 当前仍有 1 条
Greptile open thread定位到 `docs/zh-CN/godot/setting.md:75` 的 inline code 误写成
`SettingsModel&lt;ISettingsDataRepository&gt;`
- 结合当前 PR 已改动的 `docs/zh-CN/godot/storage.md` 做同类巡检后,确认 `SaveRepository&lt;TSaveData&gt;`
也会在 VitePress code span 中按字面量渲染;两处现已在本地统一改为真实泛型写法。
- `2026-04-23``origin/main``aa879d2``2026-04-23T17:51:41+08:00`)为批处理基线,对
`README.md``GFramework.*``docs/zh-CN/**` 执行同类模式巡检,确认剩余热点仅位于
`docs/zh-CN/core/functional.md``docs/zh-CN/tutorials/functional-programming.md` 共 8 处。
- 上述 8 处 inline code 中的 `Option&lt;T&gt;``Result&lt;T&gt;``Nullable&lt;T&gt;` 已统一改为真实
泛型写法,避免在 VitePress 中显示字面量 HTML entity。
- `2026-04-23` 根据本轮使用反馈,已为 `.agents/skills/gframework-batch-boot/SKILL.md`
`.agents/skills/README.md` 补充数字速记阈值语义:
- `$gframework-batch-boot 75` 默认表示“当前分支全部提交相对远程 `origin/main` 接近 75 个分支 diff 文件时停止”
- `$gframework-batch-boot 75 2000` 默认表示“当前分支全部提交相对远程 `origin/main` 接近 75 个文件或 2000 行变更时停止”
- `75 | 2000` 仅作为可理解的 OR 写法保留,不再作为推荐写法,以避免与 shell pipe 混淆
- `2026-04-23``origin/main``aa879d2``2026-04-23T17:51:41+08:00`)为批处理基线,对
`docs/zh-CN/getting-started/index.md``core/index.md``game/index.md``source-generators/index.md`
`api-reference/index.md``abstractions/core-abstractions.md``abstractions/game-abstractions.md`
做导航可达性修复,把仓库 README / 根 README 裸路径统一改为指向 GitHub `main` 分支的可点击链接。
- 该批次不改变文档语义,只收口 docs 站点中的入口可达性;适合继续作为小步快跑的低风险治理模式。
- `2026-04-23` 在同一基线下继续收口第二批专题页导航热点,已将 `core/cqrs.md``ecs/arch.md`
`abstractions/ecs-arch-abstractions.md``game/scene.md``game/ui.md` 和 6 个
`source-generators/*.md` 专题页里的 README 裸路径统一改为 GitHub `main` blob 外链。
- 截至提交 `8a11720``2026-04-23T21:01:28+08:00`),当前分支相对 `origin/main``aa879d2`)的累计 diff
`24` 个文件、`264` 行,仍低于 `$gframework-batch-boot 75` 的停止阈值;但剩余命中已主要是正文语义性提及,不再适合作为同类批处理。
- 当前剩余的托管侧信号是 GitHub `Title check` 对 PR 标题过泛的 inconclusive 提示;这属于 PR 元数据,不是本地
文件缺陷。
- `2026-04-23``2026-04-24` 的批次细节、验证日志与旧恢复建议已迁入:
`ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-status-history-rp-023-to-rp-025-2026-04-24.md`
## 当前风险
- 当前 `Core` / `Core.Abstractions``Ecs.Arch``Cqrs``Game` 的 XML 治理仍以“类型声明级基线”为主,不等于成员级契约全审计
- 当前 `Core` / `Core.Abstractions``Ecs.Arch``Cqrs``Game` 的 XML 治理证据仍主要来自类型与入口级阅读,不等于成员级契约全审计;这类治理状态只应保留在 `ai-plan/**`,不应再暴露到公开文档。
- `GFramework.Cqrs` 在当前 WSL / dotnet 环境下仍会读取失效的 fallback package folder并在标准 build 中触发
`MSB4276` / `MSB4018`;这是已知环境阻塞,不属于本轮文档回归。
- 当前 WSL 会话里 `git.exe` 可解析但不能执行,应继续使用显式 `--git-dir` / `--work-tree` 绑定作为默认 Git 策略。
- PR `#282``Title check` 仍可能提示标题过泛;这是 GitHub PR 元数据问题,不属于本地文件缺陷。
## 归档指针
@ -74,34 +53,27 @@
`ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-validation-history-through-rp-007.md`
- 阶段状态归档(`RP-001``RP-016`
`ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-status-history-through-rp-016.md`
- 阶段状态归档(`RP-023``RP-025`
`ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-status-history-rp-023-to-rp-025-2026-04-24.md`
- 时间线归档(`RP-001``RP-016`
`ai-plan/public/documentation-full-coverage-governance/archive/traces/documentation-full-coverage-governance-trace-history-through-rp-016.md`
- 时间线归档(`RP-023``RP-025`
`ai-plan/public/documentation-full-coverage-governance/archive/traces/documentation-full-coverage-governance-trace-history-rp-023-to-rp-025-2026-04-24.md`
## 最新验证
- `2026-04-23` `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
- 结果通过PR `#272` 处于 `OPEN`latest head commit 存在 1 条 Greptile open thread定位到
`docs/zh-CN/godot/setting.md:75` 的 inline code HTML entity 渲染问题。
- `2026-04-23` `rg -n '`[^`]*&lt;[^`]*`|`[^`]*&gt;[^`]*`' GFramework.Godot.SourceGenerators/README.md GFramework.Godot/README.md README.md docs/zh-CN/api-reference/index.md docs/zh-CN/game/data.md docs/zh-CN/game/serialization.md docs/zh-CN/game/setting.md docs/zh-CN/game/storage.md docs/zh-CN/godot/setting.md docs/zh-CN/godot/storage.md docs/zh-CN/source-generators/index.md`
- 结果:命中 `docs/zh-CN/godot/setting.md:75``docs/zh-CN/godot/storage.md:102` 两处同类写法,均已修正。
- `2026-04-23` `rg -n '`[^`]*&lt;[^`]*`|`[^`]*&gt;[^`]*`' README.md GFramework.* docs/zh-CN -g '*.md'`
- 结果:命中 `docs/zh-CN/core/functional.md``docs/zh-CN/tutorials/functional-programming.md` 共 8 处,已全部修正。
- `2026-04-23` `sed -n '1,260p' .agents/skills/gframework-batch-boot/SKILL.md``sed -n '1,220p' .agents/skills/README.md`
- 结果:确认原文仅描述自然语言 stop condition没有定义数字速记或多阈值 OR 语义;现已补齐。
- `2026-04-23` `rg -n '`GFramework\\.[^`]+/README\\.md`|`docs/zh-CN/[^`]+\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
- 结果:确认 landing / API 导航页仍有一批裸路径仓库入口;本轮已先修复 `getting-started``core``game`
`source-generators``api-reference` 与两个 abstractions 页面。
- `2026-04-23` `rg -n '`GFramework\\.[^`]+/README\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
- 结果:定位第二批专题页导航热点,已修复 `core/cqrs.md``ecs/arch.md``abstractions/ecs-arch-abstractions.md`
`game/scene.md``game/ui.md` 以及 6 个 `source-generators/*.md` 页面。
- `2026-04-23` `bun run build`(工作目录:`docs/`
- 结果:通过;仓库 README 外链改为 GitHub `main` blob 后,不再触发 VitePress dead link仅保留既有大 chunk warning。
- `2026-04-24` `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
- 结果通过PR `#282` 处于 `OPEN`latest head commit 有 `3` 条 CodeRabbit 与 `1` 条 Greptile open thread测试汇总为 `2156 passed`,仅剩 `Title check` 的 inconclusive PR 元数据提示。
- `2026-04-24` `rg -n --pcre2 '\\]\\(/zh-CN/[^)]+(?<!\\.md)\\)' docs/zh-CN/troubleshooting.md`
- 结果:当前无命中;`/zh-CN/core/architecture``/zh-CN/faq` 已统一补成显式 `.md` 链接。
- `2026-04-24` `bun run build`(工作目录:`docs/`
- 结果:通过;文档标题本地化、站内链接修正与 `ai-plan` 归档瘦身落地后站点仍可正常构建,仅保留既有大 chunk warning。
## 下一步
1. 若继续执行文档治理批处理,优先改做标题锚点、站内链接和少量非导航型裸路径引用的逐页复核,而不是继续按统一模板机械替换。
2. 若后续继续扩展批处理 skill可考虑再补充显式单位写法例如 `75 files 2000 lines`,但当前默认速记已足够覆盖
常见分支阈值场景
1. 若继续执行 `$gframework-batch-boot 75`,优先处理 `docs/zh-CN/index.md``tutorials/basic/01-07.md``8`
个“已有 frontmatter 但缺 `title` / `description`”的 metadata 缺口。
2. 推送当前 follow-up commit 后,再次执行 `$gframework-pr-review`,确认 PR `#282` 的 unresolved review threads 是否已在新 head commit 上消失。
3. 若后续分支继续调整 `Game` persistence runtime、README 或公共 API优先复核 `docs/zh-CN/game/data.md`
`storage.md``serialization.md``setting.md` 与 landing page 是否仍保持同一套职责边界。
4. 若后续分支继续调整 `Godot` generator 接法,优先复核 `GFramework.Godot.SourceGenerators/README.md`

View File

@ -1,57 +1,43 @@
# Documentation Full Coverage Governance Trace
## 2026-04-23
## 2026-04-24
### 当前恢复点RP-023
### 当前恢复点RP-026
- 按当前使用反馈继续执行 `documentation-full-coverage-governance` 下的 skill 文档治理。
- 本轮目标定义为“继续沿用上一批的 GitHub 外链策略,收口专题页里的裸路径 README 入口”。
- 本轮执行的修复:
- 将 `docs/zh-CN/core/cqrs.md``ecs/arch.md` 的仓库 README 入口改为 GitHub `main` blob 外链
- 将 `docs/zh-CN/abstractions/ecs-arch-abstractions.md``game/scene.md``game/ui.md` 的回跳 README 入口改为可点击链接
- 将 `docs/zh-CN/source-generators/priority-generator.md``context-aware-generator.md`
`bind-node-signal-generator.md``godot-project-generator.md``get-node-generator.md`
`auto-register-exported-collections-generator.md` 的推荐阅读 README 入口改为可点击链接
- 同步更新 active tracking / trace记录第二批导航治理与新的恢复点
- 使用 `$gframework-pr-review` 抓取 PR `#282`,确认 latest head commit
`982249151ecf8acdff3e62e664034bf95dfacd75` 当前仍有 `3` 条 CodeRabbit 与 `1` 条 Greptile open thread。
- 按“只处理 latest-head unresolved threads 中仍成立的问题”的原则,本轮仅收口 4 条本地可复现的 follow-up
- 将 `tracking.md``trace.md` 的活动入口瘦身,并把 `RP-023``RP-025` 的细节迁入新 archive 文件
- 将 `docs/zh-CN/core/context.md` 的标题与主标题本地化为 `上下文Context`
- 将 `docs/zh-CN/troubleshooting.md``/zh-CN/core/architecture``/zh-CN/faq` 统一补成显式 `.md` 链接
### 当前决策RP-023
### 当前决策RP-026
- 继续使用 `origin/main` 作为 `$gframework-batch-boot 75` 的固定基线,并以“分支累计 diff 文件数”作为主状态指标。
- 对文档治理类批次,优先选择“导航可达性 / 渲染一致性”这类不改变产品语义的低风险切片。
- 在 docs 页面里出现仓库内 README 路径时,优先使用可点击的相对链接,而不是裸路径代码片段。
- 当 docs 页需要跳转到 `docs/` 外部的 README 时,使用 GitHub `main` 分支 blob 外链,而不是跨出 `docs/` 根目录的相对路径。
- 第二批继续沿用同一外链策略,避免在同一 docs surface 中混用“裸路径 / 相对死链 / GitHub 外链”三套入口风格。
- PR review follow-up 继续遵守“先本地验证,再决定是否修复”;对已经过时或无法在当前分支复现的评论不做追随式修改。
- active `ai-plan` 入口只保留当前恢复点、活动事实、风险、最新验证与下一步;批次细节统一迁入 `archive/`
- `docs/zh-CN` 页面应优先使用中文标题;同一帮助块中的绝对站内链接应保持一致的显式 `.md` 写法。
### 当前验证RP-023
### 当前验证RP-026
- 导航热点巡检:
- `rg -n '`GFramework\\.[^`]+/README\\.md`|`docs/zh-CN/[^`]+\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
- 结果:命中 landing / API 导航页中的裸路径仓库入口,已按本轮批次收口 7 个页面。
- 第二批专题页巡检:
- `rg -n '`GFramework\\.[^`]+/README\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
- 结果:命中 `core/cqrs.md``ecs/arch.md``abstractions/ecs-arch-abstractions.md``game/scene.md`
`game/ui.md` 与 6 个 `source-generators/*.md` 专题页,均已修复。
- 构建校验:
- PR review 抓取:
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
- 结果通过PR `#282` 处于 `OPEN`,最新 review 线程与测试状态已成功解析,测试汇总为 `2156 passed`
- 链接巡检:
- `rg -n --pcre2 '\\]\\(/zh-CN/[^)]+(?<!\\.md)\\)' docs/zh-CN/troubleshooting.md`
- 结果:当前无命中;`/zh-CN/core/architecture``/zh-CN/faq` 已统一补成显式 `.md` 链接。
- 站点构建:
- `bun run build`(工作目录:`docs/`
- 结果:通过;将仓库 README 跳转改为 GitHub `main` blob 外链后,不再触发 VitePress dead link仅保留既有大 chunk warning。
- 当前阈值状态:
- `git diff --name-only origin/main...HEAD | wc -l` => `24`
- `git diff --numstat origin/main...HEAD` 汇总 => `264` changed lines
- 结论:尚未接近 `75` 文件阈值,但剩余命中主要是正文语义性提及,当前批次在低风险模板化导航治理上可先收口。
### 归档摘要RP-022
- 为 `.agents/skills/gframework-batch-boot/SKILL.md``.agents/skills/README.md` 补齐数字速记 stop condition 语义。
- 明确 `$gframework-batch-boot 75` / `75 2000` 默认绑定 `origin/main` 累计 diff 口径。
- 完成第一批 landing / API 导航页 README 外链治理,并通过 `docs/` 站点构建。
- 结果:通过;本轮文档与 `ai-plan` 调整后站点仍可正常构建,仅保留既有大 chunk warning。
### 归档指针
- `ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-validation-history-through-rp-007.md`
- `ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-status-history-through-rp-016.md`
- `ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-status-history-rp-023-to-rp-025-2026-04-24.md`
- `ai-plan/public/documentation-full-coverage-governance/archive/traces/documentation-full-coverage-governance-trace-history-through-rp-016.md`
- `ai-plan/public/documentation-full-coverage-governance/archive/traces/documentation-full-coverage-governance-trace-history-rp-023-to-rp-025-2026-04-24.md`
### 下一步
1. 提交并推送本地修正后,再次抓取 PR `#272`,确认 Greptile open thread 是否已在新 head commit 上消失。
2. 若继续执行文档治理批处理,优先排查剩余的非导航型裸路径引用、标题锚点与站内链接热点,而不是扩成跨模块大波次
1. 推送当前 follow-up commit 后,再次执行 `$gframework-pr-review`,确认 PR `#282` 的 unresolved review threads 是否已在新 head commit 上消失。
2. 若继续执行 `$gframework-batch-boot 75`,优先处理 `docs/zh-CN/index.md``tutorials/basic/01-07.md``8` 个“已有 frontmatter 但缺 `title` / `description`”的 metadata 缺口

View File

@ -1,6 +1,6 @@
---
title: Core Abstractions
description: GFramework.Core.Abstractions 的契约边界、包关系与 XML 阅读重点。
description: GFramework.Core.Abstractions 的契约边界、包关系与源码阅读重点。
---
# Core Abstractions
@ -70,29 +70,28 @@ public sealed class DiagnosticsFeature
## XML 阅读重点
如果你在做契约审计、采用设计或扩展适配,优先核对这些类型族的 XML 文档:
如果你在做契约确认、采用设计或扩展适配,优先核对这些类型族的 XML 文档:
- 架构与模块入口:`IArchitecture``IArchitectureContext``IServiceModule`
- 运行时基础设施:`IIocContainer``ILogger``IResourceManager``IConfigurationManager`
- 状态与并发能力:`IStateMachine``IStore``IAsyncKeyLockManager``ITimeProvider`
- 迁移与组合边界:`ICommandExecutor``IQueryExecutor``ICqrsRuntime`
## XML 覆盖基线
## 契约族阅读入口
下面这份 inventory 记录的是 `2026-04-22``GFramework.Core.Abstractions` 做的一轮轻量 XML 盘点结果:只统计公开 /
内部类型声明是否带 XML 注释,用来建立契约层阅读入口;成员级参数、返回值、异常与生命周期说明仍需要后续波次继续细化。
如果你要回到源码 XML 文档确认契约,请优先看下面这些族群:
| 契约族 | 基线状态 | 代表类型 | 阅读重点 |
| --- | --- | --- | --- |
| `Architectures/` | `12/12` 个类型声明已带 XML 注释 | `IArchitecture`、`IArchitectureContext``IArchitectureServices``IServiceModule` | 架构上下文、服务访问面与模块安装 / 生命周期约束 |
| `Lifecycle/` `Registries/` | `8/8` 个类型声明已带 XML 注释 | `ILifecycle`、`IAsyncInitializable``IRegistry<T, TR>``KeyValueRegistryBase<TKey, TValue>` | 初始化 / 销毁阶段和注册表抽象边界 |
| `Command/` `Query/` `Cqrs/` | `10/10` 个类型声明已带 XML 注释 | `ICommandExecutor`、`IAsyncCommand<TResult>``IQueryExecutor``ICqrsRuntime` | 旧命令 / 查询接口与新请求模型之间的兼容和迁移边界 |
| `Events/` `Property/` | `10/10` 个类型声明已带 XML 注释 | `IEventBus`、`IEventFilter<T>``IBindableProperty<T>``IReadonlyBindableProperty<T>` | 事件传播、过滤、解绑对象和属性订阅语义 |
| `State/` `StateManagement/` | `15/15` 个类型声明已带 XML 注释 | `IStateMachine`、`IAsyncState``IStore<TState>``IStoreMiddleware<TState>` | 状态机契约与 Store 的 reducer / middleware / diagnostics 边界 |
| `Coroutine/` `Time/` `Pause/` `Concurrency/` | `17/17` 个类型声明已带 XML 注释 | `IYieldInstruction`、`ICoroutineStatistics``ITimeProvider``IPauseStackManager``IAsyncKeyLockManager` | 调度模型、时间源、暂停栈和异步锁契约 |
| `Resource/` `Pool/` `Logging/` `Localization/` | `27/27` 个类型声明已带 XML 注释 | `IResourceManager`、`IObjectPoolSystem``ILogger``IStructuredLogger``ILocalizationManager` | 资源 / 池化 / 日志 / 本地化这些基础设施的宿主责任 |
| `Configuration/` `Environment/` `Data/` `Serializer/` `Storage/` `Versioning/` | `7/7` 个类型声明已带 XML 注释 | `IConfigurationManager`、`IEnvironment``ILoadableFrom<T>``ISerializer``IStorage``IVersioned` | 配置、环境、序列化和持久化边界,以及谁负责具体实现 |
| `Bases/` `Controller/` `Model/` `Systems/` `Utility/` `Rule/` `Enums/` `Properties/` | `19/19` 个类型声明已带 XML 注释 | `IPrioritized`、`IController``IModel``ISystem``IContextUtility``ArchitecturePhase` | 基础角色接口、辅助值对象和架构属性键的复用方式 |
| 契约族 | 代表类型 | 建议先确认什么 |
| --- | --- | --- |
| `Architectures/` | `IArchitecture`、`IArchitectureContext``IArchitectureServices``IServiceModule` | 架构上下文、服务访问面与模块安装 / 生命周期约束 |
| `Lifecycle/` `Registries/` | `ILifecycle`、`IAsyncInitializable``IRegistry<T, TR>``KeyValueRegistryBase<TKey, TValue>` | 初始化 / 销毁阶段和注册表抽象边界 |
| `Command/` `Query/` `Cqrs/` | `ICommandExecutor`、`IAsyncCommand<TResult>``IQueryExecutor``ICqrsRuntime` | 旧命令 / 查询接口与新请求模型之间的兼容和迁移边界 |
| `Events/` `Property/` | `IEventBus`、`IEventFilter<T>``IBindableProperty<T>``IReadonlyBindableProperty<T>` | 事件传播、过滤、解绑对象和属性订阅语义 |
| `State/` `StateManagement/` | `IStateMachine`、`IAsyncState``IStore<TState>``IStoreMiddleware<TState>` | 状态机契约与 Store 的 reducer / middleware / diagnostics 边界 |
| `Coroutine/` `Time/` `Pause/` `Concurrency/` | `IYieldInstruction`、`ICoroutineStatistics``ITimeProvider``IPauseStackManager``IAsyncKeyLockManager` | 调度模型、时间源、暂停栈和异步锁契约 |
| `Resource/` `Pool/` `Logging/` `Localization/` | `IResourceManager`、`IObjectPoolSystem``ILogger``IStructuredLogger``ILocalizationManager` | 资源 / 池化 / 日志 / 本地化这些基础设施的宿主责任 |
| `Configuration/` `Environment/` `Data/` `Serializer/` `Storage/` `Versioning/` | `IConfigurationManager`、`IEnvironment``ILoadableFrom<T>``ISerializer``IStorage``IVersioned` | 配置、环境、序列化和持久化边界,以及谁负责具体实现 |
| `Bases/` `Controller/` `Model/` `Systems/` `Utility/` `Rule/` `Enums/` `Properties/` | `IPrioritized`、`IController``IModel``ISystem``IContextUtility``ArchitecturePhase` | 基础角色接口、辅助值对象和架构属性键的复用方式 |
## 阅读顺序

View File

@ -32,13 +32,13 @@ description: GFramework.Ecs.Arch.Abstractions 的契约边界、包关系和最
| `IArchSystemAdapter<T>` | 让 ECS 系统适配到 `ISystem` 生命周期 |
| `ArchOptions` | 承载 `WorldCapacity``EnableStatistics``Priority` 等配置 |
## 类型族级 XML Inventory
## 契约阅读入口
| 类型族 | 代表类型 | XML 状态 | 阅读重点 |
| --- | --- | --- | --- |
| 模块契约 | `IArchEcsModule` | 已覆盖 | 统一更新入口、宿主循环边界 |
| 系统契约 | `IArchSystemAdapter<T>` | 已覆盖 | 只依赖更新接口而不绑定默认 runtime |
| 配置对象 | `ArchOptions` | 已覆盖 | 共享配置字段与跨程序集采用边界 |
| 类型族 | 代表类型 | 建议先确认什么 |
| --- | --- | --- |
| 模块契约 | `IArchEcsModule` | 统一更新入口、宿主循环边界 |
| 系统契约 | `IArchSystemAdapter<T>` | 只依赖更新接口而不绑定默认 runtime |
| 配置对象 | `ArchOptions` | 共享配置字段与跨程序集采用边界 |
## 最小接入路径

View File

@ -1,6 +1,6 @@
---
title: Game Abstractions
description: GFramework.Game.Abstractions 的契约边界、包关系与 XML 阅读重点。
description: GFramework.Game.Abstractions 的契约边界、包关系与源码阅读重点。
---
# Game Abstractions
@ -37,19 +37,18 @@ description: GFramework.Game.Abstractions 的契约边界、包关系与 XML 阅
| `Routing/` | `IRoute``IRouteContext``IRouteGuard<TRoute>`,作为 Scene / UI 共享的路由基础约定 |
| `Storage/` `Asset/` `Enums/` | 文件存储角色、资源注册表,以及转场 / UI 层级 / 输入动作等跨层枚举 |
## XML 覆盖基线
## 契约族阅读入口
下面这份 inventory 记录的是 `2026-04-23``GFramework.Game.Abstractions` 做的一轮轻量 XML 盘点结果:只统计公开 /
内部类型声明是否带 XML 注释,用来建立契约层阅读入口;成员级参数、返回值、异常和生命周期说明仍需要后续 API 波次继续细化。
如果你要回到源码 XML 文档确认契约,请优先看下面这些族群:
| 契约族 | 基线状态 | 代表类型 | 阅读重点 |
| --- | --- | --- | --- |
| `Config/` | `7/7` 个类型声明已带 XML 注释 | `IConfigLoader`、`IConfigRegistry``IConfigTable<TKey, TValue>``ConfigLoadException` | 配置表注册、只读访问和失败诊断边界 |
| `Data/` | `14/14` 个类型声明已带 XML 注释 | `IDataRepository`、`ISettingsDataRepository``ISaveRepository<TSaveData>``DataRepositoryOptions` | 业务数据、统一设置文件、槽位存档与迁移契约 |
| `Setting/` | `12/12` 个类型声明已带 XML 注释 | `ISettingsData`、`ISettingsModel``ISettingsSystem``LocalizationSettings` | 设置生命周期、应用语义、迁移接口和内置设置对象 |
| `Scene/` | `14/14` 个类型声明已带 XML 注释 | `IScene`、`ISceneRouter``ISceneFactory``SceneTransitionEvent` | 场景行为、工厂 / root 边界和转场模型 |
| `UI/` | `19/19` 个类型声明已带 XML 注释 | `IUiPage`、`IUiRouter``IUiFactory``UiInteractionProfile``UiTransitionHandlerOptions` | 页面栈、层级 UI、输入动作和 UI 转场契约 |
| `Routing/` `Storage/` `Asset/` `Enums/` | `13/13` 个类型声明已带 XML 注释 | `IRoute`、`IRouteContext``IFileStorage``IAssetRegistry<T>``UiLayer``SceneTransitionType` | 公共路由上下文、存储角色、资源注册表与共享枚举 |
| 契约族 | 代表类型 | 建议先确认什么 |
| --- | --- | --- |
| `Config/` | `IConfigLoader`、`IConfigRegistry``IConfigTable<TKey, TValue>``ConfigLoadException` | 配置表注册、只读访问和失败诊断边界 |
| `Data/` | `IDataRepository`、`ISettingsDataRepository``ISaveRepository<TSaveData>``DataRepositoryOptions` | 业务数据、统一设置文件、槽位存档与迁移契约 |
| `Setting/` | `ISettingsData`、`ISettingsModel``ISettingsSystem``LocalizationSettings` | 设置生命周期、应用语义、迁移接口和内置设置对象 |
| `Scene/` | `IScene`、`ISceneRouter``ISceneFactory``SceneTransitionEvent` | 场景行为、工厂 / root 边界和转场模型 |
| `UI/` | `IUiPage`、`IUiRouter``IUiFactory``UiInteractionProfile``UiTransitionHandlerOptions` | 页面栈、层级 UI、输入动作和 UI 转场契约 |
| `Routing/` `Storage/` `Asset/` `Enums/` | `IRoute`、`IRouteContext``IFileStorage``IAssetRegistry<T>``UiLayer``SceneTransitionType` | 公共路由上下文、存储角色、资源注册表与共享枚举 |
## 最小接入路径

View File

@ -18,7 +18,7 @@ description: GFramework 的 API 阅读入口,按模块映射 README、专题
### 想确认“该装哪个包、先看哪类 API”
先读模块 README再读对应 landing page
先读模块 README再读对应栏目入口页
- 入门入口:[`../getting-started/index.md`](../getting-started/index.md)
- 根模块地图:仓库根 [`README.md`](https://github.com/GeWuYou/GFramework/blob/main/README.md)
@ -51,7 +51,7 @@ description: GFramework 的 API 阅读入口,按模块映射 README、专题
### 先看教程和专题页的情况
- 你要的是最小接入路径,而不是逐个类型审计
- 你要的是最小接入路径,而不是逐个类型展开阅读
- 你想确认模块组合方式、目录约定和推荐接线顺序
- 你在做从旧入口迁移到新入口的采用决策

View File

@ -1,3 +1,8 @@
---
title: 架构设计模式指南
description: 围绕 GFramework 常见架构模式的职责划分、适用场景与组合建议。
---
# 架构设计模式指南
> 全面介绍 GFramework 中的架构设计模式,帮助你构建清晰、可维护、可扩展的游戏架构。
@ -3440,4 +3445,4 @@ public void GetPlayerStatsQuery_ShouldReturnCorrectStats()
**文档版本**: 2.0.0
**最后更新**: 2026-03-07
**作者**: GFramework Team
**作者**: GFramework Team

View File

@ -1,3 +1,8 @@
---
title: 错误处理最佳实践
description: 总结 GFramework 项目中的错误建模、异常处理、日志记录与恢复策略。
---
# 错误处理最佳实践
> 本指南介绍 GFramework 中的错误处理模式和最佳实践,帮助你构建健壮、可维护的游戏应用。

View File

@ -1,3 +1,8 @@
---
title: 最佳实践
description: 汇总使用 GFramework 时的架构、性能、错误处理与多人游戏等实践建议。
---
# 最佳实践
本文档总结了使用 GFramework 的最佳实践和设计模式。

View File

@ -1,3 +1,8 @@
---
title: 多人游戏架构指南
description: 面向 GFramework 多人游戏项目的架构拆分、同步策略与网络优化建议。
---
# 多人游戏架构指南
> 基于 GFramework 架构设计高性能、可扩展的多人游戏系统。
@ -544,3 +549,4 @@ public class ClientPresentationSystem : AbstractSystem
ShowKillFeed(e.KillerId, e.PlayerId);
}
}
```

View File

@ -1,3 +1,8 @@
---
title: 性能优化指南
description: 整理 GFramework 项目在对象池、事件、协程与资源管理上的性能优化建议。
---
# 性能优化指南
> 全面的性能优化策略和最佳实践,帮助你构建高性能的游戏应用。

View File

@ -1,3 +1,8 @@
---
title: 贡献指南
description: 说明参与 GFramework 仓库贡献时的协作方式、提交流程与社区规范。
---
# 贡献指南
欢迎为 GFramework 贡献代码!本指南将帮助你了解如何参与项目开发。

View File

@ -1,12 +1,17 @@
---
title: 开发环境能力清单
description: 说明 GFramework 当前开发和 AI 协作依赖的环境能力、推荐工具与刷新方式。
---
# 开发环境能力清单
这份文档只记录对 `GFramework` 当前开发和 AI 协作真正有用的环境能力,不收录与本项目无关的系统工具。
如果某个工具没有出现在这里默认表示它对当前仓库不是必需项AI 也不应因为“系统里刚好装了”就优先使用它。
## 当前环境基线
## 当前验证环境
当前仓库验证基线是
当前仓库主要在下面这组环境中验证:
- **运行环境**WSL2
- **发行版**Ubuntu 24.04 LTS
@ -114,4 +119,4 @@ LLM 索引文件与文档站一起部署在 GitHub Pages遵循 `docs/.vitepre
- 当前仓库构建、测试、文档或验证直接依赖它
- AI 在当前仓库中会高频使用,且能明显提升效率
- 新贡献者配置当前仓库开发环境时确实需要知道它
- 不满足上述条件的工具,不写入文档,也不写入 `.ai/environment/tools.raw.yaml` / `.ai/environment/tools.ai.yaml`
- 不满足上述条件的工具,不写入文档,也不写入 `.ai/environment/tools.raw.yaml` / `.ai/environment/tools.ai.yaml`

View File

@ -1,3 +1,8 @@
---
title: Architecture
description: 说明 GFramework.Core 的 Architecture 入口、生命周期职责与最常用注册 API。
---
# Architecture
`Architecture``GFramework.Core` 的运行时入口。它负责三件事:

View File

@ -1,3 +1,8 @@
---
title: 异步初始化指南
description: 说明 GFramework.Core 异步初始化接口、生命周期顺序与常见接入方式。
---
# 异步初始化指南
## 概述

View File

@ -1,3 +1,8 @@
---
title: Command
description: 说明 GFramework.Core.Command 旧命令体系的兼容定位、可用基类与当前使用约束。
---
# Command
本页只说明 `GFramework.Core.Command` 里的旧命令体系。

View File

@ -1,3 +1,8 @@
---
title: Configuration 包使用说明
description: 说明 GFramework.Core 的 Configuration 包、线程安全配置管理能力与核心接口。
---
# Configuration 包使用说明
## 概述

View File

@ -1,4 +1,9 @@
# Context
---
title: 上下文Context
description: 说明 IArchitectureContext 与 ArchitectureContext 的统一上下文入口和当前推荐用法。
---
# 上下文Context
`IArchitectureContext` 是框架的统一上下文入口。

View File

@ -1,6 +1,6 @@
---
title: CQRS
description: Cqrs 模块族的运行时、契约层、生成器入口,以及 XML / API 阅读链路。
description: Cqrs 模块族的运行时、契约层、生成器入口,以及源码与 API 阅读链路。
---
# CQRS
@ -169,17 +169,17 @@ RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
- 在维护历史代码:允许继续使用旧 Command / Query
- 在写新功能或新模块:优先使用 CQRS
## XML 覆盖基线
## 源码阅读入口
下面这份 inventory 记录的是 `2026-04-22``Cqrs` 家族做的一轮轻量 XML 盘点结果:只统计当前运行时、契约层和生成器入口中的类型声明级 XML 覆盖,用来校对 README、landing page 与 API 入口,不把它表述成成员级契约全审计。
如果你需要直接回到源码确认 CQRS 契约,建议按下面这几组入口阅读:
| 类型族 | 基线状态 | 代表类型 | 阅读重点 |
| --- | --- | --- | --- |
| `GFramework.Cqrs.Abstractions/Cqrs/` | `20/20` 个类型声明已带 XML 注释 | `ICqrsRuntime`、`ICqrsHandlerRegistrar``IPipelineBehavior<,>``IRequestHandler<,>``Unit` | 先看请求、处理器和 runtime seam 的最小契约 |
| `GFramework.Cqrs/Command` `Query` `Notification` `Request` `Extensions` | `7/7` 个类型声明已带 XML 注释 | `CommandBase<TInput, TResponse>`、`QueryBase<TInput, TResponse>``NotificationBase<TInput>``ContextAwareCqrsExtensions` | 业务侧常用基类和上下文发送入口 |
| `GFramework.Cqrs/Cqrs/` | `12/12` 个类型声明已带 XML 注释 | `AbstractCommandHandler<,>`、`AbstractQueryHandler<,>``AbstractNotificationHandler<>``LoggingBehavior<,>` | 默认处理器基类、上下文注入与行为管道 |
| `GFramework.Cqrs` 根入口与 `Internal/` | `19/19` 个类型声明已带 XML 注释 | `CqrsRuntimeFactory`、`ICqrsHandlerRegistry``CqrsHandlerRegistryAttribute``CqrsReflectionFallbackAttribute``DefaultCqrsRegistrationService` | runtime 创建入口、registry 协议、fallback 语义和程序集去重规则 |
| `GFramework.Cqrs.SourceGenerators/Cqrs/` | `3/3` 个类型声明已带 XML 注释 | `CqrsHandlerRegistryGenerator`、`RuntimeTypeReferenceSpec``OrderedRegistrationKind` | 生成注册器、精确 type lookup 和 fallback 诊断边界 |
| 类型族 | 代表类型 | 建议先确认什么 |
| --- | --- | --- |
| `GFramework.Cqrs.Abstractions/Cqrs/` | `ICqrsRuntime`、`ICqrsHandlerRegistrar``IPipelineBehavior<,>``IRequestHandler<,>``Unit` | 请求、处理器和 runtime seam 的最小契约 |
| `GFramework.Cqrs/Command` `Query` `Notification` `Request` `Extensions` | `CommandBase<TInput, TResponse>`、`QueryBase<TInput, TResponse>``NotificationBase<TInput>``ContextAwareCqrsExtensions` | 业务侧常用基类和上下文发送入口 |
| `GFramework.Cqrs/Cqrs/` | `AbstractCommandHandler<,>`、`AbstractQueryHandler<,>``AbstractNotificationHandler<>``LoggingBehavior<,>` | 默认处理器基类、上下文注入与行为管道 |
| `GFramework.Cqrs` 根入口与 `Internal/` | `CqrsRuntimeFactory`、`ICqrsHandlerRegistry``CqrsHandlerRegistryAttribute``CqrsReflectionFallbackAttribute``DefaultCqrsRegistrationService` | runtime 创建入口、registry 协议、fallback 语义和程序集去重规则 |
| `GFramework.Cqrs.SourceGenerators/Cqrs/` | `CqrsHandlerRegistryGenerator`、`RuntimeTypeReferenceSpec``OrderedRegistrationKind` | 生成注册器、精确 type lookup 和 fallback 诊断边界 |
## 继续阅读

View File

@ -1,3 +1,8 @@
---
title: Environment 包使用说明
description: 说明 GFramework.Core 的 Environment 包、运行时环境键值存储与核心接口。
---
# Environment 包使用说明
## 概述
@ -215,4 +220,4 @@ public class GoodExampleSystem : AbstractSystem
- [`architecture`](./architecture.md) - 在架构中使用环境配置
- [`rule`](./rule.md) - 环境基类继承自 ContextAwareBase
- [`ioc`](./ioc.md) - 环境值可通过IoC容器管理
- [`ioc`](./ioc.md) - 环境值可通过IoC容器管理

View File

@ -1,3 +1,8 @@
---
title: Events
description: 说明 GFramework.Core.Events 的轻量广播模型、安装方式与常用事件入口。
---
# Events
`GFramework.Core.Events` 是架构内的轻量广播层。它适合表达“某件事已经发生”的运行时信号、模块间松耦合通知,

View File

@ -1,3 +1,8 @@
---
title: Extensions 包使用说明
description: 说明 GFramework.Core.Extensions 常用扩展方法的分类、用途与访问入口。
---
# Extensions 包使用说明
## 概述
@ -549,4 +554,4 @@ public class AchievementSystem : AbstractSystem
- [`events`](./events.md) - 事件注册和 Or 组合扩展
- [`model`](./model.md) - 模型获取扩展
- [`system`](./system.md) - 系统获取扩展
- [`utility`](./utility.md) - 工具获取扩展
- [`utility`](./utility.md) - 工具获取扩展

View File

@ -1,3 +1,8 @@
---
title: 函数式编程指南
description: 说明 GFramework.Core 的 Option、Result 与函数式工具在业务代码中的用法。
---
# 函数式编程指南
## 概述

View File

@ -1,6 +1,6 @@
---
title: Core
description: GFramework.Core 与 GFramework.Core.Abstractions 的运行时入口、采用顺序和 XML 阅读导航。
description: GFramework.Core 与 GFramework.Core.Abstractions 的运行时入口、采用顺序和源码阅读导航。
---
# Core
@ -82,22 +82,21 @@ dotnet add package GeWuYou.GFramework.Core.Abstractions
统一入口见 [`../api-reference/index.md`](../api-reference/index.md)。
## XML 覆盖基线
## 源码阅读入口
下面这份 inventory 记录的是 `2026-04-22``GFramework.Core` 做的一轮轻量 XML 盘点结果:只统计顶层目录中的公开 /
内部类型声明是否带 XML 注释,用来确认阅读入口和治理优先级;成员级 ``<param>``、``<returns>``、异常语义与线程说明仍需要继续细审。
如果你准备直接回到源码和 XML 文档确认契约,建议按能力域分批阅读,而不是按文件数量排查:
| 类型族 | 基线状态 | 代表类型 | 阅读重点 |
| --- | --- | --- | --- |
| `Architectures/` | `16/16` 个类型声明已带 XML 注释 | `Architecture`、`ArchitectureContext``ArchitectureLifecycle``ArchitecturePhaseCoordinator` | 架构启动、模块安装、阶段切换和上下文暴露边界 |
| `Services/` | `6/6` 个类型声明已带 XML 注释 | `ServiceModuleManager`、`CommandExecutorModule``CqrsRuntimeModule` | 服务模块的注册顺序、销毁语义和默认接线 |
| `Command/` `Query/` | `15/15` 个类型声明已带 XML 注释 | `CommandExecutor`、`AsyncQueryExecutor``AbstractCommand<TInput>``AbstractQuery<TResult>` | 看旧入口兼容面与向 `CQRS` 迁移时还保留了哪些执行契约 |
| `Events/` `Property/` | `19/19` 个类型声明已带 XML 注释 | `EventBus`、`EnhancedEventBus``BindableProperty<T>``OrEvent<T>` | 事件传播、解绑约束和可绑定属性的订阅语义 |
| `State/` `StateManagement/` | `10/10` 个类型声明已带 XML 注释 | `StateMachine`、`StateMachineSystem``Store<TState>``StoreBuilder<TState>` | 看状态切换、selector / middleware / dispatch 的单向流边界 |
| `Coroutine/` `Time/` `Pause/` `Concurrency/` | `43/43` 个类型声明已带 XML 注释 | `CoroutineScheduler`、`CoroutineHandle``WaitForSecondsRealtime``PauseStackManager``AsyncKeyLockManager` | 看调度阶段、等待指令、时间源和暂停 / 锁的线程语义 |
| `Resource/` `Pool/` | `8/8` 个类型声明已带 XML 注释 | `ResourceManager`、`AutoReleaseStrategy``ManualReleaseStrategy``AbstractObjectPoolSystem<TKey, TObject>` | 资源句柄释放策略与对象池复用约束 |
| `Logging/` `Localization/` `Configuration/` `Environment/` `Ioc/` | `31/31` 个类型声明已带 XML 注释 | `ConsoleLogger`、`CompositeLogger``LocalizationManager``ConfigurationManager``MicrosoftDiContainer` | 日志组装、格式化 / filter、配置监听、环境对象与容器适配 |
| `Model/` `Systems/` `Utility/` `Rule/` `Extensions/` `Functional/` | `34/34` 个类型声明已带 XML 注释 | `AbstractModel`、`AbstractSystem``NumericDisplayFormatter``ContextAwareBase``Result<T>` | 默认基类、上下文感知 helper、数值格式化和通用扩展的使用边界 |
| 类型族 | 代表类型 | 建议先确认什么 |
| --- | --- | --- |
| `Architectures/` | `Architecture`、`ArchitectureContext``ArchitectureLifecycle``ArchitecturePhaseCoordinator` | 架构启动、模块安装、阶段切换和上下文暴露边界 |
| `Services/` | `ServiceModuleManager`、`CommandExecutorModule``CqrsRuntimeModule` | 服务模块的注册顺序、销毁语义和默认接线 |
| `Command/` `Query/` | `CommandExecutor`、`AsyncQueryExecutor``AbstractCommand<TInput>``AbstractQuery<TResult>` | 旧入口兼容面,以及向 `CQRS` 迁移时保留的执行契约 |
| `Events/` `Property/` | `EventBus`、`EnhancedEventBus``BindableProperty<T>``OrEvent<T>` | 事件传播、解绑约束和可绑定属性的订阅语义 |
| `State/` `StateManagement/` | `StateMachine`、`StateMachineSystem``Store<TState>``StoreBuilder<TState>` | 状态切换,以及 selector / middleware / dispatch 的单向流边界 |
| `Coroutine/` `Time/` `Pause/` `Concurrency/` | `CoroutineScheduler`、`CoroutineHandle``WaitForSecondsRealtime``PauseStackManager``AsyncKeyLockManager` | 调度阶段、等待指令、时间源,以及暂停 / 锁的线程语义 |
| `Resource/` `Pool/` | `ResourceManager`、`AutoReleaseStrategy``ManualReleaseStrategy``AbstractObjectPoolSystem<TKey, TObject>` | 资源句柄释放策略与对象池复用约束 |
| `Logging/` `Localization/` `Configuration/` `Environment/` `Ioc/` | `ConsoleLogger`、`CompositeLogger``LocalizationManager``ConfigurationManager``MicrosoftDiContainer` | 日志组装、格式化 / filter、配置监听、环境对象与容器适配 |
| `Model/` `Systems/` `Utility/` `Rule/` `Extensions/` `Functional/` | `AbstractModel`、`AbstractSystem``NumericDisplayFormatter``ContextAwareBase``Result<T>` | 默认基类、上下文感知 helper、数值格式化和通用扩展的使用边界 |
## 最小接入路径

View File

@ -1,3 +1,8 @@
---
title: IoC 包使用说明
description: 说明 GFramework.Core 的 IoC 容器、依赖注入职责与核心类型。
---
# IoC 包使用说明
## 概述
@ -441,7 +446,7 @@ public class IocContainer
### 线程安全机制
容器使用 [ReaderWriterLockSlim](xref:System.Threading.ReaderWriterLockSlim) 来确保线程安全操作,允许多个线程同时读取,但在写入时阻止其他线程访问。
容器使用 `ReaderWriterLockSlim` 来确保线程安全操作,允许多个线程同时读取,但在写入时阻止其他线程访问。
### 注册流程
@ -848,4 +853,4 @@ protected override void OnInit()
- [`architecture`](./architecture.md) - 使用 IoC 容器管理所有组件
- [`model`](./model.md) - Model 通过 IoC 容器注册和获取
- [`system`](./system.md) - System 通过 IoC 容器注册和获取
- [`utility`](./utility.md) - Utility 通过 IoC 容器注册和获取
- [`utility`](./utility.md) - Utility 通过 IoC 容器注册和获取

View File

@ -1,3 +1,8 @@
---
title: Localization 本地化系统
description: 说明 GFramework.Core 本地化系统的核心接口、语言切换能力与使用场景。
---
# Localization 本地化系统
## 概述

View File

@ -1,3 +1,8 @@
---
title: Logging
description: 说明 GFramework.Core.Logging 的日志接口、组合方式与常见使用入口。
---
# Logging
`GFramework.Core.Logging` 是 Core runtime 的默认日志实现。只加载抽象层时,`LoggerFactoryResolver` 会退回

View File

@ -1,3 +1,8 @@
---
title: Model 包使用说明
description: 说明 GFramework.Core.Model 的模型职责、基类结构与常见生命周期入口。
---
# Model 包使用说明
## 概述
@ -229,4 +234,4 @@ await architecture.InitializeAsync();
- [`property`](./property.md) - BindableProperty 用于定义可监听属性
- [`events`](./events.md) - Model 发送事件通知变化
- [`utility`](./utility.md) - Model 可以使用工具类
- [`extensions`](./extensions.md) - 提供 GetModel 等扩展方法
- [`extensions`](./extensions.md) - 提供 GetModel 等扩展方法

View File

@ -1,3 +1,8 @@
---
title: 暂停管理系统使用说明
description: 说明 GFramework.Core 暂停管理系统的栈模型、作用域与协作方式。
---
# 暂停管理系统使用说明
## 概述

View File

@ -1,3 +1,8 @@
---
title: 对象池系统 (Object Pool System)
description: 说明 GFramework.Core 对象池系统的核心组件、池化策略与生命周期管理。
---
# 对象池系统 (Object Pool System)
## 概述

View File

@ -1,3 +1,8 @@
---
title: Property
description: 说明 GFramework.Core.Property 的可绑定属性模型、订阅方式与常见用法。
---
# Property
`GFramework.Core.Property` 负责字段级响应式值。它最适合“一个字段变化就足以驱动视图或局部业务逻辑”的场景;

View File

@ -1,3 +1,8 @@
---
title: Query
description: 说明 GFramework.Core.Query 旧查询体系的兼容定位、可用基类与当前使用约束。
---
# Query
本页说明 `GFramework.Core.Query` 里的旧查询体系。

View File

@ -1,3 +1,8 @@
---
title: Rule 包使用说明
description: 说明 GFramework.Core.Rule 中 IContextAware 规则接口与上下文访问约定。
---
# Rule 包使用说明
## 概述
@ -348,4 +353,4 @@ IArchitectureContext IContextAware.GetContext()
- [`query`](./query.md) - Query 继承 `AbstractQuery<TResult>` (实现 `IContextAware`)
- [`model`](./model.md) - Model 继承 `AbstractModel` (实现 `IContextAware`)
- [`system`](./system.md) - System 继承 `AbstractSystem` (实现 `IContextAware`)
- [`extensions`](./extensions.md) - 提供 `ContextAwareExtensions` 扩展方法
- [`extensions`](./extensions.md) - 提供 `ContextAwareExtensions` 扩展方法

View File

@ -1,3 +1,8 @@
---
title: State Management 包使用说明
description: 说明 GFramework.Core.StateManagement 的 Store、Reducer 与状态容器用法。
---
# State Management 包使用说明
## 概述
@ -482,7 +487,7 @@ public partial class PlayerPanelController : IController
## 相关文档
- [`property`](./property) - 字段级响应式属性
- [`model`](./model) - Store 常见承载位置
- [`events`](./events) - 组件间事件通信
- [`state-machine-tutorial`](../tutorials/state-machine-tutorial) - 流程状态切换能力
- [`property`](./property.md) - 字段级响应式属性
- [`model`](./model.md) - Store 常见承载位置
- [`events`](./events.md) - 组件间事件通信
- [`state-machine-tutorial`](../tutorials/state-machine-tutorial.md) - 流程状态切换能力

View File

@ -1,3 +1,8 @@
---
title: System 包使用说明
description: 说明 GFramework.Core.System 的业务逻辑层职责、基类结构与协作方式。
---
# System 包使用说明
## 概述
@ -660,4 +665,4 @@ public class ParticleSystem : AbstractSystem
- [`command`](./command.md) - System 中可以发送 Command
- [`query`](./query.md) - System 中可以发送 Query
- [`utility`](./utility.md) - System 可以使用 Utility
- [`architecture`](./architecture.md) - 在架构中注册 System
- [`architecture`](./architecture.md) - 在架构中注册 System

View File

@ -1,3 +1,8 @@
---
title: Utility 包使用说明
description: 说明 GFramework.Core.Utility 的工具组件定位、注册方式与使用场景。
---
# Utility 包使用说明
## 概述

View File

@ -1,6 +1,6 @@
---
title: Arch ECS 集成
description: GFramework.Ecs.Arch 的默认运行时装配路径、系统桥接方式与 XML 阅读入口。
description: GFramework.Ecs.Arch 的默认运行时装配路径、系统桥接方式与源码阅读入口。
---
# Arch ECS 集成
@ -127,14 +127,14 @@ ecsModule.Update(deltaTime);
- `Priority` 影响 `ArchEcsModule` 作为服务模块的排序
- `EnableStatistics` 目前保留在公开配置面上;采用时应以源码 XML 注释和实现行为为准,而不是依赖旧文档推断
## 类型族级 XML Inventory
## 源码阅读入口
| 类型族 | 代表类型 | XML 状态 | 阅读重点 |
| --- | --- | --- | --- |
| 装配入口 | `ArchExtensions` | 已覆盖 | `UseArch(...)` 的时机、链式调用返回值 |
| 服务模块 | `ArchEcsModule` | 已覆盖 | `World` 注册、系统收集、模块销毁顺序 |
| 系统桥接层 | `ArchSystemAdapter<T>` | 已覆盖 | `OnArchInitialize` / `OnUpdate` / `OnArchDispose` |
| 示例类型 | `Position``Velocity``MovementSystem` | 已覆盖 | 组件布局、查询写法、最小集成样例 |
| 类型族 | 代表类型 | 建议先确认什么 |
| --- | --- | --- |
| 装配入口 | `ArchExtensions` | `UseArch(...)` 的时机、链式调用返回值 |
| 服务模块 | `ArchEcsModule` | `World` 注册、系统收集、模块销毁顺序 |
| 系统桥接层 | `ArchSystemAdapter<T>` | `OnArchInitialize` / `OnUpdate` / `OnArchDispose` |
| 示例类型 | `Position``Velocity``MovementSystem` | 组件布局、查询写法和最小集成样例 |
## 相关入口

View File

@ -1,6 +1,6 @@
---
title: ECS 系统集成
description: GFramework 当前 ECS 模块族的包边界、采用顺序与 XML 阅读入口。
description: GFramework 当前 ECS 模块族的包边界、采用顺序与源码阅读入口。
---
# ECS 系统集成
@ -118,16 +118,16 @@ public sealed class GameLoop
3. 只想保留共享边界时继续读 [`../abstractions/ecs-arch-abstractions.md`](../abstractions/ecs-arch-abstractions.md)
4. 统一查阅 README / docs / XML 入口时回到 [`../api-reference/index.md`](../api-reference/index.md)
## 类型族级 XML Inventory
## 源码阅读入口
下表记录当前 `Ecs.Arch` family 的类型声明级 XML 基线,便于从 README、站内 landing 和源码之间建立一致的审计入口。
如果你要从栏目入口页回到源码和 XML 文档,建议按下面的入口阅读:
| 包 | 类型族 | 代表类型 | XML 状态 | 阅读重点 |
| --- | --- | --- | --- | --- |
| `GFramework.Ecs.Arch` | 运行时装配与模块生命周期 | `ArchExtensions``ArchEcsModule` | 已覆盖 | `UseArch(...)` 的接入时机、`World` 注册、模块优先级 |
| `GFramework.Ecs.Arch` | 系统桥接层 | `ArchSystemAdapter<T>` | 已覆盖 | GFramework `ISystem` 生命周期如何桥接到 Arch `ISystem<T>` |
| `GFramework.Ecs.Arch` | 示例组件与系统 | `Position``Velocity``MovementSystem` | 已覆盖 | 查询写法、组件布局、最小可运行示例 |
| `GFramework.Ecs.Arch.Abstractions` | 契约与配置对象 | `IArchEcsModule``IArchSystemAdapter<T>``ArchOptions` | 已覆盖 | 共享宿主循环、测试替身、跨程序集配置边界 |
| 包 | 类型族 | 代表类型 | 建议先确认什么 |
| --- | --- | --- | --- |
| `GFramework.Ecs.Arch` | 运行时装配与模块生命周期 | `ArchExtensions``ArchEcsModule` | `UseArch(...)` 的接入时机、`World` 注册、模块优先级 |
| `GFramework.Ecs.Arch` | 系统桥接层 | `ArchSystemAdapter<T>` | GFramework `ISystem` 生命周期如何桥接到 Arch `ISystem<T>` |
| `GFramework.Ecs.Arch` | 示例组件与系统 | `Position``Velocity``MovementSystem` | 查询写法、组件布局和最小可运行示例 |
| `GFramework.Ecs.Arch.Abstractions` | 契约与配置对象 | `IArchEcsModule``IArchSystemAdapter<T>``ArchOptions` | 共享宿主循环、测试替身和跨程序集配置边界 |
## 边界说明

View File

@ -1,3 +1,8 @@
---
title: 常见问题FAQ
description: 汇总 GFramework 在安装、架构、命令、事件与性能方面的常见问题。
---
# 常见问题FAQ
## 安装与配置

View File

@ -1,3 +1,8 @@
---
title: 游戏内容配置系统
description: 说明 GFramework.Game 配置系统的定位、目录约定、生成能力与最小采用路径。
---
# 游戏内容配置系统
> 面向静态游戏内容的 AI-First 配表方案

View File

@ -1,6 +1,6 @@
---
title: Game
description: GFramework.Game family 的运行时入口、采用顺序与 XML 阅读基线
description: GFramework.Game family 的运行时入口、采用顺序与源码阅读导航
---
# Game
@ -103,15 +103,15 @@ IStorage storage = new FileStorage("GameData", serializer);
4. [setting](./setting.md)
5. [scene](./scene.md) 或 [ui](./ui.md)
## Game Family XML 覆盖基线
## 源码与 API 阅读入口
下面这份 inventory 记录的是 `2026-04-23``Game` family 做的一轮轻量 XML 盘点结果:只统计公开 / 内部类型声明是否带 XML 注释,用来建立 README / landing / API 阅读链路;成员级 `param``returns``exception` 与生命周期说明仍需要后续波次继续细化。
如果你已经完成栏目入口页阅读,下一步通常不是看统计表,而是按模块角色回到源码和 XML 文档:
| 模块 | 基线状态 | 代表类型 | 阅读重点 |
| --- | --- | --- | --- |
| `GFramework.Game` | `56/56` 个类型声明已带 XML 注释 | `YamlConfigLoader`、`SettingsModel<TRepository>``SceneRouterBase``UiRouterBase` | 先看运行时默认实现、配置加载、设置编排和路由基类 |
| `GFramework.Game.Abstractions` | `80/80` 个类型声明已带 XML 注释 | `IConfigRegistry`、`ISaveRepository<TSaveData>``ISettingsSystem``ISceneRouter``IUiRouter` | 再看契约层边界,决定项目哪些程序集只依赖接口 |
| `GFramework.Game.SourceGenerators` | `2/2` 个类型声明已带 XML 注释 | `SchemaConfigGenerator`、`ConfigSchemaDiagnostics` | 最后看 schema 生成入口与诊断模型,确认配置系统的编译期链路 |
| 模块 | 代表类型 | 建议先确认什么 |
| --- | --- | --- |
| `GFramework.Game` | `YamlConfigLoader`、`SettingsModel<TRepository>``SceneRouterBase``UiRouterBase` | 默认运行时实现、配置加载、设置编排和路由基类 |
| `GFramework.Game.Abstractions` | `IConfigRegistry`、`ISaveRepository<TSaveData>``ISettingsSystem``ISceneRouter``IUiRouter` | 契约层边界,以及项目中哪些程序集只应依赖接口 |
| `GFramework.Game.SourceGenerators` | `SchemaConfigGenerator`、`ConfigSchemaDiagnostics` | schema 生成入口与诊断模型,确认配置系统的编译期链路 |
## 与真实接法的关系

View File

@ -1,3 +1,8 @@
---
title: 入门指南
description: 概览 GFramework 的模块组成、最小接入路径与继续阅读入口。
---
# 入门指南
这一部分只回答三个问题:

View File

@ -1,3 +1,8 @@
---
title: 安装配置
description: 说明 GFramework 各运行时与 source generator 包的安装选择和配置方式。
---
# 安装配置
GFramework 提供多种安装方式,您可以根据项目需求选择合适的包进行安装。

View File

@ -1,3 +1,8 @@
---
title: 快速开始
description: 通过只依赖 Core 的最小示例快速跑通 GFramework 基础架构。
---
# 快速开始
本页给出一个只依赖 `Core` 的最小路径,用来确认你已经成功接入 `Architecture``Model``System` 与旧版命令执行器。

View File

@ -1,3 +1,8 @@
---
title: Godot 协程系统
description: 说明 GFramework.Godot.Coroutine 的宿主集成能力、阶段语义与使用方式。
---
# Godot 协程系统
## 概述

View File

@ -1,3 +1,8 @@
---
title: AutoRegisterModule 生成器
description: 介绍 AutoRegisterModule 生成器如何为模块安装生成固定顺序的注册代码。
---
# AutoRegisterModule 生成器
> 为架构模块生成固定顺序的组件注册代码,收敛 `Install(IArchitecture)` 样板。
@ -149,5 +154,5 @@ partial class GameplayModule
## 相关文档
- [源码生成器总览](./index)
- [Context Get 注入生成器](./context-get-generator)
- [源码生成器总览](./index.md)
- [Context Get 注入生成器](./context-get-generator.md)

View File

@ -1,3 +1,8 @@
---
title: AutoScene 生成器
description: 介绍 AutoScene 生成器如何生成场景包装入口与统一场景键声明。
---
# AutoScene 生成器
> 为场景根节点生成 `GetScene()` 样板,统一场景键声明与行为包装。
@ -116,5 +121,5 @@ partial class GameplayRoot
## 相关文档
- [源码生成器总览](./index)
- [AutoUiPage 生成器](./auto-ui-page-generator)
- [源码生成器总览](./index.md)
- [AutoUiPage 生成器](./auto-ui-page-generator.md)

View File

@ -1,3 +1,8 @@
---
title: AutoUiPage 生成器
description: 介绍 AutoUiPage 生成器如何为 Godot UI 页面生成页面包装入口。
---
# AutoUiPage 生成器
> 为 Godot UI 页面生成 `GetPage()` 样板,统一页面键与层级声明。
@ -149,6 +154,6 @@ public partial class PauseMenu : Control
## 相关文档
- [源码生成器总览](./index)
- [GetNode 生成器](./get-node-generator)
- [BindNodeSignal 生成器](./bind-node-signal-generator)
- [源码生成器总览](./index.md)
- [GetNode 生成器](./get-node-generator.md)
- [BindNodeSignal 生成器](./bind-node-signal-generator.md)

View File

@ -11,7 +11,7 @@ description: 说明 [BindNodeSignal] 当前生成什么、如何与 GetNode 协
- 特性来源:`GFramework.Godot.SourceGenerators.Abstractions`
- 生成器实现:`GFramework.Godot.SourceGenerators`
- 目标字段基线`nodeFieldName` 指向的字段必须继承 `Godot.Node`
- 使用前提`nodeFieldName` 指向的字段必须继承 `Godot.Node`
## 最小用法

View File

@ -1,3 +1,8 @@
---
title: Context Get 注入生成器
description: 介绍 Context Get 注入生成器的能力、依赖前提与使用方式。
---
# Context Get 注入生成器
> 自动注入架构组件,消除样板代码

View File

@ -116,7 +116,7 @@ RegisterCqrsHandlersFromAssemblies(
这条诊断的含义不是“某个 handler 写错了”,而是“当前 runtime 合同不足以安全承载这轮生成结果”。
## XML / API 阅读入口
## 源码与 API 阅读入口
如果你要核对生成器对外暴露的契约,优先看这些类型:

View File

@ -1,3 +1,8 @@
---
title: 枚举扩展生成器
description: 介绍枚举扩展生成器生成的 IsX 与 IsIn 方法及其典型用法。
---
# 枚举扩展生成器
> 自动为枚举类型生成扩展方法
@ -267,6 +272,6 @@ public class PlayerController
## 相关文档
- [Source Generators 概述](./index)
- [日志生成器](./logging-generator)
- [ContextAware 生成器](./context-aware-generator)
- [Source Generators 概述](./index.md)
- [日志生成器](./logging-generator.md)
- [ContextAware 生成器](./context-aware-generator.md)

View File

@ -11,7 +11,7 @@ description: 说明 [GetNode] 当前生成什么、路径如何推断,以及 _
- 特性来源:`GFramework.Godot.SourceGenerators.Abstractions`
- 生成器实现:`GFramework.Godot.SourceGenerators`
- 目标类型基线:字段类型必须继承 `Godot.Node`
- 使用前提:字段类型必须继承 `Godot.Node`
## 最小用法

View File

@ -1,3 +1,8 @@
---
title: 日志生成器
description: 介绍日志生成器如何为标记类型生成 ILogger 字段并减少日志样板。
---
# 日志生成器
> GFramework.Core.SourceGenerators 自动生成日志代码,减少样板代码
@ -336,6 +341,6 @@ public static partial class StaticHelper
**相关文档**
- [Source Generators 概述](./index)
- [枚举扩展生成器](./enum-generator)
- [ContextAware 生成器](./context-aware-generator)
- [Source Generators 概述](./index.md)
- [枚举扩展生成器](./enum-generator.md)
- [ContextAware 生成器](./context-aware-generator.md)

View File

@ -1,3 +1,8 @@
---
title: 故障排除与调试
description: 提供 GFramework 常见安装、架构、事件与运行时问题的排查思路。
---
# 故障排除与调试
本指南帮助你诊断和解决 GFramework 使用中的常见问题。
@ -2047,12 +2052,12 @@ public class MyLoader : IResourceLoader
如果问题仍未解决:
1. 查看 [Core 文档](/zh-CN/core/) 了解更多细节
2. 查看 [架构组件](/zh-CN/core/architecture) 了解架构设计
3. 查看 [Godot 集成](/zh-CN/godot/) 了解 Godot 特定问题
1. 查看 [Core 文档](/zh-CN/core/index.md) 了解更多细节
2. 查看 [架构组件](/zh-CN/core/architecture.md) 了解架构设计
3. 查看 [Godot 集成](/zh-CN/godot/index.md) 了解 Godot 特定问题
4. 在 [GitHub Issues](https://github.com/GeWuYou/GFramework/issues) 提交问题
5. 查看 [教程](/zh-CN/tutorials/) 中的示例代码
6. 查看 [常见问题](/zh-CN/faq) 获取快速答案
5. 查看 [教程](/zh-CN/tutorials/index.md) 中的示例代码
6. 查看 [常见问题](/zh-CN/faq.md) 获取快速答案
---

View File

@ -1,3 +1,8 @@
---
title: 高级模式教程
description: 通过高级模式示例说明 GFramework 中的复杂架构组织与扩展方式。
---
# 高级模式教程
> 深入学习 GFramework 的高级特性和设计模式,构建更复杂和可维护的游戏系统。
@ -1637,4 +1642,4 @@ public partial class NetworkController : Node, IController
---
**教程版本**: 1.0.0
**更新日期**: 2026-01-12
**更新日期**: 2026-01-12

View File

@ -1,3 +1,8 @@
---
title: 基础教程:从零开始使用 GFramework
description: 从零开始串联环境准备、项目搭建与核心概念的基础教程入口。
---
# 基础教程:从零开始使用 GFramework
欢迎来到 GFramework 的基础教程!本教程将带你从零开始,创建一个完整的计数器应用,逐步掌握 GFramework 的核心概念和最佳实践。

View File

@ -1,3 +1,8 @@
---
title: 教程
description: 汇总 GFramework 的基础与进阶教程入口,帮助按学习阶段选择阅读路径。
---
# 教程
欢迎来到 GFramework 教程中心!这里提供从入门到精进的完整学习路径,帮助你掌握这个面向游戏开发的模块化 C# 框架。