mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-12 13:14:30 +08:00
Compare commits
20 Commits
4b5a760643
...
45b25f429f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45b25f429f | ||
|
|
8a117201d4 | ||
|
|
a4bb041b0d | ||
|
|
148cfe14b0 | ||
|
|
59fe63bba6 | ||
|
|
aa879d2c9a | ||
|
|
fdf7382717 | ||
|
|
efc0518996 | ||
|
|
2263cf296b | ||
|
|
564b45bde1 | ||
|
|
c3085f7c6a | ||
|
|
8cd492506d | ||
|
|
18c595a72f | ||
|
|
a6b87c23e4 | ||
|
|
cebdbdbe9b | ||
|
|
3203239726 | ||
|
|
9d251ab1f8 | ||
|
|
31ae1460a9 | ||
|
|
d0e86933cf | ||
|
|
dc8c5766dc |
@ -1,6 +1,6 @@
|
||||
# GFramework Skills
|
||||
|
||||
文档工作流的公开入口已统一为 `gframework-doc-refresh`。
|
||||
公开入口目前包含 `gframework-doc-refresh` 与 `gframework-batch-boot`。
|
||||
|
||||
## 公开入口
|
||||
|
||||
@ -29,6 +29,44 @@
|
||||
/gframework-doc-refresh Cqrs
|
||||
```
|
||||
|
||||
### `gframework-batch-boot`
|
||||
|
||||
在 `gframework-boot` 的基础上,自动推进可分批执行的重复性任务,不需要人工一轮轮重新触发。
|
||||
|
||||
适用场景:
|
||||
|
||||
- analyzer warning reduction
|
||||
- 大批量测试结构收口
|
||||
- 分模块文档刷新 wave
|
||||
- 任何有明确 stop condition 的多批次任务
|
||||
|
||||
推荐调用:
|
||||
|
||||
```bash
|
||||
/gframework-batch-boot <task-or-stop-condition>
|
||||
```
|
||||
|
||||
批处理阈值速记:
|
||||
|
||||
```bash
|
||||
/gframework-batch-boot 75
|
||||
/gframework-batch-boot 75 2000
|
||||
```
|
||||
|
||||
- 单个数字默认表示“分支相对基线接近多少个文件变更时停止”
|
||||
- 单个数字默认表示“当前分支全部提交相对远程 `origin/main` 接近多少个文件变更时停止”
|
||||
- 两个数字默认表示“当前分支全部提交相对远程 `origin/main` 的 `文件数 OR 变更行数`”,顺序固定为 `<files> <lines>`
|
||||
- 不推荐写 `/gframework-batch-boot 75 | 2000`,因为 `|` 很像 shell pipe;若用户这样写,也应按 OR 语义理解并在后续说明中归一化成无 `|` 版本
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
/gframework-batch-boot 75
|
||||
/gframework-batch-boot 75 2000
|
||||
/gframework-batch-boot continue analyzer warning reduction until branch diff vs origin/main approaches 75 files
|
||||
/gframework-batch-boot keep refactoring repetitive source-generator tests in bounded batches
|
||||
```
|
||||
|
||||
## 共享资源
|
||||
|
||||
- `_shared/DOCUMENTATION_STANDARDS.md`
|
||||
|
||||
180
.agents/skills/gframework-batch-boot/SKILL.md
Normal file
180
.agents/skills/gframework-batch-boot/SKILL.md
Normal file
@ -0,0 +1,180 @@
|
||||
---
|
||||
name: gframework-batch-boot
|
||||
description: Repository-specific bulk-task workflow for the GFramework repo. Use when Codex should start from the normal GFramework boot context and then continue a repetitive or large-scope task in automatic batches without waiting for manual round-by-round prompts, especially for analyzer warning cleanup, repetitive test refactors, documentation waves, or similar multi-file work with an explicit stop condition such as changed-file count, warning count, or timebox.
|
||||
---
|
||||
|
||||
# GFramework Batch Boot
|
||||
|
||||
## Overview
|
||||
|
||||
Use this skill when `gframework-boot` is necessary but not sufficient because the task should keep advancing in bounded
|
||||
batches until a clear stop condition is met.
|
||||
|
||||
Treat `AGENTS.md` as the source of truth. This skill extends `gframework-boot`; it does not replace it.
|
||||
|
||||
## Startup Workflow
|
||||
|
||||
1. Execute the normal `gframework-boot` startup sequence first:
|
||||
- read `AGENTS.md`
|
||||
- read `.ai/environment/tools.ai.yaml`
|
||||
- read `ai-plan/public/README.md`
|
||||
- read the mapped active topic `todos/` and `traces/`
|
||||
2. Classify the task as a batch candidate only if all of the following are true:
|
||||
- the work is repetitive, sliceable, or likely to require multiple similar iterations
|
||||
- each batch can be given an explicit ownership boundary
|
||||
- a stop condition can be measured locally
|
||||
3. Before any delegation, define the batch objective in one sentence:
|
||||
- warning family reduction
|
||||
- repeated test refactor pattern
|
||||
- module-by-module documentation refresh
|
||||
- other repetitive multi-file cleanup
|
||||
|
||||
## Baseline Selection
|
||||
|
||||
When the stop condition depends on branch size or changed-file count, choose the baseline carefully.
|
||||
|
||||
1. Prefer the freshest remote-tracking reference that already exists locally:
|
||||
- `origin/main`
|
||||
- or the mapped upstream base branch for the current topic
|
||||
2. Do not default to local `main` when `refs/heads/main` is behind `refs/remotes/origin/main`.
|
||||
3. If both local and remote-tracking refs exist, report:
|
||||
- ref name
|
||||
- short SHA
|
||||
- committer date
|
||||
4. If only a local branch exists, state that the baseline may be stale before using it.
|
||||
5. When the task is tied to a PR or topic branch rather than `main`, prefer that explicit upstream comparison target over
|
||||
a generic `main`.
|
||||
|
||||
For changed-file limits, measure branch-wide scope against the chosen baseline, not just the current working tree:
|
||||
|
||||
- use `git diff --name-only <baseline>...HEAD`
|
||||
- do not confuse branch diff size with `git status --short`
|
||||
|
||||
For changed-line limits, also measure branch-wide scope against the chosen baseline:
|
||||
|
||||
- prefer `git diff --numstat <baseline>...HEAD`
|
||||
- treat "changed lines" as `added + deleted` summed across the branch diff
|
||||
- do not use working-tree-only line counts as a substitute for branch-wide scope
|
||||
|
||||
For shorthand numeric thresholds, use a fixed default baseline:
|
||||
|
||||
- compare the current branch's cumulative diff against remote `origin/main`
|
||||
- include all commits reachable from `HEAD` that are not already in `origin/main`
|
||||
- do not reinterpret shorthand thresholds as "this batch only" or "current unstaged changes only"
|
||||
- only use another baseline when the user explicitly names it in the prompt
|
||||
|
||||
## Stop Conditions
|
||||
|
||||
Choose one primary stop condition before the first batch and restate it to the user.
|
||||
|
||||
Common stop conditions:
|
||||
|
||||
- branch diff vs baseline approaches a file-count threshold
|
||||
- warnings-only build reaches a target count
|
||||
- a specific hotspot list is exhausted
|
||||
- a timebox or validation budget is reached
|
||||
|
||||
If multiple stop conditions exist, rank them and treat one as primary.
|
||||
|
||||
## Shorthand Stop-Condition Syntax
|
||||
|
||||
`gframework-batch-boot` may be invoked with shorthand numeric thresholds when the user clearly wants a branch-size stop
|
||||
condition instead of a long natural-language prompt.
|
||||
|
||||
Interpret shorthand as follows:
|
||||
|
||||
- `$gframework-batch-boot 75`
|
||||
- means: stop when the current branch's cumulative diff vs remote `origin/main` approaches `75` changed files
|
||||
- `$gframework-batch-boot 75 2000`
|
||||
- means: stop when the current branch's cumulative diff vs remote `origin/main` approaches `75` changed files OR
|
||||
`2000` changed lines
|
||||
- default positional meaning is `<files> <lines>`
|
||||
- `$gframework-batch-boot 75 | 2000`
|
||||
- may be interpreted as the same OR shorthand in plain-language chat
|
||||
- when restating, planning, or documenting the command, normalize it to `$gframework-batch-boot 75 2000`
|
||||
- prefer the no-pipe form because `|` is easy to confuse with a shell pipeline
|
||||
|
||||
When shorthand is used:
|
||||
|
||||
- report the resolved thresholds explicitly before the first batch
|
||||
- report that the baseline is remote `origin/main`, unless the user explicitly overrides it
|
||||
- if two numeric thresholds are present, treat file count as the default primary metric for status reporting unless the
|
||||
user says otherwise
|
||||
- stop when either threshold is reached or exceeded, even if the other threshold still has headroom
|
||||
|
||||
## Batch Loop
|
||||
|
||||
1. Inspect the current state before the first batch:
|
||||
- current branch and active topic
|
||||
- selected baseline
|
||||
- current stop-condition metric
|
||||
- next candidate slices
|
||||
2. Keep the critical path local.
|
||||
3. Delegate only bounded slices with explicit ownership:
|
||||
- one file
|
||||
- one warning family within one project
|
||||
- one module documentation wave
|
||||
4. For each worker batch, specify:
|
||||
- objective
|
||||
- owned files or subsystem
|
||||
- required validation commands
|
||||
- output format
|
||||
- reminder that other agents may be editing the repo
|
||||
5. While workers run, use the main thread for non-overlapping tasks:
|
||||
- queue the next candidate slice
|
||||
- inspect the next hotspot
|
||||
- recompute branch size or warning distribution
|
||||
6. After each completed batch:
|
||||
- integrate or verify the result
|
||||
- rerun the required validation
|
||||
- recompute the primary stop-condition metric
|
||||
- decide immediately whether to continue or stop
|
||||
7. Do not require the user to manually trigger every round unless:
|
||||
- the next slice is ambiguous
|
||||
- a validation failure changes strategy
|
||||
- the batch objective conflicts with the active topic
|
||||
|
||||
## Task Tracking
|
||||
|
||||
For multi-batch work, keep recovery artifacts current.
|
||||
|
||||
- Update the active `ai-plan/public/<topic>/todos/` document when a meaningful batch lands.
|
||||
- Update the matching `traces/` document with:
|
||||
- accepted delegated scope
|
||||
- validation milestones
|
||||
- current stop-condition metric
|
||||
- next recommended batch
|
||||
- Keep the active recovery point concise; archive detailed history when it starts to sprawl.
|
||||
|
||||
## Delegation Defaults
|
||||
|
||||
- Prefer `worker` subagents for independent write slices.
|
||||
- Prefer `explorer` subagents for read-only hotspot ranking or next-batch discovery.
|
||||
- Keep each worker ownership boundary disjoint.
|
||||
- Avoid launching a new batch when the expected write set would push the branch beyond the declared threshold without a
|
||||
deliberate decision.
|
||||
|
||||
## Completion
|
||||
|
||||
Stop the loop when any of the following becomes true:
|
||||
|
||||
- the primary stop condition has been reached or exceeded
|
||||
- the remaining slices are no longer low-risk
|
||||
- validation failures indicate the task is no longer repetitive
|
||||
- the branch has grown large enough that reviewability would materially degrade
|
||||
|
||||
When stopping, report:
|
||||
|
||||
- which baseline was used
|
||||
- the exact metric value at stop time
|
||||
- completed batches
|
||||
- remaining candidate batches
|
||||
- whether further work should continue in a new turn or after rebasing/fetching
|
||||
|
||||
## Example Triggers
|
||||
|
||||
- `Use $gframework-batch-boot 75 to keep reducing analyzer warnings until the branch diff vs baseline approaches 75 files.`
|
||||
- `Use $gframework-batch-boot 75 2000 to keep reducing warnings until the branch diff approaches 75 files or 2000 changed lines.`
|
||||
- `Use $gframework-batch-boot and keep reducing analyzer warnings until the branch diff vs origin/main approaches 75 files.`
|
||||
- `Use $gframework-batch-boot to continue this repetitive test refactor in bounded batches until the warning count drops below 10.`
|
||||
- `Use $gframework-batch-boot and refresh module docs in waves without asking me to trigger every round.`
|
||||
4
.agents/skills/gframework-batch-boot/agents/openai.yaml
Normal file
4
.agents/skills/gframework-batch-boot/agents/openai.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "GFramework Batch Boot"
|
||||
short_description: "Run boot, then iterate bounded bulk batches"
|
||||
default_prompt: "Use $gframework-batch-boot to start from the normal GFramework boot context and continue the current repetitive task in automatic bounded batches until the declared stop condition is reached."
|
||||
File diff suppressed because it is too large
Load Diff
@ -129,6 +129,35 @@
|
||||
|
||||
也就是说,本包负责生成辅助方法,但调用时机仍由项目侧决定。
|
||||
|
||||
最小接法可以直接写成:
|
||||
|
||||
```csharp
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using Godot;
|
||||
|
||||
public partial class MainMenu : Control
|
||||
{
|
||||
[GetNode]
|
||||
private Button _startButton = null!;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
__InjectGetNodes_Generated();
|
||||
__BindNodeSignals_Generated();
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
__UnbindNodeSignals_Generated();
|
||||
}
|
||||
|
||||
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
|
||||
private void OnStartPressed()
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 按场景选特性
|
||||
|
||||
- 项目级元数据:
|
||||
|
||||
@ -132,462 +132,545 @@ public sealed class ContextRegistrationAnalyzerTests
|
||||
}
|
||||
""";
|
||||
|
||||
[Test]
|
||||
public async Task Reports_Warning_When_FieldInjectedModel_Is_Not_Registered()
|
||||
{
|
||||
var markup = MarkupTestSource.Parse(
|
||||
Wrap("""
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel {|#0:_model|} = null!;
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
"""));
|
||||
// Keep scenario fixtures at class scope so MA0051 reduction does not change analyzer inputs or markup spans.
|
||||
private const string MissingFieldInjectedModelRegistrationSource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
|
||||
markup.Source,
|
||||
markup.WithSpan(
|
||||
new DiagnosticResult("GF_ContextRegistration_001", DiagnosticSeverity.Warning)
|
||||
.WithArguments("IInventoryModel", "InventoryPanelSystem", "GameArchitecture"),
|
||||
"0"));
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel {|#0:_model|} = null!;
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string RegisteredFieldInjectedModelSource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryModel : IInventoryModel { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel _model = null!;
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterModel(new InventoryModel());
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string MissingHandWrittenGetSystemRegistrationSource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Extensions;
|
||||
|
||||
public interface ICombatSystem : ISystem { }
|
||||
|
||||
public sealed class UiUtility : IUtility
|
||||
{
|
||||
public void Initialize()
|
||||
{
|
||||
{|#0:this.GetSystem<ICombatSystem>()|};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterUtility(new UiUtility());
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string ModuleProvidedModelRegistrationSource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryModel : IInventoryModel { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel _model = null!;
|
||||
}
|
||||
|
||||
public sealed class InventoryModule : IArchitectureModule
|
||||
{
|
||||
public void Install(IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterModel(new InventoryModel());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
InstallModule(new InventoryModule());
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string AmbiguousOwningArchitectureSource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel _model = null!;
|
||||
}
|
||||
|
||||
public sealed class FirstArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SecondArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string MissingGetUtilitiesRegistrationSource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryUtility : IUtility { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetUtilities]
|
||||
private IReadOnlyList<IInventoryUtility> {|#0:_utilities|} = null!;
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string DerivedArchitectureVirtualHelperRegistrationSource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryModel : IInventoryModel { }
|
||||
|
||||
public abstract class ArchitectureBase : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterComponents();
|
||||
}
|
||||
|
||||
protected virtual void RegisterComponents()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel _model = null!;
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : ArchitectureBase
|
||||
{
|
||||
protected override void RegisterComponents()
|
||||
{
|
||||
RegisterModel(new InventoryModel());
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string DerivedModuleVirtualHelperRegistrationSource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryModel : IInventoryModel { }
|
||||
|
||||
public abstract class ModuleBase : IArchitectureModule
|
||||
{
|
||||
public void Install(IArchitecture architecture)
|
||||
{
|
||||
RegisterComponents(architecture);
|
||||
}
|
||||
|
||||
protected virtual void RegisterComponents(IArchitecture architecture)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DerivedInventoryModule : ModuleBase
|
||||
{
|
||||
protected override void RegisterComponents(IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterModel(new InventoryModel());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel _model = null!;
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
InstallModule(new DerivedInventoryModule());
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string DerivedArchitectureBaseHelperCallSource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryModel : IInventoryModel { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel {|#0:_model|} = null!;
|
||||
}
|
||||
|
||||
public abstract class ArchitectureBase : Architecture
|
||||
{
|
||||
protected virtual void RegisterComponents()
|
||||
{
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : ArchitectureBase
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
base.RegisterComponents();
|
||||
}
|
||||
|
||||
protected override void RegisterComponents()
|
||||
{
|
||||
RegisterModel(new InventoryModel());
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string DerivedModuleBaseHelperCallSource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryModel : IInventoryModel { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel {|#0:_model|} = null!;
|
||||
}
|
||||
|
||||
public abstract class ModuleBase : IArchitectureModule
|
||||
{
|
||||
public virtual void Install(IArchitecture architecture)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void RegisterComponents(IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DerivedInventoryModule : ModuleBase
|
||||
{
|
||||
public override void Install(IArchitecture architecture)
|
||||
{
|
||||
base.RegisterComponents(architecture);
|
||||
}
|
||||
|
||||
protected override void RegisterComponents(IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterModel(new InventoryModel());
|
||||
architecture.RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
InstallModule(new DerivedInventoryModule());
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// 验证字段注入模型未注册时会报告缺失注册告警。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public Task Reports_Warning_When_FieldInjectedModel_Is_Not_Registered()
|
||||
{
|
||||
return RunWarningScenarioAsync(
|
||||
MissingFieldInjectedModelRegistrationSource,
|
||||
CreateContextRegistrationWarning(
|
||||
"GF_ContextRegistration_001",
|
||||
"IInventoryModel",
|
||||
"InventoryPanelSystem",
|
||||
"GameArchitecture"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证字段注入模型已注册时不会产生误报。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Does_Not_Report_When_FieldInjectedModel_Is_Registered()
|
||||
public Task Does_Not_Report_When_FieldInjectedModel_Is_Registered()
|
||||
{
|
||||
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
|
||||
Wrap("""
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryModel : IInventoryModel { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel _model = null!;
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterModel(new InventoryModel());
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
"""));
|
||||
return RunNoDiagnosticScenarioAsync(RegisteredFieldInjectedModelSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证手写扩展方法访问未注册 System 时会报告缺失注册告警。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Reports_Warning_When_HandWrittenGetSystem_Call_Has_No_Registration()
|
||||
public Task Reports_Warning_When_HandWrittenGetSystem_Call_Has_No_Registration()
|
||||
{
|
||||
var markup = MarkupTestSource.Parse(
|
||||
Wrap("""
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Extensions;
|
||||
|
||||
public interface ICombatSystem : ISystem { }
|
||||
|
||||
public sealed class UiUtility : IUtility
|
||||
{
|
||||
public void Initialize()
|
||||
{
|
||||
{|#0:this.GetSystem<ICombatSystem>()|};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterUtility(new UiUtility());
|
||||
}
|
||||
}
|
||||
}
|
||||
"""));
|
||||
|
||||
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
|
||||
markup.Source,
|
||||
markup.WithSpan(
|
||||
new DiagnosticResult("GF_ContextRegistration_002", DiagnosticSeverity.Warning)
|
||||
.WithArguments("ICombatSystem", "UiUtility", "GameArchitecture"),
|
||||
"0"));
|
||||
return RunWarningScenarioAsync(
|
||||
MissingHandWrittenGetSystemRegistrationSource,
|
||||
CreateContextRegistrationWarning(
|
||||
"GF_ContextRegistration_002",
|
||||
"ICombatSystem",
|
||||
"UiUtility",
|
||||
"GameArchitecture"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证模块安装链路提供注册时分析器会把该注册视为有效来源。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Does_Not_Report_When_Registration_Comes_From_Installed_Module()
|
||||
public Task Does_Not_Report_When_Registration_Comes_From_Installed_Module()
|
||||
{
|
||||
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
|
||||
Wrap("""
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryModel : IInventoryModel { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel _model = null!;
|
||||
}
|
||||
|
||||
public sealed class InventoryModule : IArchitectureModule
|
||||
{
|
||||
public void Install(IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterModel(new InventoryModel());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
InstallModule(new InventoryModule());
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
"""));
|
||||
return RunNoDiagnosticScenarioAsync(ModuleProvidedModelRegistrationSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证无法唯一推导所属 Architecture 时分析器保持静默以避免误报。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Does_Not_Report_When_Owning_Architecture_Cannot_Be_Uniquely_Determined()
|
||||
public Task Does_Not_Report_When_Owning_Architecture_Cannot_Be_Uniquely_Determined()
|
||||
{
|
||||
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
|
||||
Wrap("""
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel _model = null!;
|
||||
}
|
||||
|
||||
public sealed class FirstArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SecondArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
"""));
|
||||
return RunNoDiagnosticScenarioAsync(AmbiguousOwningArchitectureSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证集合注入 Utility 缺失注册时仍会报告对应告警。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Reports_Warning_When_GetUtilities_Field_Has_No_Registered_Utility()
|
||||
public Task Reports_Warning_When_GetUtilities_Field_Has_No_Registered_Utility()
|
||||
{
|
||||
var markup = MarkupTestSource.Parse(
|
||||
Wrap("""
|
||||
namespace TestApp
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryUtility : IUtility { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetUtilities]
|
||||
private IReadOnlyList<IInventoryUtility> {|#0:_utilities|} = null!;
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
"""));
|
||||
|
||||
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
|
||||
markup.Source,
|
||||
markup.WithSpan(
|
||||
new DiagnosticResult("GF_ContextRegistration_003", DiagnosticSeverity.Warning)
|
||||
.WithArguments("IInventoryUtility", "InventoryPanelSystem", "GameArchitecture"),
|
||||
"0"));
|
||||
return RunWarningScenarioAsync(
|
||||
MissingGetUtilitiesRegistrationSource,
|
||||
CreateContextRegistrationWarning(
|
||||
"GF_ContextRegistration_003",
|
||||
"IInventoryUtility",
|
||||
"InventoryPanelSystem",
|
||||
"GameArchitecture"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证基类初始化经由虚方法分派到派生实现时,派生注册仍会被识别。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task
|
||||
public Task
|
||||
Does_Not_Report_When_Inherited_OnInitialize_Calls_Virtual_Helper_Overridden_In_Derived_Architecture()
|
||||
{
|
||||
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
|
||||
Wrap("""
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryModel : IInventoryModel { }
|
||||
|
||||
public abstract class ArchitectureBase : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterComponents();
|
||||
}
|
||||
|
||||
protected virtual void RegisterComponents()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel _model = null!;
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : ArchitectureBase
|
||||
{
|
||||
protected override void RegisterComponents()
|
||||
{
|
||||
RegisterModel(new InventoryModel());
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
"""));
|
||||
return RunNoDiagnosticScenarioAsync(DerivedArchitectureVirtualHelperRegistrationSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证模块基类通过虚方法转发注册时,派生模块的注册依然会被识别。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Does_Not_Report_When_Inherited_Module_Install_Calls_Virtual_Helper_Overridden_In_Derived_Module()
|
||||
public Task Does_Not_Report_When_Inherited_Module_Install_Calls_Virtual_Helper_Overridden_In_Derived_Module()
|
||||
{
|
||||
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
|
||||
Wrap("""
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryModel : IInventoryModel { }
|
||||
|
||||
public abstract class ModuleBase : IArchitectureModule
|
||||
{
|
||||
public void Install(IArchitecture architecture)
|
||||
{
|
||||
RegisterComponents(architecture);
|
||||
}
|
||||
|
||||
protected virtual void RegisterComponents(IArchitecture architecture)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DerivedInventoryModule : ModuleBase
|
||||
{
|
||||
protected override void RegisterComponents(IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterModel(new InventoryModel());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel _model = null!;
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
InstallModule(new DerivedInventoryModule());
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
"""));
|
||||
return RunNoDiagnosticScenarioAsync(DerivedModuleVirtualHelperRegistrationSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证显式调用基类 helper 时,分析器按基类实际执行的注册路径发出告警。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Reports_Warning_When_Derived_Architecture_Explicitly_Calls_Base_Helper()
|
||||
public Task Reports_Warning_When_Derived_Architecture_Explicitly_Calls_Base_Helper()
|
||||
{
|
||||
var markup = MarkupTestSource.Parse(
|
||||
Wrap("""
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryModel : IInventoryModel { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel {|#0:_model|} = null!;
|
||||
}
|
||||
|
||||
public abstract class ArchitectureBase : Architecture
|
||||
{
|
||||
protected virtual void RegisterComponents()
|
||||
{
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : ArchitectureBase
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
base.RegisterComponents();
|
||||
}
|
||||
|
||||
protected override void RegisterComponents()
|
||||
{
|
||||
RegisterModel(new InventoryModel());
|
||||
RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
"""));
|
||||
return RunWarningScenarioAsync(
|
||||
DerivedArchitectureBaseHelperCallSource,
|
||||
CreateContextRegistrationWarning(
|
||||
"GF_ContextRegistration_001",
|
||||
"IInventoryModel",
|
||||
"InventoryPanelSystem",
|
||||
"GameArchitecture"));
|
||||
}
|
||||
|
||||
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
|
||||
/// <summary>
|
||||
/// 验证模块显式调用基类 helper 时,分析器按实际执行的安装路径发出告警。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public Task Reports_Warning_When_Derived_Module_Explicitly_Calls_Base_Helper()
|
||||
{
|
||||
return RunWarningScenarioAsync(
|
||||
DerivedModuleBaseHelperCallSource,
|
||||
CreateContextRegistrationWarning(
|
||||
"GF_ContextRegistration_001",
|
||||
"IInventoryModel",
|
||||
"InventoryPanelSystem",
|
||||
"GameArchitecture"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行包含诊断标记的 analyzer 场景,并把预期诊断绑定到统一的 `#0` span。
|
||||
/// </summary>
|
||||
/// <param name="source">不含公共前导代码的测试源码。</param>
|
||||
/// <param name="expectedDiagnostic">需要命中的预期诊断。</param>
|
||||
/// <returns>代表 analyzer 验证流程的异步任务。</returns>
|
||||
private static Task RunWarningScenarioAsync(string source, DiagnosticResult expectedDiagnostic)
|
||||
{
|
||||
MarkupTestSource markup = MarkupTestSource.Parse(Wrap(source));
|
||||
return AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
|
||||
markup.Source,
|
||||
markup.WithSpan(
|
||||
new DiagnosticResult("GF_ContextRegistration_001", DiagnosticSeverity.Warning)
|
||||
.WithArguments("IInventoryModel", "InventoryPanelSystem", "GameArchitecture"),
|
||||
"0"));
|
||||
markup.WithSpan(expectedDiagnostic, "0"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Reports_Warning_When_Derived_Module_Explicitly_Calls_Base_Helper()
|
||||
/// <summary>
|
||||
/// 运行不应产生诊断的 analyzer 场景。
|
||||
/// </summary>
|
||||
/// <param name="source">不含公共前导代码的测试源码。</param>
|
||||
/// <returns>代表 analyzer 验证流程的异步任务。</returns>
|
||||
private static Task RunNoDiagnosticScenarioAsync(string source)
|
||||
{
|
||||
var markup = MarkupTestSource.Parse(
|
||||
Wrap("""
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public interface IInventoryModel : IModel { }
|
||||
|
||||
public sealed class InventoryModel : IInventoryModel { }
|
||||
|
||||
public sealed class InventoryPanelSystem : ISystem
|
||||
{
|
||||
[GetModel]
|
||||
private IInventoryModel {|#0:_model|} = null!;
|
||||
}
|
||||
|
||||
public abstract class ModuleBase : IArchitectureModule
|
||||
{
|
||||
public virtual void Install(IArchitecture architecture)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void RegisterComponents(IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DerivedInventoryModule : ModuleBase
|
||||
{
|
||||
public override void Install(IArchitecture architecture)
|
||||
{
|
||||
base.RegisterComponents(architecture);
|
||||
}
|
||||
|
||||
protected override void RegisterComponents(IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterModel(new InventoryModel());
|
||||
architecture.RegisterSystem(new InventoryPanelSystem());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
InstallModule(new DerivedInventoryModule());
|
||||
}
|
||||
}
|
||||
}
|
||||
"""));
|
||||
|
||||
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
|
||||
markup.Source,
|
||||
markup.WithSpan(
|
||||
new DiagnosticResult("GF_ContextRegistration_001", DiagnosticSeverity.Warning)
|
||||
.WithArguments("IInventoryModel", "InventoryPanelSystem", "GameArchitecture"),
|
||||
"0"));
|
||||
return AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(Wrap(source));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造 Context 注册分析器的统一预期诊断,以保持断言参数顺序稳定。
|
||||
/// </summary>
|
||||
/// <param name="diagnosticId">预期诊断 ID。</param>
|
||||
/// <param name="serviceType">缺失注册的服务或依赖类型。</param>
|
||||
/// <param name="ownerType">触发访问的拥有者类型。</param>
|
||||
/// <param name="architectureType">推导出的所属 Architecture 类型。</param>
|
||||
/// <returns>配置好参数的预期诊断结果。</returns>
|
||||
private static DiagnosticResult CreateContextRegistrationWarning(
|
||||
string diagnosticId,
|
||||
string serviceType,
|
||||
string ownerType,
|
||||
string architectureType)
|
||||
{
|
||||
return new DiagnosticResult(diagnosticId, DiagnosticSeverity.Warning)
|
||||
.WithArguments(serviceType, ownerType, architectureType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将公共测试前导代码与具体场景源码拼接为完整编译单元。
|
||||
/// </summary>
|
||||
/// <param name="source">具体测试场景源码。</param>
|
||||
/// <returns>包含公共前导代码的完整源码文本。</returns>
|
||||
private static string Wrap(string source)
|
||||
{
|
||||
return $"{TestPreamble}{Environment.NewLine}{Environment.NewLine}{source}";
|
||||
|
||||
@ -3,110 +3,324 @@ using GFramework.SourceGenerators.Tests.Core;
|
||||
|
||||
namespace GFramework.SourceGenerators.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 验证 <see cref="AutoRegisterModuleGenerator" /> 在模块自动注册场景下的生成契约与输出顺序。
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class AutoRegisterModuleGeneratorTests
|
||||
{
|
||||
private const string AttributeOrderSource = """
|
||||
using System;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
|
||||
|
||||
namespace GFramework.Core.SourceGenerators.Abstractions.Architectures
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterModuleAttribute : Attribute { }
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterModelAttribute : Attribute
|
||||
{
|
||||
public RegisterModelAttribute(Type modelType) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterSystemAttribute : Attribute
|
||||
{
|
||||
public RegisterSystemAttribute(Type systemType) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterUtilityAttribute : Attribute
|
||||
{
|
||||
public RegisterUtilityAttribute(Type utilityType) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Architectures
|
||||
{
|
||||
public interface IArchitecture
|
||||
{
|
||||
T RegisterModel<T>(T model) where T : GFramework.Core.Abstractions.Model.IModel;
|
||||
T RegisterSystem<T>(T system) where T : GFramework.Core.Abstractions.Systems.ISystem;
|
||||
T RegisterUtility<T>(T utility) where T : GFramework.Core.Abstractions.Utility.IUtility;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Model
|
||||
{
|
||||
public interface IModel { }
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Systems
|
||||
{
|
||||
public interface ISystem { }
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Utility
|
||||
{
|
||||
public interface IUtility { }
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
|
||||
|
||||
public sealed class PlayerModel : IModel { }
|
||||
public sealed class CombatSystem : ISystem { }
|
||||
public sealed class AudioUtility : IUtility { }
|
||||
|
||||
[AutoRegisterModule]
|
||||
[RegisterSystem(typeof(CombatSystem))]
|
||||
[RegisterModel(typeof(PlayerModel))]
|
||||
[RegisterUtility(typeof(AudioUtility))]
|
||||
public partial class GameplayModule
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string AttributeOrderExpected = """
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
|
||||
namespace TestApp;
|
||||
|
||||
partial class GameplayModule
|
||||
{
|
||||
public void Install(global::GFramework.Core.Abstractions.Architectures.IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterSystem(new global::TestApp.CombatSystem());
|
||||
architecture.RegisterModel(new global::TestApp.PlayerModel());
|
||||
architecture.RegisterUtility(new global::TestApp.AudioUtility());
|
||||
}
|
||||
}
|
||||
|
||||
""";
|
||||
|
||||
private const string DeterministicOrderCommonSource = """
|
||||
using System;
|
||||
|
||||
namespace GFramework.Core.SourceGenerators.Abstractions.Architectures
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterModuleAttribute : Attribute { }
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterModelAttribute : Attribute
|
||||
{
|
||||
public RegisterModelAttribute(Type modelType) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterSystemAttribute : Attribute
|
||||
{
|
||||
public RegisterSystemAttribute(Type systemType) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterUtilityAttribute : Attribute
|
||||
{
|
||||
public RegisterUtilityAttribute(Type utilityType) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Architectures
|
||||
{
|
||||
public interface IArchitecture
|
||||
{
|
||||
T RegisterModel<T>(T model) where T : GFramework.Core.Abstractions.Model.IModel;
|
||||
T RegisterSystem<T>(T system) where T : GFramework.Core.Abstractions.Systems.ISystem;
|
||||
T RegisterUtility<T>(T utility) where T : GFramework.Core.Abstractions.Utility.IUtility;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Model
|
||||
{
|
||||
public interface IModel { }
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Systems
|
||||
{
|
||||
public interface ISystem { }
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Utility
|
||||
{
|
||||
public interface IUtility { }
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
|
||||
public sealed class PlayerModel : IModel { }
|
||||
public sealed class CombatSystem : ISystem { }
|
||||
public sealed class AudioUtility : IUtility { }
|
||||
}
|
||||
""";
|
||||
|
||||
private const string DeterministicOrderPartASource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
|
||||
|
||||
// Padding ensures this attribute lives later in the file than the attributes in PartB.
|
||||
// The generator should still place it first because PartA sorts before PartB.
|
||||
// padding 01
|
||||
// padding 02
|
||||
// padding 03
|
||||
// padding 04
|
||||
// padding 05
|
||||
// padding 06
|
||||
// padding 07
|
||||
// padding 08
|
||||
// padding 09
|
||||
// padding 10
|
||||
[AutoRegisterModule]
|
||||
[RegisterUtility(typeof(AudioUtility))]
|
||||
public partial class GameplayModule
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string DeterministicOrderPartBSource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
|
||||
|
||||
[RegisterSystem(typeof(CombatSystem))]
|
||||
[RegisterModel(typeof(PlayerModel))]
|
||||
public partial class GameplayModule
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string DeterministicOrderExpected = """
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
|
||||
namespace TestApp;
|
||||
|
||||
partial class GameplayModule
|
||||
{
|
||||
public void Install(global::GFramework.Core.Abstractions.Architectures.IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterUtility(new global::TestApp.AudioUtility());
|
||||
architecture.RegisterSystem(new global::TestApp.CombatSystem());
|
||||
architecture.RegisterModel(new global::TestApp.PlayerModel());
|
||||
}
|
||||
}
|
||||
|
||||
""";
|
||||
|
||||
private const string TypeConstraintSource = """
|
||||
#nullable enable
|
||||
using System;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
|
||||
|
||||
namespace GFramework.Core.SourceGenerators.Abstractions.Architectures
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterModuleAttribute : Attribute { }
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterModelAttribute : Attribute
|
||||
{
|
||||
public RegisterModelAttribute(Type modelType) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterSystemAttribute : Attribute
|
||||
{
|
||||
public RegisterSystemAttribute(Type systemType) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterUtilityAttribute : Attribute
|
||||
{
|
||||
public RegisterUtilityAttribute(Type utilityType) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Architectures
|
||||
{
|
||||
public interface IArchitecture
|
||||
{
|
||||
T RegisterModel<T>(T model) where T : GFramework.Core.Abstractions.Model.IModel;
|
||||
T RegisterSystem<T>(T system) where T : GFramework.Core.Abstractions.Systems.ISystem;
|
||||
T RegisterUtility<T>(T utility) where T : GFramework.Core.Abstractions.Utility.IUtility;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Model
|
||||
{
|
||||
public interface IModel { }
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Systems
|
||||
{
|
||||
public interface ISystem { }
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Utility
|
||||
{
|
||||
public interface IUtility { }
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
|
||||
|
||||
public sealed class PlayerModel : IModel { }
|
||||
|
||||
[AutoRegisterModule]
|
||||
[RegisterModel(typeof(PlayerModel))]
|
||||
public partial class GameplayModule<TNullableRef, TNotNull, TUnmanaged>
|
||||
where TNullableRef : class?
|
||||
where TNotNull : notnull
|
||||
where TUnmanaged : unmanaged
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string TypeConstraintExpected = """
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
|
||||
namespace TestApp;
|
||||
|
||||
partial class GameplayModule<TNullableRef, TNotNull, TUnmanaged>
|
||||
where TNullableRef : class?
|
||||
where TNotNull : notnull
|
||||
where TUnmanaged : unmanaged
|
||||
{
|
||||
public void Install(global::GFramework.Core.Abstractions.Architectures.IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterModel(new global::TestApp.PlayerModel());
|
||||
}
|
||||
}
|
||||
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// 验证同一声明上的注册特性会按照源码中的书写顺序生成安装代码。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Generates_Module_Install_Method_In_Attribute_Order()
|
||||
public Task Generates_Module_Install_Method_In_Attribute_Order()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
|
||||
|
||||
namespace GFramework.Core.SourceGenerators.Abstractions.Architectures
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterModuleAttribute : Attribute { }
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterModelAttribute : Attribute
|
||||
{
|
||||
public RegisterModelAttribute(Type modelType) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterSystemAttribute : Attribute
|
||||
{
|
||||
public RegisterSystemAttribute(Type systemType) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterUtilityAttribute : Attribute
|
||||
{
|
||||
public RegisterUtilityAttribute(Type utilityType) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Architectures
|
||||
{
|
||||
public interface IArchitecture
|
||||
{
|
||||
T RegisterModel<T>(T model) where T : GFramework.Core.Abstractions.Model.IModel;
|
||||
T RegisterSystem<T>(T system) where T : GFramework.Core.Abstractions.Systems.ISystem;
|
||||
T RegisterUtility<T>(T utility) where T : GFramework.Core.Abstractions.Utility.IUtility;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Model
|
||||
{
|
||||
public interface IModel { }
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Systems
|
||||
{
|
||||
public interface ISystem { }
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Utility
|
||||
{
|
||||
public interface IUtility { }
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
|
||||
|
||||
public sealed class PlayerModel : IModel { }
|
||||
public sealed class CombatSystem : ISystem { }
|
||||
public sealed class AudioUtility : IUtility { }
|
||||
|
||||
[AutoRegisterModule]
|
||||
[RegisterSystem(typeof(CombatSystem))]
|
||||
[RegisterModel(typeof(PlayerModel))]
|
||||
[RegisterUtility(typeof(AudioUtility))]
|
||||
public partial class GameplayModule
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
|
||||
namespace TestApp;
|
||||
|
||||
partial class GameplayModule
|
||||
{
|
||||
public void Install(global::GFramework.Core.Abstractions.Architectures.IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterSystem(new global::TestApp.CombatSystem());
|
||||
architecture.RegisterModel(new global::TestApp.PlayerModel());
|
||||
architecture.RegisterUtility(new global::TestApp.AudioUtility());
|
||||
}
|
||||
}
|
||||
|
||||
""";
|
||||
|
||||
await GeneratorTest<AutoRegisterModuleGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_GameplayModule.AutoRegisterModule.g.cs", expected));
|
||||
return GeneratorTest<AutoRegisterModuleGenerator>.RunAsync(
|
||||
AttributeOrderSource,
|
||||
("TestApp_GameplayModule.AutoRegisterModule.g.cs", AttributeOrderExpected));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -115,140 +329,20 @@ public class AutoRegisterModuleGeneratorTests
|
||||
[Test]
|
||||
public async Task Generates_Module_Install_Method_In_Deterministic_Order_Across_Partial_Declarations()
|
||||
{
|
||||
const string commonSource = """
|
||||
using System;
|
||||
|
||||
namespace GFramework.Core.SourceGenerators.Abstractions.Architectures
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterModuleAttribute : Attribute { }
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterModelAttribute : Attribute
|
||||
{
|
||||
public RegisterModelAttribute(Type modelType) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterSystemAttribute : Attribute
|
||||
{
|
||||
public RegisterSystemAttribute(Type systemType) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterUtilityAttribute : Attribute
|
||||
{
|
||||
public RegisterUtilityAttribute(Type utilityType) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Architectures
|
||||
{
|
||||
public interface IArchitecture
|
||||
{
|
||||
T RegisterModel<T>(T model) where T : GFramework.Core.Abstractions.Model.IModel;
|
||||
T RegisterSystem<T>(T system) where T : GFramework.Core.Abstractions.Systems.ISystem;
|
||||
T RegisterUtility<T>(T utility) where T : GFramework.Core.Abstractions.Utility.IUtility;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Model
|
||||
{
|
||||
public interface IModel { }
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Systems
|
||||
{
|
||||
public interface ISystem { }
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Utility
|
||||
{
|
||||
public interface IUtility { }
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
|
||||
public sealed class PlayerModel : IModel { }
|
||||
public sealed class CombatSystem : ISystem { }
|
||||
public sealed class AudioUtility : IUtility { }
|
||||
}
|
||||
""";
|
||||
|
||||
const string partASource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
|
||||
|
||||
// Padding ensures this attribute lives later in the file than the attributes in PartB.
|
||||
// The generator should still place it first because PartA sorts before PartB.
|
||||
// padding 01
|
||||
// padding 02
|
||||
// padding 03
|
||||
// padding 04
|
||||
// padding 05
|
||||
// padding 06
|
||||
// padding 07
|
||||
// padding 08
|
||||
// padding 09
|
||||
// padding 10
|
||||
[AutoRegisterModule]
|
||||
[RegisterUtility(typeof(AudioUtility))]
|
||||
public partial class GameplayModule
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
const string partBSource = """
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
|
||||
|
||||
[RegisterSystem(typeof(CombatSystem))]
|
||||
[RegisterModel(typeof(PlayerModel))]
|
||||
public partial class GameplayModule
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
|
||||
namespace TestApp;
|
||||
|
||||
partial class GameplayModule
|
||||
{
|
||||
public void Install(global::GFramework.Core.Abstractions.Architectures.IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterUtility(new global::TestApp.AudioUtility());
|
||||
architecture.RegisterSystem(new global::TestApp.CombatSystem());
|
||||
architecture.RegisterModel(new global::TestApp.PlayerModel());
|
||||
}
|
||||
}
|
||||
|
||||
""";
|
||||
|
||||
var test = new CSharpSourceGeneratorTest<AutoRegisterModuleGenerator, DefaultVerifier>
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources =
|
||||
{
|
||||
("Common.cs", commonSource),
|
||||
("GameplayModule.PartA.cs", partASource),
|
||||
("GameplayModule.PartB.cs", partBSource)
|
||||
("Common.cs", DeterministicOrderCommonSource),
|
||||
("GameplayModule.PartA.cs", DeterministicOrderPartASource),
|
||||
("GameplayModule.PartB.cs", DeterministicOrderPartBSource)
|
||||
},
|
||||
GeneratedSources =
|
||||
{
|
||||
(typeof(AutoRegisterModuleGenerator), "TestApp_GameplayModule.AutoRegisterModule.g.cs",
|
||||
NormalizeLineEndings(expected))
|
||||
NormalizeLineEndings(DeterministicOrderExpected))
|
||||
}
|
||||
},
|
||||
DisabledDiagnostics = { "GF_Common_Trace_001" }
|
||||
@ -261,102 +355,11 @@ public class AutoRegisterModuleGeneratorTests
|
||||
/// 验证生成器会保留可空引用、notnull 与 unmanaged 约束。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Generates_Type_Constraints_For_NullableReference_NotNull_And_Unmanaged()
|
||||
public Task Generates_Type_Constraints_For_NullableReference_NotNull_And_Unmanaged()
|
||||
{
|
||||
const string source = """
|
||||
#nullable enable
|
||||
using System;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
|
||||
|
||||
namespace GFramework.Core.SourceGenerators.Abstractions.Architectures
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterModuleAttribute : Attribute { }
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterModelAttribute : Attribute
|
||||
{
|
||||
public RegisterModelAttribute(Type modelType) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterSystemAttribute : Attribute
|
||||
{
|
||||
public RegisterSystemAttribute(Type systemType) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class RegisterUtilityAttribute : Attribute
|
||||
{
|
||||
public RegisterUtilityAttribute(Type utilityType) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Architectures
|
||||
{
|
||||
public interface IArchitecture
|
||||
{
|
||||
T RegisterModel<T>(T model) where T : GFramework.Core.Abstractions.Model.IModel;
|
||||
T RegisterSystem<T>(T system) where T : GFramework.Core.Abstractions.Systems.ISystem;
|
||||
T RegisterUtility<T>(T utility) where T : GFramework.Core.Abstractions.Utility.IUtility;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Model
|
||||
{
|
||||
public interface IModel { }
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Systems
|
||||
{
|
||||
public interface ISystem { }
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Utility
|
||||
{
|
||||
public interface IUtility { }
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
|
||||
|
||||
public sealed class PlayerModel : IModel { }
|
||||
|
||||
[AutoRegisterModule]
|
||||
[RegisterModel(typeof(PlayerModel))]
|
||||
public partial class GameplayModule<TNullableRef, TNotNull, TUnmanaged>
|
||||
where TNullableRef : class?
|
||||
where TNotNull : notnull
|
||||
where TUnmanaged : unmanaged
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
|
||||
namespace TestApp;
|
||||
|
||||
partial class GameplayModule<TNullableRef, TNotNull, TUnmanaged>
|
||||
where TNullableRef : class?
|
||||
where TNotNull : notnull
|
||||
where TUnmanaged : unmanaged
|
||||
{
|
||||
public void Install(global::GFramework.Core.Abstractions.Architectures.IArchitecture architecture)
|
||||
{
|
||||
architecture.RegisterModel(new global::TestApp.PlayerModel());
|
||||
}
|
||||
}
|
||||
|
||||
""";
|
||||
|
||||
await GeneratorTest<AutoRegisterModuleGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_GameplayModule.AutoRegisterModule.g.cs", expected));
|
||||
return GeneratorTest<AutoRegisterModuleGenerator>.RunAsync(
|
||||
TypeConstraintSource,
|
||||
("TestApp_GameplayModule.AutoRegisterModule.g.cs", TypeConstraintExpected));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -13,7 +13,7 @@ public class SchemaConfigGeneratorEnumTests
|
||||
/// 验证对象 <c>enum</c> 文档输出与快照保持一致。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Snapshot_Should_Preserve_Object_Enum_Documentation()
|
||||
public Task Snapshot_Should_Preserve_Object_Enum_Documentation()
|
||||
{
|
||||
const string source = """
|
||||
namespace TestApp
|
||||
@ -51,14 +51,14 @@ public class SchemaConfigGeneratorEnumTests
|
||||
("monster.schema.json", schema));
|
||||
|
||||
Assert.That(result.Results.Single().Diagnostics, Is.Empty);
|
||||
await AssertSnapshotAsync(result, "MonsterConfig.ObjectEnum.g.txt");
|
||||
return AssertSnapshotAsync(result, "MonsterConfig.ObjectEnum.g.txt");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证数组项 <c>enum</c> 文档回退输出与快照保持一致。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Snapshot_Should_Preserve_Array_Item_Enum_Documentation_Fallback()
|
||||
public Task Snapshot_Should_Preserve_Array_Item_Enum_Documentation_Fallback()
|
||||
{
|
||||
const string source = """
|
||||
namespace TestApp
|
||||
@ -88,14 +88,14 @@ public class SchemaConfigGeneratorEnumTests
|
||||
("monster.schema.json", schema));
|
||||
|
||||
Assert.That(result.Results.Single().Diagnostics, Is.Empty);
|
||||
await AssertSnapshotAsync(result, "MonsterConfig.ArrayItemEnum.g.txt");
|
||||
return AssertSnapshotAsync(result, "MonsterConfig.ArrayItemEnum.g.txt");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证对象数组项 <c>enum</c> 文档回退输出与快照保持一致。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Snapshot_Should_Preserve_Array_Object_Item_Enum_Documentation_Fallback()
|
||||
public Task Snapshot_Should_Preserve_Array_Object_Item_Enum_Documentation_Fallback()
|
||||
{
|
||||
const string source = """
|
||||
namespace TestApp
|
||||
@ -136,7 +136,7 @@ public class SchemaConfigGeneratorEnumTests
|
||||
("monster.schema.json", schema));
|
||||
|
||||
Assert.That(result.Results.Single().Diagnostics, Is.Empty);
|
||||
await AssertSnapshotAsync(result, "MonsterConfig.ArrayObjectItemEnum.g.txt");
|
||||
return AssertSnapshotAsync(result, "MonsterConfig.ArrayObjectItemEnum.g.txt");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -176,11 +176,11 @@ public class SchemaConfigGeneratorEnumTests
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(snapshotFolder);
|
||||
await File.WriteAllTextAsync(path, actual);
|
||||
await File.WriteAllTextAsync(path, actual).ConfigureAwait(false);
|
||||
Assert.Fail($"Snapshot not found. Generated new snapshot at:\n{path}");
|
||||
}
|
||||
|
||||
var expected = await File.ReadAllTextAsync(path);
|
||||
var expected = await File.ReadAllTextAsync(path).ConfigureAwait(false);
|
||||
Assert.That(
|
||||
Normalize(expected),
|
||||
Is.EqualTo(Normalize(actual)),
|
||||
|
||||
@ -8,171 +8,241 @@ namespace GFramework.SourceGenerators.Tests.Config;
|
||||
[TestFixture]
|
||||
public class SchemaConfigGeneratorSnapshotTests
|
||||
{
|
||||
private const string RuntimeContractsSource = """
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GFramework.Game.Abstractions.Config
|
||||
{
|
||||
public interface IConfigTable
|
||||
{
|
||||
Type KeyType { get; }
|
||||
Type ValueType { get; }
|
||||
int Count { get; }
|
||||
}
|
||||
|
||||
public interface IConfigTable<TKey, TValue> : IConfigTable
|
||||
where TKey : notnull
|
||||
{
|
||||
TValue Get(TKey key);
|
||||
bool TryGet(TKey key, out TValue? value);
|
||||
bool ContainsKey(TKey key);
|
||||
IReadOnlyCollection<TValue> All();
|
||||
}
|
||||
|
||||
public interface IConfigRegistry
|
||||
{
|
||||
IConfigTable<TKey, TValue> GetTable<TKey, TValue>(string name)
|
||||
where TKey : notnull;
|
||||
|
||||
bool TryGetTable<TKey, TValue>(string name, out IConfigTable<TKey, TValue>? table)
|
||||
where TKey : notnull;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Game.Config
|
||||
{
|
||||
public sealed class YamlConfigLoader
|
||||
{
|
||||
public YamlConfigLoader RegisterTable<TKey, TValue>(
|
||||
string tableName,
|
||||
string relativePath,
|
||||
string schemaRelativePath,
|
||||
Func<TValue, TKey> keySelector,
|
||||
IEqualityComparer<TKey>? comparer = null)
|
||||
where TKey : notnull
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string MonsterSchema = """
|
||||
{
|
||||
"title": "Monster Config",
|
||||
"description": "Represents one monster entry generated from schema metadata.",
|
||||
"type": "object",
|
||||
"minProperties": 4,
|
||||
"maxProperties": 8,
|
||||
"required": ["id", "name", "reward", "phases"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"description": "Unique monster identifier."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Monster Name",
|
||||
"description": "Localized monster display name.",
|
||||
"x-gframework-index": true,
|
||||
"minLength": 3,
|
||||
"maxLength": 16,
|
||||
"pattern": "^[A-Z][a-z]+$",
|
||||
"default": "Slime",
|
||||
"enum": ["Slime", "Goblin"]
|
||||
},
|
||||
"hp": {
|
||||
"type": "integer",
|
||||
"const": 10,
|
||||
"minimum": 1,
|
||||
"maximum": 999,
|
||||
"exclusiveMinimum": 0,
|
||||
"exclusiveMaximum": 1000,
|
||||
"multipleOf": 5,
|
||||
"default": 10
|
||||
},
|
||||
"dropItems": {
|
||||
"description": "Referenced drop ids.",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 3,
|
||||
"minContains": 1,
|
||||
"maxContains": 2,
|
||||
"uniqueItems": true,
|
||||
"contains": {
|
||||
"type": "string",
|
||||
"const": "potion"
|
||||
},
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 12,
|
||||
"enum": ["potion", "slime_gel"]
|
||||
},
|
||||
"default": ["potion"],
|
||||
"x-gframework-ref-table": "item"
|
||||
},
|
||||
"reward": {
|
||||
"type": "object",
|
||||
"description": "Reward payload.",
|
||||
"minProperties": 2,
|
||||
"maxProperties": 2,
|
||||
"required": ["gold", "currency"],
|
||||
"properties": {
|
||||
"gold": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 10
|
||||
},
|
||||
"currency": {
|
||||
"type": "string",
|
||||
"enum": ["coin", "gem"]
|
||||
}
|
||||
},
|
||||
"dependentRequired": {
|
||||
"currency": ["gold"]
|
||||
},
|
||||
"dependentSchemas": {
|
||||
"currency": {
|
||||
"type": "object",
|
||||
"required": ["gold"],
|
||||
"properties": {
|
||||
"gold": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["gold"],
|
||||
"properties": {
|
||||
"gold": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"if": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"currency": {
|
||||
"type": "string",
|
||||
"const": "gem"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"type": "object",
|
||||
"required": ["gold"],
|
||||
"properties": {
|
||||
"gold": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"else": {
|
||||
"type": "object",
|
||||
"required": ["currency"],
|
||||
"properties": {
|
||||
"currency": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"phases": {
|
||||
"type": "array",
|
||||
"description": "Encounter phases.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["wave", "monsterId"],
|
||||
"properties": {
|
||||
"wave": {
|
||||
"type": "integer"
|
||||
},
|
||||
"monsterId": {
|
||||
"type": "string",
|
||||
"description": "Monster reference id.",
|
||||
"minLength": 2,
|
||||
"maxLength": 32,
|
||||
"x-gframework-ref-table": "monster"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// 验证一个最小 monster schema 能生成配置类型、表包装和注册辅助。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Snapshot_SchemaConfigGenerator()
|
||||
public Task Snapshot_SchemaConfigGenerator()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GFramework.Game.Abstractions.Config
|
||||
{
|
||||
public interface IConfigTable
|
||||
{
|
||||
Type KeyType { get; }
|
||||
Type ValueType { get; }
|
||||
int Count { get; }
|
||||
}
|
||||
|
||||
public interface IConfigTable<TKey, TValue> : IConfigTable
|
||||
where TKey : notnull
|
||||
{
|
||||
TValue Get(TKey key);
|
||||
bool TryGet(TKey key, out TValue? value);
|
||||
bool ContainsKey(TKey key);
|
||||
IReadOnlyCollection<TValue> All();
|
||||
}
|
||||
|
||||
public interface IConfigRegistry
|
||||
{
|
||||
IConfigTable<TKey, TValue> GetTable<TKey, TValue>(string name)
|
||||
where TKey : notnull;
|
||||
|
||||
bool TryGetTable<TKey, TValue>(string name, out IConfigTable<TKey, TValue>? table)
|
||||
where TKey : notnull;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Game.Config
|
||||
{
|
||||
public sealed class YamlConfigLoader
|
||||
{
|
||||
public YamlConfigLoader RegisterTable<TKey, TValue>(
|
||||
string tableName,
|
||||
string relativePath,
|
||||
string schemaRelativePath,
|
||||
Func<TValue, TKey> keySelector,
|
||||
IEqualityComparer<TKey>? comparer = null)
|
||||
where TKey : notnull
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
const string schema = """
|
||||
{
|
||||
"title": "Monster Config",
|
||||
"description": "Represents one monster entry generated from schema metadata.",
|
||||
"type": "object",
|
||||
"minProperties": 4,
|
||||
"maxProperties": 8,
|
||||
"required": ["id", "name", "reward", "phases"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"description": "Unique monster identifier."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Monster Name",
|
||||
"description": "Localized monster display name.",
|
||||
"x-gframework-index": true,
|
||||
"minLength": 3,
|
||||
"maxLength": 16,
|
||||
"pattern": "^[A-Z][a-z]+$",
|
||||
"default": "Slime",
|
||||
"enum": ["Slime", "Goblin"]
|
||||
},
|
||||
"hp": {
|
||||
"type": "integer",
|
||||
"const": 10,
|
||||
"minimum": 1,
|
||||
"maximum": 999,
|
||||
"exclusiveMinimum": 0,
|
||||
"exclusiveMaximum": 1000,
|
||||
"multipleOf": 5,
|
||||
"default": 10
|
||||
},
|
||||
"dropItems": {
|
||||
"description": "Referenced drop ids.",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 3,
|
||||
"minContains": 1,
|
||||
"maxContains": 2,
|
||||
"uniqueItems": true,
|
||||
"contains": {
|
||||
"type": "string",
|
||||
"const": "potion"
|
||||
},
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 12,
|
||||
"enum": ["potion", "slime_gel"]
|
||||
},
|
||||
"default": ["potion"],
|
||||
"x-gframework-ref-table": "item"
|
||||
},
|
||||
"reward": {
|
||||
"type": "object",
|
||||
"description": "Reward payload.",
|
||||
"minProperties": 2,
|
||||
"maxProperties": 2,
|
||||
"required": ["gold", "currency"],
|
||||
"properties": {
|
||||
"gold": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 10
|
||||
},
|
||||
"currency": {
|
||||
"type": "string",
|
||||
"enum": ["coin", "gem"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"phases": {
|
||||
"type": "array",
|
||||
"description": "Encounter phases.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["wave", "monsterId"],
|
||||
"properties": {
|
||||
"wave": {
|
||||
"type": "integer"
|
||||
},
|
||||
"monsterId": {
|
||||
"type": "string",
|
||||
"description": "Monster reference id.",
|
||||
"minLength": 2,
|
||||
"maxLength": 32,
|
||||
"x-gframework-ref-table": "monster"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
var generatedSources = GenerateSourcesForMonsterSchema();
|
||||
var snapshotFolder = GetSchemaSnapshotFolder();
|
||||
return AssertAllSnapshotsAsync(generatedSources, snapshotFolder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行 monster schema 场景,并把生成结果转换为按 hint name 索引的字典。
|
||||
/// </summary>
|
||||
/// <returns>当前快照场景的全部生成文件内容。</returns>
|
||||
private static IReadOnlyDictionary<string, string> GenerateSourcesForMonsterSchema()
|
||||
{
|
||||
var result = SchemaGeneratorTestDriver.Run(
|
||||
source,
|
||||
("monster.schema.json", schema));
|
||||
RuntimeContractsSource,
|
||||
("monster.schema.json", MonsterSchema));
|
||||
|
||||
var generatedSources = result.Results
|
||||
return result.Results
|
||||
.Single()
|
||||
.GeneratedSources
|
||||
.ToDictionary(
|
||||
static sourceResult => sourceResult.HintName,
|
||||
static sourceResult => sourceResult.SourceText.ToString(),
|
||||
StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析 schema 生成器快照目录,确保断言始终落在仓库内已提交的 snapshot 资产上。
|
||||
/// </summary>
|
||||
/// <returns>schema 生成器快照目录的绝对路径。</returns>
|
||||
private static string GetSchemaSnapshotFolder()
|
||||
{
|
||||
var snapshotFolder = Path.Combine(
|
||||
TestContext.CurrentContext.TestDirectory,
|
||||
"..",
|
||||
@ -181,14 +251,7 @@ public class SchemaConfigGeneratorSnapshotTests
|
||||
"Config",
|
||||
"snapshots",
|
||||
"SchemaConfigGenerator");
|
||||
snapshotFolder = Path.GetFullPath(snapshotFolder);
|
||||
|
||||
await AssertSnapshotAsync(generatedSources, snapshotFolder, "MonsterConfig.g.cs", "MonsterConfig.g.txt");
|
||||
await AssertSnapshotAsync(generatedSources, snapshotFolder, "MonsterTable.g.cs", "MonsterTable.g.txt");
|
||||
await AssertSnapshotAsync(generatedSources, snapshotFolder, "MonsterConfigBindings.g.cs",
|
||||
"MonsterConfigBindings.g.txt");
|
||||
await AssertSnapshotAsync(generatedSources, snapshotFolder, "GeneratedConfigCatalog.g.cs",
|
||||
"GeneratedConfigCatalog.g.txt");
|
||||
return Path.GetFullPath(snapshotFolder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -213,17 +276,45 @@ public class SchemaConfigGeneratorSnapshotTests
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(snapshotFolder);
|
||||
await File.WriteAllTextAsync(path, actual);
|
||||
await File.WriteAllTextAsync(path, actual).ConfigureAwait(false);
|
||||
Assert.Fail($"Snapshot not found. Generated new snapshot at:\n{path}");
|
||||
}
|
||||
|
||||
var expected = await File.ReadAllTextAsync(path);
|
||||
var expected = await File.ReadAllTextAsync(path).ConfigureAwait(false);
|
||||
Assert.That(
|
||||
Normalize(expected),
|
||||
Is.EqualTo(Normalize(actual)),
|
||||
$"Snapshot mismatch: {generatedFileName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 依次验证 schema 生成器产出的全部核心快照文件。
|
||||
/// </summary>
|
||||
/// <param name="generatedSources">生成结果字典。</param>
|
||||
/// <param name="snapshotFolder">快照目录。</param>
|
||||
/// <returns>全部快照断言完成后的异步任务。</returns>
|
||||
private static async Task AssertAllSnapshotsAsync(
|
||||
IReadOnlyDictionary<string, string> generatedSources,
|
||||
string snapshotFolder)
|
||||
{
|
||||
await AssertSnapshotAsync(generatedSources, snapshotFolder, "MonsterConfig.g.cs", "MonsterConfig.g.txt")
|
||||
.ConfigureAwait(false);
|
||||
await AssertSnapshotAsync(generatedSources, snapshotFolder, "MonsterTable.g.cs", "MonsterTable.g.txt")
|
||||
.ConfigureAwait(false);
|
||||
await AssertSnapshotAsync(
|
||||
generatedSources,
|
||||
snapshotFolder,
|
||||
"MonsterConfigBindings.g.cs",
|
||||
"MonsterConfigBindings.g.txt")
|
||||
.ConfigureAwait(false);
|
||||
await AssertSnapshotAsync(
|
||||
generatedSources,
|
||||
snapshotFolder,
|
||||
"GeneratedConfigCatalog.g.cs",
|
||||
"GeneratedConfigCatalog.g.txt")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标准化快照文本以避免平台换行差异。
|
||||
/// </summary>
|
||||
|
||||
@ -78,7 +78,7 @@ public sealed partial class MonsterConfig
|
||||
/// Reward payload.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Constraints: minProperties = 2, maxProperties = 2.
|
||||
/// Constraints: minProperties = 2, maxProperties = 2, dependentRequired = { currency => [gold] }, dependentSchemas = { currency => object (required = [gold]) }, allOf = [ object (required = [gold]) ], if/then/else = if object; properties = { currency: string (const = "gem") }; then object (required = [gold]); properties = { gold: integer }; else object (required = [currency]); properties = { currency: string }.
|
||||
/// </remarks>
|
||||
public sealed partial class RewardConfig
|
||||
{
|
||||
|
||||
@ -15,7 +15,7 @@ public static class AnalyzerTestDriver<TAnalyzer>
|
||||
/// <param name="source">测试输入源码。</param>
|
||||
/// <param name="diagnostics">期望诊断集合。</param>
|
||||
/// <returns>异步测试任务。</returns>
|
||||
public static async Task RunAsync(
|
||||
public static Task RunAsync(
|
||||
string source,
|
||||
params DiagnosticResult[] diagnostics)
|
||||
{
|
||||
@ -29,6 +29,6 @@ public static class AnalyzerTestDriver<TAnalyzer>
|
||||
};
|
||||
|
||||
test.ExpectedDiagnostics.AddRange(diagnostics);
|
||||
await test.RunAsync();
|
||||
return test.RunAsync();
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
|
||||
namespace GFramework.SourceGenerators.Tests.Core;
|
||||
|
||||
@ -28,74 +29,12 @@ public static class GeneratorSnapshotTest<TGenerator>
|
||||
string snapshotFolder,
|
||||
Func<string, string>? snapshotFileNameSelector = null)
|
||||
{
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(source);
|
||||
var compilation = CSharpCompilation.Create(
|
||||
$"{typeof(TGenerator).Name}SnapshotTests",
|
||||
[syntaxTree],
|
||||
MetadataReferenceTestBuilder.GetRuntimeMetadataReferences(),
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
GeneratorDriver driver = CSharpGeneratorDriver.Create(
|
||||
generators: [CreateGenerator()],
|
||||
parseOptions: (CSharpParseOptions)syntaxTree.Options);
|
||||
driver = driver.RunGeneratorsAndUpdateCompilation(
|
||||
compilation,
|
||||
out var updatedCompilation,
|
||||
out var generatorDiagnostics);
|
||||
var (driver, updatedCompilation, generatorDiagnostics) = RunGenerator(source);
|
||||
AssertNoGeneratorErrors(generatorDiagnostics);
|
||||
AssertNoCompilationErrors(updatedCompilation);
|
||||
|
||||
var generatorErrors = generatorDiagnostics
|
||||
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
|
||||
.ToArray();
|
||||
Assert.That(
|
||||
generatorErrors,
|
||||
Is.Empty,
|
||||
() =>
|
||||
$"执行生成器时出现错误:{Environment.NewLine}{string.Join(Environment.NewLine, generatorErrors.Select(static diagnostic => diagnostic.ToString()))}");
|
||||
|
||||
var compilationErrors = updatedCompilation.GetDiagnostics()
|
||||
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
|
||||
.ToArray();
|
||||
Assert.That(
|
||||
compilationErrors,
|
||||
Is.Empty,
|
||||
() =>
|
||||
$"编译生成的代码时出现错误:{Environment.NewLine}{string.Join(Environment.NewLine, compilationErrors.Select(static diagnostic => diagnostic.ToString()))}");
|
||||
|
||||
var runResult = driver.GetRunResult();
|
||||
var generated = runResult.Results
|
||||
.SelectMany(static result => result.GeneratedSources)
|
||||
.OrderBy(static source => source.HintName, StringComparer.Ordinal)
|
||||
.Select(static source => (filename: source.HintName, content: source.SourceText.ToString()))
|
||||
.ToArray();
|
||||
Assert.That(
|
||||
generated,
|
||||
Is.Not.Empty,
|
||||
$"生成器 '{typeof(TGenerator).FullName}' 未产生任何输出。");
|
||||
|
||||
foreach (var (filename, content) in generated)
|
||||
{
|
||||
// 不同测试套件可能需要将生成文件映射到非 .cs 快照,以避免测试资产被当作可编译源码参与构建。
|
||||
var snapshotFileName = snapshotFileNameSelector?.Invoke(filename) ?? filename;
|
||||
var path = ResolveSnapshotPath(
|
||||
snapshotFolder,
|
||||
snapshotFileName);
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
// 第一次运行:生成 snapshot
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||||
await File.WriteAllTextAsync(path, content.ToString());
|
||||
|
||||
Assert.Fail(
|
||||
$"未找到快照文件,已在以下路径生成新快照:\n{path}");
|
||||
}
|
||||
|
||||
var expected = await File.ReadAllTextAsync(path);
|
||||
|
||||
Assert.That(
|
||||
Normalize(expected),
|
||||
Is.EqualTo(Normalize(content.ToString())),
|
||||
$"快照不匹配:{snapshotFileName}");
|
||||
}
|
||||
var generatedSources = GetGeneratedSources(driver);
|
||||
await AssertGeneratedSnapshotsAsync(generatedSources, snapshotFolder, snapshotFileNameSelector).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -105,7 +44,163 @@ public static class GeneratorSnapshotTest<TGenerator>
|
||||
/// <returns>标准化后的文本</returns>
|
||||
private static string Normalize(string text)
|
||||
{
|
||||
return text.Replace("\r\n", "\n").Trim();
|
||||
return text.Replace("\r\n", "\n", StringComparison.Ordinal).Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建测试编译并执行目标生成器,返回更新后的编译结果和生成器诊断。
|
||||
/// </summary>
|
||||
/// <param name="source">要交给生成器处理的输入源码。</param>
|
||||
/// <returns>包含驱动、更新后编译和生成器诊断的元组。</returns>
|
||||
private static (GeneratorDriver Driver, Compilation UpdatedCompilation, ImmutableArray<Diagnostic> GeneratorDiagnostics)
|
||||
RunGenerator(string source)
|
||||
{
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(source);
|
||||
var compilation = CreateCompilation(syntaxTree);
|
||||
GeneratorDriver driver = CSharpGeneratorDriver.Create(
|
||||
generators: [CreateGenerator()],
|
||||
parseOptions: (CSharpParseOptions)syntaxTree.Options);
|
||||
|
||||
driver = driver.RunGeneratorsAndUpdateCompilation(
|
||||
compilation,
|
||||
out var updatedCompilation,
|
||||
out var generatorDiagnostics);
|
||||
|
||||
return (driver, updatedCompilation, generatorDiagnostics);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为快照测试创建最小可运行的 Roslyn 编译上下文。
|
||||
/// </summary>
|
||||
/// <param name="syntaxTree">由测试输入生成的语法树。</param>
|
||||
/// <returns>包含运行时元数据引用的动态链接库编译对象。</returns>
|
||||
private static CSharpCompilation CreateCompilation(SyntaxTree syntaxTree)
|
||||
{
|
||||
return CSharpCompilation.Create(
|
||||
$"{typeof(TGenerator).Name}SnapshotTests",
|
||||
[syntaxTree],
|
||||
MetadataReferenceTestBuilder.GetRuntimeMetadataReferences(),
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言生成器自身没有报告错误级诊断。
|
||||
/// </summary>
|
||||
/// <param name="generatorDiagnostics">生成器执行期间产生的诊断集合。</param>
|
||||
private static void AssertNoGeneratorErrors(ImmutableArray<Diagnostic> generatorDiagnostics)
|
||||
{
|
||||
var generatorErrors = generatorDiagnostics
|
||||
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
|
||||
.ToArray();
|
||||
Assert.That(
|
||||
generatorErrors,
|
||||
Is.Empty,
|
||||
() =>
|
||||
$"执行生成器时出现错误:{Environment.NewLine}{string.Join(Environment.NewLine, generatorErrors.Select(static diagnostic => diagnostic.ToString()))}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言合并生成结果后的最终编译仍然可通过。
|
||||
/// </summary>
|
||||
/// <param name="updatedCompilation">已注入生成输出的编译对象。</param>
|
||||
private static void AssertNoCompilationErrors(Compilation updatedCompilation)
|
||||
{
|
||||
var compilationErrors = updatedCompilation.GetDiagnostics()
|
||||
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
|
||||
.ToArray();
|
||||
Assert.That(
|
||||
compilationErrors,
|
||||
Is.Empty,
|
||||
() =>
|
||||
$"编译生成的代码时出现错误:{Environment.NewLine}{string.Join(Environment.NewLine, compilationErrors.Select(static diagnostic => diagnostic.ToString()))}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 收集并排序生成器输出,保持快照断言顺序稳定。
|
||||
/// </summary>
|
||||
/// <param name="driver">已经执行完成的生成器驱动。</param>
|
||||
/// <returns>按 HintName 排序后的生成文件名与内容。</returns>
|
||||
private static (string Filename, string Content)[] GetGeneratedSources(GeneratorDriver driver)
|
||||
{
|
||||
var generatedSources = driver.GetRunResult()
|
||||
.Results
|
||||
.SelectMany(static result => result.GeneratedSources)
|
||||
.OrderBy(static source => source.HintName, StringComparer.Ordinal)
|
||||
.Select(static source => (source.HintName, source.SourceText.ToString()))
|
||||
.ToArray();
|
||||
|
||||
Assert.That(
|
||||
generatedSources,
|
||||
Is.Not.Empty,
|
||||
$"生成器 '{typeof(TGenerator).FullName}' 未产生任何输出。");
|
||||
|
||||
return generatedSources;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 逐个比对生成输出与已提交快照,必要时写出缺失快照并中断测试。
|
||||
/// </summary>
|
||||
/// <param name="generatedSources">已排序的生成文件名与内容。</param>
|
||||
/// <param name="snapshotFolder">快照根目录。</param>
|
||||
/// <param name="snapshotFileNameSelector">可选的快照文件名映射规则。</param>
|
||||
/// <returns>当全部快照比对完成后结束的异步任务。</returns>
|
||||
private static async Task AssertGeneratedSnapshotsAsync(
|
||||
(string Filename, string Content)[] generatedSources,
|
||||
string snapshotFolder,
|
||||
Func<string, string>? snapshotFileNameSelector)
|
||||
{
|
||||
foreach (var (filename, content) in generatedSources)
|
||||
{
|
||||
var snapshotFileName = snapshotFileNameSelector?.Invoke(filename) ?? filename;
|
||||
var expected = await ReadExpectedSnapshotAsync(
|
||||
snapshotFolder,
|
||||
snapshotFileName,
|
||||
content)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Assert.That(
|
||||
Normalize(expected),
|
||||
Is.EqualTo(Normalize(content)),
|
||||
$"快照不匹配:{snapshotFileName}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取指定快照;若快照不存在,则先写出当前生成结果并通过断言提示调用方提交资产。
|
||||
/// </summary>
|
||||
/// <param name="snapshotFolder">快照根目录。</param>
|
||||
/// <param name="snapshotFileName">映射后的快照文件名。</param>
|
||||
/// <param name="generatedContent">当前生成器输出内容。</param>
|
||||
/// <returns>现有快照的文本内容。</returns>
|
||||
private static async Task<string> ReadExpectedSnapshotAsync(
|
||||
string snapshotFolder,
|
||||
string snapshotFileName,
|
||||
string generatedContent)
|
||||
{
|
||||
// 不同测试套件可能需要将生成文件映射到非 .cs 快照,以避免测试资产被当作可编译源码参与构建。
|
||||
var path = ResolveSnapshotPath(snapshotFolder, snapshotFileName);
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
await WriteMissingSnapshotAndFailAsync(path, generatedContent).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await File.ReadAllTextAsync(path).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为首次运行缺失的快照写入当前结果,并立即终止测试以提醒提交新资产。
|
||||
/// </summary>
|
||||
/// <param name="path">目标快照绝对路径。</param>
|
||||
/// <param name="generatedContent">要写入的生成输出。</param>
|
||||
private static async Task WriteMissingSnapshotAndFailAsync(string path, string generatedContent)
|
||||
{
|
||||
// ResolveSnapshotPath 保证快照不会越界,但根目录路径仍会让 GetDirectoryName 返回 null。
|
||||
var snapshotDirectory = Path.GetDirectoryName(path)
|
||||
?? throw new InvalidOperationException(
|
||||
$"Snapshot path '{path}' must include a parent directory.");
|
||||
Directory.CreateDirectory(snapshotDirectory);
|
||||
await File.WriteAllTextAsync(path, generatedContent).ConfigureAwait(false);
|
||||
Assert.Fail($"未找到快照文件,已在以下路径生成新快照:\n{path}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -20,8 +20,8 @@ public class GeneratorSnapshotTestSecurityTests
|
||||
var snapshotRoot = CreateSnapshotRoot();
|
||||
var source = BuildSource();
|
||||
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
|
||||
Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
|
||||
source,
|
||||
snapshotRoot,
|
||||
_ => Path.Combine(snapshotRoot, "Status.EnumExtensions.g.cs")));
|
||||
@ -36,8 +36,8 @@ public class GeneratorSnapshotTestSecurityTests
|
||||
var snapshotRoot = CreateSnapshotRoot();
|
||||
var source = BuildSource();
|
||||
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
|
||||
Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
|
||||
source,
|
||||
snapshotRoot,
|
||||
_ => Path.Combine("..", "escaped", "Status.EnumExtensions.g.cs")));
|
||||
|
||||
@ -13,11 +13,11 @@ public static class GeneratorTest<TGenerator>
|
||||
/// <param name="source">输入的源代码</param>
|
||||
/// <param name="generatedSources">期望生成的源文件集合,包含文件名和内容的元组</param>
|
||||
/// <returns>异步操作任务</returns>
|
||||
public static async Task RunAsync(
|
||||
public static Task RunAsync(
|
||||
string source,
|
||||
params (string filename, string content)[] generatedSources)
|
||||
{
|
||||
await RunAsync(
|
||||
return RunAsync(
|
||||
source,
|
||||
additionalReferences: [],
|
||||
generatedSources);
|
||||
@ -30,7 +30,7 @@ public static class GeneratorTest<TGenerator>
|
||||
/// <param name="additionalReferences">附加元数据引用,用于构造多程序集场景。</param>
|
||||
/// <param name="generatedSources">期望生成的源文件集合,包含文件名和内容的元组。</param>
|
||||
/// <returns>异步操作任务。</returns>
|
||||
public static async Task RunAsync(
|
||||
public static Task RunAsync(
|
||||
string source,
|
||||
IEnumerable<MetadataReference> additionalReferences,
|
||||
params (string filename, string content)[] generatedSources)
|
||||
@ -52,7 +52,7 @@ public static class GeneratorTest<TGenerator>
|
||||
foreach (var additionalReference in additionalReferences)
|
||||
test.TestState.AdditionalReferences.Add(additionalReference);
|
||||
|
||||
await test.RunAsync();
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -4,559 +4,232 @@ using GFramework.SourceGenerators.Tests.Core;
|
||||
|
||||
namespace GFramework.SourceGenerators.Tests.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// 验证 <see cref="LoggerGenerator" /> 在常见日志声明配置下的快照输出保持稳定。
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class LoggerGeneratorSnapshotTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证默认配置下的类日志字段快照。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Snapshot_DefaultConfiguration_Class()
|
||||
public Task Snapshot_DefaultConfiguration_Class()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
|
||||
namespace GFramework.Core.SourceGenerators.Abstractions.Logging
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class LogAttribute : Attribute
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FieldName { get; set; }
|
||||
public string AccessModifier { get; set; }
|
||||
public bool IsStatic { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Logging
|
||||
{
|
||||
public interface ILogger
|
||||
{
|
||||
void Info(string message);
|
||||
void Error(string message);
|
||||
void Warn(string message);
|
||||
void Debug(string message);
|
||||
void Trace(string message);
|
||||
void Fatal(string message);
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Logging
|
||||
{
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
|
||||
public static class LoggerFactoryResolver
|
||||
{
|
||||
public static ILoggerProvider Provider { get; set; }
|
||||
|
||||
public static ILoggerProvider CreateLogger(string name)
|
||||
{
|
||||
return Provider ?? new MockLoggerProvider();
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILoggerProvider
|
||||
{
|
||||
ILogger CreateLogger(string name);
|
||||
}
|
||||
|
||||
internal class MockLoggerProvider : ILoggerProvider
|
||||
{
|
||||
public ILogger CreateLogger(string name)
|
||||
{
|
||||
return new MockLogger(name);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MockLogger : ILogger
|
||||
{
|
||||
private readonly string _name;
|
||||
|
||||
public MockLogger(string name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public void Info(string message) { }
|
||||
public void Error(string message) { }
|
||||
public void Warn(string message) { }
|
||||
public void Debug(string message) { }
|
||||
public void Trace(string message) { }
|
||||
public void Fatal(string message) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Logging;
|
||||
|
||||
[Log]
|
||||
public partial class MyService
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
||||
source,
|
||||
GetSnapshotFolder("DefaultConfiguration_Class"));
|
||||
return RunScenarioAsync(
|
||||
"DefaultConfiguration_Class",
|
||||
"[Log]",
|
||||
"public partial class MyService");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证自定义 logger 名称会反映到生成快照。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Snapshot_CustomName_Class()
|
||||
public Task Snapshot_CustomName_Class()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
|
||||
namespace GFramework.Core.SourceGenerators.Abstractions.Logging
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class LogAttribute : Attribute
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FieldName { get; set; }
|
||||
public string AccessModifier { get; set; }
|
||||
public bool IsStatic { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Logging
|
||||
{
|
||||
public interface ILogger
|
||||
{
|
||||
void Info(string message);
|
||||
void Error(string message);
|
||||
void Warn(string message);
|
||||
void Debug(string message);
|
||||
void Trace(string message);
|
||||
void Fatal(string message);
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Logging
|
||||
{
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
|
||||
public static class LoggerFactoryResolver
|
||||
{
|
||||
public static ILoggerProvider Provider { get; set; }
|
||||
|
||||
public static ILoggerProvider CreateLogger(string name)
|
||||
{
|
||||
return Provider ?? new MockLoggerProvider();
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILoggerProvider
|
||||
{
|
||||
ILogger CreateLogger(string name);
|
||||
}
|
||||
|
||||
internal class MockLoggerProvider : ILoggerProvider
|
||||
{
|
||||
public ILogger CreateLogger(string name)
|
||||
{
|
||||
return new MockLogger(name);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MockLogger : ILogger
|
||||
{
|
||||
private readonly string _name;
|
||||
|
||||
public MockLogger(string name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public void Info(string message) { }
|
||||
public void Error(string message) { }
|
||||
public void Warn(string message) { }
|
||||
public void Debug(string message) { }
|
||||
public void Trace(string message) { }
|
||||
public void Fatal(string message) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Logging;
|
||||
|
||||
[Log(Name = "CustomLogger")]
|
||||
public partial class MyService
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
||||
source,
|
||||
GetSnapshotFolder("CustomName_Class"));
|
||||
return RunScenarioAsync(
|
||||
"CustomName_Class",
|
||||
"[Log(Name = \"CustomLogger\")]",
|
||||
"public partial class MyService");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证自定义字段名会反映到生成快照。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Snapshot_CustomFieldName_Class()
|
||||
public Task Snapshot_CustomFieldName_Class()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
|
||||
namespace GFramework.Core.SourceGenerators.Abstractions.Logging
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class LogAttribute : Attribute
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FieldName { get; set; }
|
||||
public string AccessModifier { get; set; }
|
||||
public bool IsStatic { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Logging
|
||||
{
|
||||
public interface ILogger
|
||||
{
|
||||
void Info(string message);
|
||||
void Error(string message);
|
||||
void Warn(string message);
|
||||
void Debug(string message);
|
||||
void Trace(string message);
|
||||
void Fatal(string message);
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Logging
|
||||
{
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
|
||||
public static class LoggerFactoryResolver
|
||||
{
|
||||
public static ILoggerProvider Provider { get; set; }
|
||||
|
||||
public static ILoggerProvider CreateLogger(string name)
|
||||
{
|
||||
return Provider ?? new MockLoggerProvider();
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILoggerProvider
|
||||
{
|
||||
ILogger CreateLogger(string name);
|
||||
}
|
||||
|
||||
internal class MockLoggerProvider : ILoggerProvider
|
||||
{
|
||||
public ILogger CreateLogger(string name)
|
||||
{
|
||||
return new MockLogger(name);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MockLogger : ILogger
|
||||
{
|
||||
private readonly string _name;
|
||||
|
||||
public MockLogger(string name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public void Info(string message) { }
|
||||
public void Error(string message) { }
|
||||
public void Warn(string message) { }
|
||||
public void Debug(string message) { }
|
||||
public void Trace(string message) { }
|
||||
public void Fatal(string message) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Logging;
|
||||
|
||||
[Log(FieldName = "MyLogger")]
|
||||
public partial class MyService
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
||||
source,
|
||||
GetSnapshotFolder("CustomFieldName_Class"));
|
||||
return RunScenarioAsync(
|
||||
"CustomFieldName_Class",
|
||||
"[Log(FieldName = \"MyLogger\")]",
|
||||
"public partial class MyService");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证实例字段模式会反映到生成快照。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Snapshot_InstanceField_Class()
|
||||
public Task Snapshot_InstanceField_Class()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
|
||||
namespace GFramework.Core.SourceGenerators.Abstractions.Logging
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class LogAttribute : Attribute
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FieldName { get; set; }
|
||||
public string AccessModifier { get; set; }
|
||||
public bool IsStatic { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Logging
|
||||
{
|
||||
public interface ILogger
|
||||
{
|
||||
void Info(string message);
|
||||
void Error(string message);
|
||||
void Warn(string message);
|
||||
void Debug(string message);
|
||||
void Trace(string message);
|
||||
void Fatal(string message);
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Logging
|
||||
{
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
|
||||
public static class LoggerFactoryResolver
|
||||
{
|
||||
public static ILoggerProvider Provider { get; set; }
|
||||
|
||||
public static ILoggerProvider CreateLogger(string name)
|
||||
{
|
||||
return Provider ?? new MockLoggerProvider();
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILoggerProvider
|
||||
{
|
||||
ILogger CreateLogger(string name);
|
||||
}
|
||||
|
||||
internal class MockLoggerProvider : ILoggerProvider
|
||||
{
|
||||
public ILogger CreateLogger(string name)
|
||||
{
|
||||
return new MockLogger(name);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MockLogger : ILogger
|
||||
{
|
||||
private readonly string _name;
|
||||
|
||||
public MockLogger(string name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public void Info(string message) { }
|
||||
public void Error(string message) { }
|
||||
public void Warn(string message) { }
|
||||
public void Debug(string message) { }
|
||||
public void Trace(string message) { }
|
||||
public void Fatal(string message) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Logging;
|
||||
|
||||
[Log(IsStatic = false)]
|
||||
public partial class MyService
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
||||
source,
|
||||
GetSnapshotFolder("InstanceField_Class"));
|
||||
return RunScenarioAsync(
|
||||
"InstanceField_Class",
|
||||
"[Log(IsStatic = false)]",
|
||||
"public partial class MyService");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证公共字段可见性会反映到生成快照。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Snapshot_PublicField_Class()
|
||||
public Task Snapshot_PublicField_Class()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
|
||||
namespace GFramework.Core.SourceGenerators.Abstractions.Logging
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class LogAttribute : Attribute
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FieldName { get; set; }
|
||||
public string AccessModifier { get; set; }
|
||||
public bool IsStatic { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Logging
|
||||
{
|
||||
public interface ILogger
|
||||
{
|
||||
void Info(string message);
|
||||
void Error(string message);
|
||||
void Warn(string message);
|
||||
void Debug(string message);
|
||||
void Trace(string message);
|
||||
void Fatal(string message);
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Logging
|
||||
{
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
|
||||
public static class LoggerFactoryResolver
|
||||
{
|
||||
public static ILoggerProvider Provider { get; set; }
|
||||
|
||||
public static ILoggerProvider CreateLogger(string name)
|
||||
{
|
||||
return Provider ?? new MockLoggerProvider();
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILoggerProvider
|
||||
{
|
||||
ILogger CreateLogger(string name);
|
||||
}
|
||||
|
||||
internal class MockLoggerProvider : ILoggerProvider
|
||||
{
|
||||
public ILogger CreateLogger(string name)
|
||||
{
|
||||
return new MockLogger(name);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MockLogger : ILogger
|
||||
{
|
||||
private readonly string _name;
|
||||
|
||||
public MockLogger(string name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public void Info(string message) { }
|
||||
public void Error(string message) { }
|
||||
public void Warn(string message) { }
|
||||
public void Debug(string message) { }
|
||||
public void Trace(string message) { }
|
||||
public void Fatal(string message) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Logging;
|
||||
|
||||
[Log(AccessModifier = "public")]
|
||||
public partial class MyService
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
||||
source,
|
||||
GetSnapshotFolder("PublicField_Class"));
|
||||
return RunScenarioAsync(
|
||||
"PublicField_Class",
|
||||
"[Log(AccessModifier = \"public\")]",
|
||||
"public partial class MyService");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证泛型类声明的日志字段快照。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Snapshot_GenericClass()
|
||||
public Task Snapshot_GenericClass()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
return RunScenarioAsync(
|
||||
"GenericClass",
|
||||
"[Log]",
|
||||
"public partial class MyService<T>");
|
||||
}
|
||||
|
||||
namespace GFramework.Core.SourceGenerators.Abstractions.Logging
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class LogAttribute : Attribute
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FieldName { get; set; }
|
||||
public string AccessModifier { get; set; }
|
||||
public bool IsStatic { get; set; } = true;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 为给定场景组装最小测试源并执行快照校验。
|
||||
/// </summary>
|
||||
/// <param name="scenarioName">快照场景名称。</param>
|
||||
/// <param name="logAttributeLine">目标类型上的 <c>[Log(...)]</c> 声明。</param>
|
||||
/// <param name="classDeclaration">目标 partial 类型声明。</param>
|
||||
/// <returns>表示快照测试完成的异步任务。</returns>
|
||||
private static Task RunScenarioAsync(string scenarioName, string logAttributeLine, string classDeclaration)
|
||||
{
|
||||
return GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
||||
CreateSource(logAttributeLine, classDeclaration),
|
||||
GetSnapshotFolder(scenarioName));
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Logging
|
||||
{
|
||||
public interface ILogger
|
||||
{
|
||||
void Info(string message);
|
||||
void Error(string message);
|
||||
void Warn(string message);
|
||||
void Debug(string message);
|
||||
void Trace(string message);
|
||||
void Fatal(string message);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 生成日志源生成器测试所需的最小宿主源代码。
|
||||
/// </summary>
|
||||
/// <param name="logAttributeLine">目标类型上的 <c>[Log(...)]</c> 声明。</param>
|
||||
/// <param name="classDeclaration">目标 partial 类型声明。</param>
|
||||
/// <returns>可直接送入快照测试的完整源码字符串。</returns>
|
||||
private static string CreateSource(string logAttributeLine, string classDeclaration)
|
||||
{
|
||||
return string.Join(
|
||||
$"{Environment.NewLine}{Environment.NewLine}",
|
||||
CreateLoggingAttributeSource(),
|
||||
CreateLoggingContractsSource(),
|
||||
CreateLoggingRuntimeSource(),
|
||||
CreateTestAppSource(logAttributeLine, classDeclaration));
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Logging
|
||||
{
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
/// <summary>
|
||||
/// 生成日志测试使用的 attribute 定义源码。
|
||||
/// </summary>
|
||||
/// <returns>包含 <c>LogAttribute</c> 的源码片段。</returns>
|
||||
private static string CreateLoggingAttributeSource()
|
||||
{
|
||||
return """
|
||||
using System;
|
||||
|
||||
public static class LoggerFactoryResolver
|
||||
{
|
||||
public static ILoggerProvider Provider { get; set; }
|
||||
namespace GFramework.Core.SourceGenerators.Abstractions.Logging
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class LogAttribute : Attribute
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FieldName { get; set; }
|
||||
public string AccessModifier { get; set; }
|
||||
public bool IsStatic { get; set; } = true;
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
public static ILoggerProvider CreateLogger(string name)
|
||||
{
|
||||
return Provider ?? new MockLoggerProvider();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 生成日志抽象契约源码,供测试编译图引用。
|
||||
/// </summary>
|
||||
/// <returns>包含 <c>ILogger</c> 的源码片段。</returns>
|
||||
private static string CreateLoggingContractsSource()
|
||||
{
|
||||
return """
|
||||
namespace GFramework.Core.Abstractions.Logging
|
||||
{
|
||||
public interface ILogger
|
||||
{
|
||||
void Info(string message);
|
||||
void Error(string message);
|
||||
void Warn(string message);
|
||||
void Debug(string message);
|
||||
void Trace(string message);
|
||||
void Fatal(string message);
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
public interface ILoggerProvider
|
||||
{
|
||||
ILogger CreateLogger(string name);
|
||||
}
|
||||
/// <summary>
|
||||
/// 生成最小运行时宿主源码,供生成器解析 logger provider 依赖。
|
||||
/// </summary>
|
||||
/// <returns>包含 provider 与 mock logger 的源码片段。</returns>
|
||||
private static string CreateLoggingRuntimeSource()
|
||||
{
|
||||
return """
|
||||
namespace GFramework.Core.Logging
|
||||
{
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
|
||||
internal class MockLoggerProvider : ILoggerProvider
|
||||
{
|
||||
public ILogger CreateLogger(string name)
|
||||
{
|
||||
return new MockLogger(name);
|
||||
}
|
||||
}
|
||||
public static class LoggerFactoryResolver
|
||||
{
|
||||
public static ILoggerProvider Provider { get; set; }
|
||||
|
||||
internal class MockLogger : ILogger
|
||||
{
|
||||
private readonly string _name;
|
||||
public static ILoggerProvider CreateLogger(string name)
|
||||
{
|
||||
return Provider ?? new MockLoggerProvider();
|
||||
}
|
||||
}
|
||||
|
||||
public MockLogger(string name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
public interface ILoggerProvider
|
||||
{
|
||||
ILogger CreateLogger(string name);
|
||||
}
|
||||
|
||||
public void Info(string message) { }
|
||||
public void Error(string message) { }
|
||||
public void Warn(string message) { }
|
||||
public void Debug(string message) { }
|
||||
public void Trace(string message) { }
|
||||
public void Fatal(string message) { }
|
||||
}
|
||||
}
|
||||
internal class MockLoggerProvider : ILoggerProvider
|
||||
{
|
||||
public ILogger CreateLogger(string name)
|
||||
{
|
||||
return new MockLogger(name);
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Logging;
|
||||
internal class MockLogger : ILogger
|
||||
{
|
||||
private readonly string _name;
|
||||
|
||||
[Log]
|
||||
public partial class MyService<T>
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
public MockLogger(string name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
||||
source,
|
||||
GetSnapshotFolder("GenericClass"));
|
||||
public void Info(string message) { }
|
||||
public void Error(string message) { }
|
||||
public void Warn(string message) { }
|
||||
public void Debug(string message) { }
|
||||
public void Trace(string message) { }
|
||||
public void Fatal(string message) { }
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成实际承载 <c>[Log]</c> 声明的测试类型源码。
|
||||
/// </summary>
|
||||
/// <param name="logAttributeLine">目标类型上的 <c>[Log(...)]</c> 声明。</param>
|
||||
/// <param name="classDeclaration">目标 partial 类型声明。</param>
|
||||
/// <returns>测试应用命名空间下的目标类型源码片段。</returns>
|
||||
private static string CreateTestAppSource(string logAttributeLine, string classDeclaration)
|
||||
{
|
||||
return $$"""
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Logging;
|
||||
|
||||
{{logAttributeLine}}
|
||||
{{classDeclaration}}
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -7,8 +7,8 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-028`
|
||||
- 当前阶段:`Phase 28`
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-034`
|
||||
- 当前阶段:`Phase 34`
|
||||
- 当前焦点:
|
||||
- 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次
|
||||
- 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock
|
||||
@ -38,6 +38,9 @@
|
||||
- 已完成当前分支与 `main` 的 `CqrsHandlerRegistryGenerator.cs` 文件级冲突收口:确认 `main` 侧新增的是
|
||||
`OrderedRegistrationKind` / `RuntimeTypeReferenceSpec` 的 XML 文档,现已按当前 partial 拆分结构迁移到
|
||||
`CqrsHandlerRegistryGenerator.Models.cs`,不回退已完成的生成器拆分
|
||||
- 已完成 `SchemaConfigGenerator.cs` 剩余 `MA0051` 收口:将 `dependentRequired` / `allOf` / conditional schema 校验
|
||||
拆成更小的验证阶段,并将 `GenerateTableClass`、`GenerateBindingsClass`、`AppendGeneratedConfigCatalogType`
|
||||
拆成稳定的代码发射 helper,保持生成输出与快照一致
|
||||
- 已更新 `AGENTS.md`:变更模块必须运行对应 `dotnet build -c Release`,并处理或显式报告模块构建 warning,
|
||||
不再默认留给长期 warning 清理分支
|
||||
- `CoroutineScheduler` 的 tag/group 字典已显式使用 `StringComparer.Ordinal`,保持既有区分大小写语义
|
||||
@ -46,12 +49,40 @@
|
||||
- 当前 `GFramework.Core` `net8.0` warnings-only 基线已降到 `0` 条
|
||||
- 当前 `GFramework.Core.SourceGenerators` warnings-only 基线已降到 `0` 条
|
||||
- 当前 `GFramework.Cqrs.SourceGenerators` warnings-only 基线已降到 `0` 条
|
||||
- 当前 `GFramework.Game.SourceGenerators` warnings-only 基线已从 `46` 条降到 `9` 条,剩余均为
|
||||
`SchemaConfigGenerator.cs` 的 `MA0051`
|
||||
- 当前 `GFramework.Game.SourceGenerators` warnings-only 基线已从 `46` 条降到 `0` 条
|
||||
- 已完成 `GFramework.SourceGenerators.Tests` 低风险 `MA0004` / `MA0048` 收口:测试辅助器改为直接返回 `Task`,
|
||||
文件 I/O 显式补齐 `ConfigureAwait(false)`,`AnalyzerTestDriver` 文件名与类型名重新对齐
|
||||
- 当前 `GFramework.SourceGenerators.Tests` warnings-only 基线已从 `61` 条降到 `49` 条,剩余 warning 均为 `MA0051`
|
||||
- 已完成 `GFramework.SourceGenerators.Tests/Logging/LoggerGeneratorSnapshotTests.cs` 的 `MA0051` 收口:同构 snapshot
|
||||
场景已收敛为模板化 helper,保留原有快照目录与生成器输入语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release build 基线已从 `49` 条降到 `43` 条;`LoggerGeneratorSnapshotTests.cs`
|
||||
已不再出现在 `MA0051` 列表中
|
||||
- 已完成 `GFramework.SourceGenerators.Tests/Architectures/AutoRegisterModuleGeneratorTests.cs` 的 `MA0051` 收口:
|
||||
将内联测试源码与期望快照抽到类级常量、补齐测试类 XML 文档,并将仅作转发的异步测试改为直接返回 `Task`
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release build 基线已从 `43` 条降到 `40` 条;
|
||||
`AutoRegisterModuleGeneratorTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- 已完成 `GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorSnapshotTests.cs` 的 `MA0051` 收口:
|
||||
将 monster 场景的运行时契约与 schema 输入提取为类级常量,并把生成结果与快照目录解析拆成小 helper,保持
|
||||
生成文件名、快照目录和断言语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release build 基线已从 `40` 条降到 `39` 条;
|
||||
`SchemaConfigGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- 已完成当前 PR #273 review follow-up 首轮核对:确认本地仍成立的问题集中在
|
||||
`SchemaConfigGenerator` helper XML 文档、`GeneratorSnapshotTest` 的 `StringComparison.Ordinal` /
|
||||
snapshot 路径空值防御、`AutoRegisterModuleGeneratorTests` 的 XML 文档位置,以及
|
||||
`SchemaConfigGeneratorSnapshotTests` 的 monster 快照覆盖缺口
|
||||
- 已将 monster 快照场景扩展到 `dependentRequired`、`dependentSchemas`、`allOf` 与 object-focused
|
||||
`if/then/else`,以便把新增 schema 约束文档纳入 snapshot 验证
|
||||
- 已完成本轮定向验证:
|
||||
`GFramework.Game.SourceGenerators` Release build 通过;
|
||||
`GFramework.SourceGenerators.Tests` 在 `-m:1 --no-restore` 下 Release build 通过;
|
||||
`SchemaConfigGeneratorSnapshotTests` 与 `AutoRegisterModuleGeneratorTests` 定向测试共 `4` 项全部通过
|
||||
- 当前验证仍受环境/基线约束:
|
||||
`GFramework.SourceGenerators.Tests` Release build 保留既有 `MA0051` warning 基线;
|
||||
NuGet vulnerability audit 在离线环境下产生 `NU1900`
|
||||
- `GFramework.Godot` 的 `Timing.cs` 已同步适配新事件签名,但当前 worktree 的 Godot restore 资产仍受 Windows fallback package folder 干扰,独立 build 需在修复资产后补跑
|
||||
- 后续继续按 warning 类型和数量批处理,而不是回退到按单文件切片推进
|
||||
- 下一轮默认继续拆分 `GFramework.Game.SourceGenerators` 的 `MA0051` 热点,或评估跨 target 的 `MA0158`
|
||||
锁替换风险
|
||||
- 下一轮默认重新抓取 PR #273 最新 review 线程,并确认本轮 snapshot 更新后是否还存在剩余 open thread 或
|
||||
`dotnet-format` 细项
|
||||
- 单次 `boot` 的工作树改动上限控制在约 `100` 个文件以内,避免 recovery context 与 review 面同时失控
|
||||
- 若任务边界互不冲突,允许使用不同模型的 subagent 并行处理不同 warning 类型或不同目录,但必须遵守显式 ownership
|
||||
|
||||
@ -84,8 +115,15 @@
|
||||
inherited-collision 快照测试
|
||||
- 已完成当前分支与 `main` 的 `CqrsHandlerRegistryGenerator.cs` 冲突化解:保留当前 partial 结构,并把
|
||||
`main` 侧新增的模型文档合并到 `CqrsHandlerRegistryGenerator.Models.cs`
|
||||
- 已完成 `GFramework.Game.SourceGenerators` 中 `SchemaConfigGenerator` 的第一批 `MA0051` 收口;warnings-only 基线剩余 `9` 条
|
||||
`MA0051`
|
||||
- 已完成 `GFramework.Game.SourceGenerators` 中 `SchemaConfigGenerator` 的剩余 `MA0051` 收口;warnings-only 基线已降到 `0` 条
|
||||
- 已完成 `GFramework.SourceGenerators.Tests` 的首轮低风险 warning 清理;当前项目已清空 `MA0004` / `MA0048`,剩余 warning
|
||||
全部收敛为 `MA0051`
|
||||
- 已完成 `LoggerGeneratorSnapshotTests` 的单文件 `MA0051` 收口;当前 `GFramework.SourceGenerators.Tests` Release build 基线已降到
|
||||
`43` 条,并通过 focused snapshot tests 保持行为不变
|
||||
- 已完成 `AutoRegisterModuleGeneratorTests` 的单文件 `MA0051` 收口;当前 `GFramework.SourceGenerators.Tests` Release build 基线已降到
|
||||
`40` 条,并通过 focused generator tests 保持输出契约不变
|
||||
- 已完成 `SchemaConfigGeneratorSnapshotTests` 的单文件 `MA0051` 收口;当前 `GFramework.SourceGenerators.Tests`
|
||||
Release build 基线已降到 `39` 条,并通过 focused snapshot test 保持生成输出契约不变
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
@ -126,6 +164,10 @@
|
||||
通过 schema 类型比较 helper 与显式 `StringComparison.Ordinal` 清空当前项目的 `MA0006`
|
||||
- `RP-020` 继续拆分 `SchemaConfigGenerator.cs` 的 `MA0051` 热点,将当前项目 warnings-only 基线从 `19` 条降到 `9` 条,
|
||||
并用 focused schema generator tests 验证 50 个用例通过
|
||||
- `RP-032` 已完成 `AutoRegisterModuleGeneratorTests` 的 3 个 `MA0051` 收口:通过提取类级常量承载测试源码与快照,保持
|
||||
生成文件名、断言路径与源生成输出不变;`GFramework.SourceGenerators.Tests` warnings-only 基线由 `43` 降至 `40`
|
||||
- `RP-033` 已完成 `SchemaConfigGeneratorSnapshotTests` 的 `MA0051` 收口:monster schema 运行时契约与 schema 输入已提取为
|
||||
类级常量,生成结果映射与快照目录解析已拆为小 helper;`GFramework.SourceGenerators.Tests` warnings-only 基线由 `40` 降至 `39`
|
||||
- `RP-021` 使用 `$gframework-pr-review` 复核当前分支 PR #269 后,修复仍在本地成立的 4 个项:将
|
||||
`CqrsHandlerRegistryGenerator` 拆分为职责清晰的 partial 文件、为 `ContextAwareGenerator` 生成字段增加稳定前缀并补上
|
||||
`SetContextProvider` 的运行时 null 校验、为 `Option<T>` 补齐 `<remarks>`,并新增字段重名场景的生成器快照测试
|
||||
@ -141,6 +183,13 @@
|
||||
- `RP-025` 继续复核 PR #269 剩余 outside-diff / nitpick 信号后,确认本地仍成立的是 `SchemaConfigGenerator`
|
||||
的归一化字段名冲突与 `Cqrs` 对 `dynamic` 的直接类型引用;已分别补上诊断、运行时类型归一化与回归测试,
|
||||
并把“变更模块必须运行对应 build 且处理 warning”的治理规则写回 `AGENTS.md`
|
||||
- `RP-029` 已完成 `SchemaConfigGenerator` 剩余 `MA0051` 收口:`GFramework.Game.SourceGenerators` 独立 Release
|
||||
warnings-only build 已清零,并通过 `SchemaConfigGenerator` focused generator tests 锁定生成输出未回退
|
||||
- `RP-030` 已完成 `GFramework.SourceGenerators.Tests` 低风险 `MA0004` / `MA0048` 收口:`AnalyzerTestDriver` 文件名已与
|
||||
类型名一致,测试辅助器与 schema snapshot 断言路径已改为直接返回 `Task` 或显式使用 `ConfigureAwait(false)`;
|
||||
当前测试项目 warnings-only 基线从 `61` 条降到 `49` 条,剩余均为 `MA0051`
|
||||
- `RP-031` 已完成 `LoggerGeneratorSnapshotTests` 的 `MA0051` 收口:重复场景源码改为模板化 helper 生成,
|
||||
当前 `GFramework.SourceGenerators.Tests` Release build 基线从 `49` 条降到 `43` 条,并通过 focused test 验证 6 个用例全部通过
|
||||
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
|
||||
|
||||
## 当前风险
|
||||
@ -153,13 +202,12 @@
|
||||
- 缓解措施:继续以唯一源位置和 warning 家族为主要决策依据,而不是只看原始 warning 总数
|
||||
- net10 专属 warning 风险:`MA0158` 建议使用 `System.Threading.Lock`,但项目多 target 时需要确认兼容边界
|
||||
- 缓解措施:下一轮先按 target framework 与 API 可用性评估,不直接批量替换共享源码中的 `object` lock
|
||||
- source generator warning 外溢风险:运行 `GFramework.SourceGenerators.Tests` 会构建相邻 generator/test 项目并显示既有
|
||||
`GFramework.Game.SourceGenerators` 与测试项目 warning
|
||||
- 缓解措施:继续以被修改 generator 项目的独立 warnings-only build 作为主验收,并用 focused generator test 验证行为
|
||||
- source generator test warning 治理风险:`GFramework.SourceGenerators.Tests` 当前仍有既有 `MA0051` / `MA0004` / `MA0048`
|
||||
warning,本轮 focused test 已通过,但测试项目整包 warning 尚未进入本轮写集
|
||||
- 缓解措施:本轮已在 failed-test follow-up 的定向 `dotnet test` 中再次确认这些 warning 仍为既有基线;后续若继续修改该测试项目,
|
||||
应按新增 `AGENTS.md` 规则先明确 warning 收口范围,再决定是否进入专门清理切片
|
||||
- source generator warning 外溢风险:运行 `GFramework.SourceGenerators.Tests` 会构建相邻 generator/test 项目,并在输出中混入
|
||||
测试项目自身的结构性 warning 基线
|
||||
- 缓解措施:继续以被修改项目的独立 warnings-only build 作为主验收,并用 focused generator test 验证行为
|
||||
- source generator test warning 治理风险:`GFramework.SourceGenerators.Tests` 当前仍有 `43` 条既有 `MA0051` warning;
|
||||
一旦继续进入该写集,就必须把测试项目 warning 一并纳入本轮完成条件
|
||||
- 缓解措施:已先清空低风险 `MA0004` / `MA0048`,后续继续保持“单 warning family、单测试域”的节奏推进 `MA0051`
|
||||
- ContextAware 基类命名隐藏风险:若生成器只看当前类型声明成员,派生规则会重新占用基类已声明的
|
||||
`_gFrameworkContextAware*` 字段名,导致生成成员隐藏继承状态并让快照无法锁定后缀分配行为
|
||||
- 缓解措施:本轮已改为遍历完整 base-type 链收集保留名,并用 inherited collision 快照用例锁定该行为
|
||||
@ -311,13 +359,43 @@
|
||||
- 说明:测试项目构建仍会显示既有 `GFramework.SourceGenerators.Tests` analyzer warning;不属于本轮写集
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~CollectionExtensionsTests|FullyQualifiedName~LoggingConfigurationTests" -m:1 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:`27 Passed`,`0 Failed`
|
||||
- `RP-029` 的验证结果:
|
||||
- `dotnet restore GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;刷新 Linux 侧 restore 资产以移除 Windows fallback package folder 干扰
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;focused test 所属测试项目已同步刷新 Linux 侧 restore 资产
|
||||
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`;`SchemaConfigGenerator.cs` 剩余 `MA0051` 已清零
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~SchemaConfigGenerator -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`54 Passed`,`0 Failed`
|
||||
- 说明:测试项目构建仍显示既有 `GFramework.SourceGenerators.Tests` `MA0048` / `MA0051` / `MA0004` warning;不属于本轮
|
||||
`GFramework.Game.SourceGenerators` 写集
|
||||
- `RP-030` 的验证结果:
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;刷新 Linux 侧 restore 资产以移除 Windows fallback package folder 干扰
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`49 Warning(s)`,`0 Error(s)`;当前项目已不再出现 `MA0004` / `MA0048`,剩余 warning 全部为 `MA0051`
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~GeneratorSnapshotTestSecurityTests|FullyQualifiedName~SchemaConfigGeneratorSnapshotTests|FullyQualifiedName~SchemaConfigGeneratorEnumTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`6 Passed`,`0 Failed`
|
||||
- `RP-031` 的验证结果:
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;刷新 Linux 侧 restore 资产以支持后续串行 build/test 验证
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:Summary`
|
||||
- 结果:`43 Warning(s)`,`0 Error(s)`;`LoggerGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~LoggerGeneratorSnapshotTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`6 Passed`,`0 Failed`
|
||||
- `RP-033` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`39 Warning(s)`,`0 Error(s)`;`SchemaConfigGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~SchemaConfigGeneratorSnapshotTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`1 Passed`,`0 Failed`
|
||||
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 若要继续该主题,先读 active tracking,再按需展开历史归档中的 warning 热点与验证记录
|
||||
2. 下一轮优先继续拆分 `GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs` 的剩余 `MA0051`;建议先从
|
||||
`GenerateBindingsClass`、`AppendGeneratedConfigCatalogType` 或对象/条件 schema target 验证方法切入
|
||||
2. 下一轮优先继续 `GFramework.SourceGenerators.Tests` 的 `MA0051` 收口,先在 `GeneratorSnapshotTest`、
|
||||
`ContextRegistrationAnalyzerTests` 或 `ContextGetGeneratorTests` 中选择一个单写集推进,不再把已清零的 `MA0004` / `MA0048` 混回写集
|
||||
3. 若改回推进 `MA0158`,先设计 `net8.0` / `net9.0` / `net10.0` 多 target 条件编译方案,不直接批量替换共享源码中的
|
||||
`object` lock
|
||||
4. 若后续继续改动 `GFramework.Godot`,先修复该项目的 Linux 侧 restore 资产,再补跑独立 build
|
||||
|
||||
@ -1,5 +1,172 @@
|
||||
# Analyzer Warning Reduction 追踪
|
||||
|
||||
## 2026-04-23 — RP-033
|
||||
|
||||
### 阶段:`SchemaConfigGeneratorSnapshotTests.cs` `MA0051` 收口(RP-033)
|
||||
|
||||
- 启动复核:
|
||||
- 按 `gframework-boot` 流程恢复当前 worktree,读取 `AGENTS.md`、`.ai/environment/tools.ai.yaml`、
|
||||
`ai-plan/public/README.md` 与 active topic 跟踪文件,确认当前分支 `fix/analyzer-warning-reduction-batch`
|
||||
仍映射到 `analyzer-warning-reduction`
|
||||
- 用
|
||||
`dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
复核当前 `MA0051` 热点,确认 `SchemaConfigGeneratorSnapshotTests.cs` 仍保留 1 个超长方法,适合作为单文件低风险写集
|
||||
- 决策:
|
||||
- 保持 monster schema 场景的输入源码、schema 文本、生成文件名与快照目录不变,只收敛测试方法长度
|
||||
- 沿用前几轮 snapshot test 的收口策略:提取类级常量承载大段 fixture 输入,再用小 helper 封装生成结果映射与快照目录解析
|
||||
- 同一测试项目的 build/test 继续采用串行验证;并行执行会在 WSL worktree 上制造瞬时输出缺失,导致 `MSB3030` / `CS0006`
|
||||
- 实施调整:
|
||||
- 为 `SchemaConfigGeneratorSnapshotTests` 新增 `RuntimeContractsSource` 与 `MonsterSchema` 类级常量,保留既有 monster 场景内容
|
||||
- 把生成结果字典构造拆到 `GenerateSourcesForMonsterSchema()`,把快照目录解析拆到 `GetSchemaSnapshotFolder()`
|
||||
- 保持 `AssertAllSnapshotsAsync(...)`、快照文件名与断言流程不变,不改生成器逻辑和 snapshot 资产
|
||||
- 验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`39 Warning(s)`,`0 Error(s)`;`SchemaConfigGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~SchemaConfigGeneratorSnapshotTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`1 Passed`,`0 Failed`
|
||||
- 下一步建议:
|
||||
- 若继续压缩 `GFramework.SourceGenerators.Tests` 的 `MA0051`,优先处理只剩单个超长方法的 `GeneratorSnapshotTest` 或
|
||||
`ContextRegistrationAnalyzerTests`
|
||||
- 若希望继续按 warning 数量收敛,则回到 `ContextGetGeneratorTests.cs`,但需要接受更大的单文件写集
|
||||
|
||||
## 2026-04-23 — RP-032
|
||||
|
||||
### 阶段:`AutoRegisterModuleGeneratorTests.cs` `MA0051` 收口(RP-032)
|
||||
|
||||
- 启动复核:
|
||||
- 按 `gframework-boot` 流程恢复当前 worktree,读取 `AGENTS.md`、`.ai/environment/tools.ai.yaml`、
|
||||
`ai-plan/public/README.md` 与 active topic 跟踪文件,确认当前分支 `fix/analyzer-warning-reduction-batch`
|
||||
仍映射到 `analyzer-warning-reduction`
|
||||
- 先用
|
||||
`dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
复核当前 `MA0051` 热点,确认 `AutoRegisterModuleGeneratorTests.cs` 仍有 `3` 个超长方法,适合作为单文件低风险写集
|
||||
- 决策:
|
||||
- 保持 `AutoRegisterModuleGeneratorTests` 的测试输入、生成文件名、快照文本与断言结构不变,只收敛方法长度
|
||||
- 采用“提取类级常量承载大段测试源码与期望输出”的方式,避免引入新的共享 helper 或改变场景组装顺序
|
||||
- 验证阶段改为串行执行 build/test;避免和同项目并行运行时拿到不完整 `bin/Release` 输出
|
||||
- 实施调整:
|
||||
- 为 `AutoRegisterModuleGeneratorTests` 补齐测试类 XML 文档
|
||||
- 将 3 个长测试方法中的源码与期望快照提取为类级 `const string`,保留原有生成文件名与断言目标
|
||||
- 将仅转发 `GeneratorTest.RunAsync(...)` 的两个异步测试改为直接返回 `Task`
|
||||
- 验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`40 Warning(s)`,`0 Error(s)`;`AutoRegisterModuleGeneratorTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --disable-build-servers --filter FullyQualifiedName~AutoRegisterModuleGeneratorTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`3 Passed`,`0 Failed`
|
||||
- 下一步建议:
|
||||
- 若继续压缩 `GFramework.SourceGenerators.Tests` 的 `MA0051`,优先处理仅剩单个超长方法的
|
||||
`GFramework.SourceGenerators.Tests/Core/GeneratorSnapshotTest.cs`
|
||||
- 若希望单次继续多降几条 warning,则改选 `ContextGetGeneratorTests.cs`,但需要接受更大的单文件写集
|
||||
|
||||
## 2026-04-23 — RP-031
|
||||
|
||||
### 阶段:`LoggerGeneratorSnapshotTests.cs` `MA0051` 收口(RP-031)
|
||||
|
||||
- 启动复核:
|
||||
- 按 `gframework-boot` 流程恢复当前 worktree,读取 `AGENTS.md`、`.ai/environment/tools.ai.yaml`、
|
||||
`ai-plan/public/README.md` 与 active topic 跟踪文件,确认当前分支 `fix/analyzer-warning-reduction-batch`
|
||||
仍映射到 `analyzer-warning-reduction`
|
||||
- 结合 `RP-030` 的下一步建议与当前文件规模,优先选择 `GFramework.SourceGenerators.Tests/Logging/LoggerGeneratorSnapshotTests.cs`
|
||||
作为单文件、同构 snapshot 场景的低风险写集
|
||||
- 决策:
|
||||
- 保持 `LoggerGenerator` 现有快照资产、场景命名与输入语义不变,只压缩测试方法和样板源码构造的结构复杂度
|
||||
- 先把重复场景统一为模板化 helper,再根据 analyzer 结果继续拆分 helper,直到 `LoggerGeneratorSnapshotTests.cs`
|
||||
不再出现在 `MA0051` 输出中
|
||||
- 验证阶段避免并行运行同一测试项目的 build/test,防止 WSL worktree 上的 `bin/Release` 文件占用噪音污染结果
|
||||
- 实施调整:
|
||||
- 为 `LoggerGeneratorSnapshotTests` 补齐类与测试方法 XML 文档
|
||||
- 将 6 个 snapshot 场景改为统一调用 `RunScenarioAsync(...)`
|
||||
- 将原先重复内联的完整测试源码拆成 `CreateLoggingAttributeSource()`、
|
||||
`CreateLoggingContractsSource()`、`CreateLoggingRuntimeSource()` 与 `CreateTestAppSource(...)`
|
||||
- 验证结果:
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:Summary`
|
||||
- 结果:`43 Warning(s)`,`0 Error(s)`;`LoggerGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~LoggerGeneratorSnapshotTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`6 Passed`,`0 Failed`
|
||||
- 下一步建议:
|
||||
- 若继续 `GFramework.SourceGenerators.Tests` 的 `MA0051` 治理,优先选择 `AutoRegisterModuleGeneratorTests` 或
|
||||
`GeneratorSnapshotTest` 作为下一批单写集
|
||||
- 若需要先压缩 warning 数量而不是单文件难度,可转向 `ContextGetGeneratorTests`,但应先明确本轮允许的文件数上限
|
||||
|
||||
## 2026-04-23 — RP-030
|
||||
|
||||
### 阶段:`GFramework.SourceGenerators.Tests` 低风险 `MA0004` / `MA0048` 收口(RP-030)
|
||||
|
||||
- 启动复核:
|
||||
- 按 `gframework-boot` 流程恢复当前 worktree 后,读取 `AGENTS.md`、`.ai/environment/tools.ai.yaml`、
|
||||
`ai-plan/public/README.md` 与 active topic 跟踪文件,确认当前分支 `fix/analyzer-warning-reduction-batch`
|
||||
仍映射到 `analyzer-warning-reduction`
|
||||
- 先对 `GFramework.SourceGenerators.Tests` 执行
|
||||
`dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`,
|
||||
刷新 Linux 侧 restore 资产,规避 Windows fallback package folder 干扰
|
||||
- 用
|
||||
`dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
复核当前基线,确认该测试项目共有 `61` 条 warning,其中低风险切片集中在 `MA0004` 与单个 `MA0048`
|
||||
- 决策:
|
||||
- 不直接进入大型 snapshot/test 方法的 `MA0051`,先收口纯 test-infrastructure 层的 `MA0004` / `MA0048`
|
||||
- 对“只是转发异步调用”的 helper 直接返回 `Task`,只在真实文件 I/O 上显式补 `ConfigureAwait(false)`,避免无意义的
|
||||
`async/await` 包装
|
||||
- 将 `AnalyzerTestDriver<TAnalyzer>` 所在文件改名为与类型一致,单独清理 `MA0048`,不改类型名与调用方契约
|
||||
- 实施调整:
|
||||
- 将 `AnalyzerTestDriver.RunAsync(...)` 与 `GeneratorTest.RunAsync(...)` 改为直接返回下游 `Task`
|
||||
- 为 `GeneratorSnapshotTest`、`SchemaConfigGeneratorSnapshotTests` 与 `SchemaConfigGeneratorEnumTests` 中的异步文件读写
|
||||
显式补齐 `ConfigureAwait(false)`,并把仅作转发的测试方法改为直接返回 `Task`
|
||||
- 将 `GeneratorSnapshotTestSecurityTests` 的 `Assert.ThrowsAsync(...)` 改为直接返回目标 `Task`,移除无收益的
|
||||
`async` 包装
|
||||
- 将 `GFramework.SourceGenerators.Tests/Core/AnalyzerTest.cs` 重命名为
|
||||
`GFramework.SourceGenerators.Tests/Core/AnalyzerTestDriver.cs`
|
||||
- 验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`49 Warning(s)`,`0 Error(s)`;当前项目已不再出现 `MA0004` / `MA0048`,剩余 warning 全部为 `MA0051`
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~GeneratorSnapshotTestSecurityTests|FullyQualifiedName~SchemaConfigGeneratorSnapshotTests|FullyQualifiedName~SchemaConfigGeneratorEnumTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`6 Passed`,`0 Failed`
|
||||
- 下一步建议:
|
||||
- 若继续 analyzer warning reduction,继续把 `GFramework.SourceGenerators.Tests` 作为独立写集,只处理 `MA0051`
|
||||
- 下一轮优先选择单一测试域的同构长方法,例如 `LoggerGeneratorSnapshotTests`、`AutoRegisterModuleGeneratorTests`
|
||||
或共享 helper `GeneratorSnapshotTest`
|
||||
|
||||
## 2026-04-23 — RP-029
|
||||
|
||||
### 阶段:`SchemaConfigGenerator.cs` 剩余 `MA0051` 收口(RP-029)
|
||||
|
||||
- 启动复核:
|
||||
- 按 `gframework-boot` 流程恢复当前 worktree 后,先读取 `AGENTS.md`、`.ai/environment/tools.ai.yaml`、`ai-plan/public/README.md`
|
||||
与 active topic 跟踪文件,确认当前分支 `fix/analyzer-warning-reduction-batch` 仍映射到
|
||||
`analyzer-warning-reduction`
|
||||
- 用历史基线命令重新执行 `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`,
|
||||
复现 `SchemaConfigGenerator.cs` 剩余 `9` 条 `MA0051`
|
||||
- 决策:
|
||||
- 继续沿用“低风险结构拆分、不改诊断 ID、不改生成顺序、不改快照输出”的收口策略
|
||||
- 先把 schema 元数据校验方法拆成更小验证阶段,再把 `GenerateTableClass`、`GenerateBindingsClass` 与
|
||||
`AppendGeneratedConfigCatalogType` 的代码发射流程分段,避免直接改动生成文本内容
|
||||
- focused test 仍以 `SchemaConfigGenerator` 相关用例为主;`GFramework.SourceGenerators.Tests` 里既有测试项目 warning
|
||||
不纳入本轮写集
|
||||
- 实施调整:
|
||||
- 为 `dependentRequired`、`dependentSchemas`、`allOf`、conditional schema 等对象级校验补上细粒度 helper,
|
||||
把 declared-properties 获取、分支校验、target 校验拆成独立阶段
|
||||
- 为生成代码头部、表包装、bindings metadata/references、catalog metadata 发射补充结构化 helper,
|
||||
将长方法按“头部 / 元数据 / 行为方法”拆分
|
||||
- 修正 `References` 代码发射 helper 的闭合范围,确保重构后的 `MonsterConfigBindings.g.cs` 与现有快照保持一致
|
||||
- 在构建阶段遇到 Linux `dotnet` 命中 Windows fallback package folder 时,先对
|
||||
`GFramework.Game.SourceGenerators` 与 `GFramework.SourceGenerators.Tests` 执行
|
||||
`dotnet restore -p:RestoreFallbackFolders=""`,再继续 `--no-restore` 验证
|
||||
- 验证结果:
|
||||
- `dotnet restore GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过
|
||||
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~SchemaConfigGenerator -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`54 Passed`,`0 Failed`
|
||||
- 说明:测试项目构建仍打印既有 `MA0048` / `MA0051` / `MA0004` warning;这些 warning 属于 `GFramework.SourceGenerators.Tests`
|
||||
基线,不属于本轮 `GFramework.Game.SourceGenerators` 写集
|
||||
- 下一步建议:
|
||||
- 若继续 analyzer warning reduction,可评估是否为 `GFramework.SourceGenerators.Tests` 单独开新的 warning 清理切片
|
||||
- 若改回推进运行时主线,则按 `RP-017` 记录的策略先设计 `MA0158` 的多 target 兼容方案,再决定是否动共享 `object` lock
|
||||
|
||||
## 2026-04-23 — RP-028
|
||||
|
||||
### 阶段:`CqrsHandlerRegistryGenerator.cs` 文件级冲突化解(RP-028)
|
||||
@ -797,3 +964,20 @@
|
||||
|
||||
1. 若继续 analyzer warning reduction,优先回到 `GFramework.Core` 剩余 `MA0051` 热点,并继续保持“单 warning family、单切入点”的节奏
|
||||
2. 后续所有 WSL 下的 .NET 定向验证命令继续显式附带 `-p:RestoreFallbackFolders=`,避免把环境问题误判成代码回归
|
||||
# 2026-04-23
|
||||
|
||||
- RP-034 / PR #273 review follow-up:
|
||||
- 使用 `gframework-pr-review` 抓取当前分支 PR #273 的 latest-head review threads、MegaLinter 和测试摘要。
|
||||
- 本地复核后确认仍成立的项集中在 `SchemaConfigGenerator` helper XML 文档、
|
||||
`GeneratorSnapshotTest` 的 `StringComparison.Ordinal` 与 snapshot 路径空值防御、
|
||||
`AutoRegisterModuleGeneratorTests` 的 XML 文档位置,以及
|
||||
`SchemaConfigGeneratorSnapshotTests` 的 monster snapshot 覆盖缺口。
|
||||
- 已扩展 monster schema 场景以覆盖 `dependentRequired`、`dependentSchemas`、`allOf` 与 object-focused
|
||||
`if/then/else`,并同步更新 `MonsterConfig.g.txt` 的约束快照。
|
||||
- `DOTNET_CLI_HOME=/tmp/dotnet-home dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -p:RestoreFallbackFolders=`
|
||||
通过;离线 NuGet vulnerability audit 产生 `NU1900`。
|
||||
- `DOTNET_CLI_HOME=/tmp/dotnet-home dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1`
|
||||
通过;测试项目保留既有 `MA0051` warning 基线。
|
||||
- `DOTNET_CLI_HOME=/tmp/dotnet-home dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~SchemaConfigGeneratorSnapshotTests|FullyQualifiedName~AutoRegisterModuleGeneratorTests" -m:1`
|
||||
通过,`4` 个用例全部通过;需要在沙箱外执行以绕过 `vstest` 本地 socket 权限限制。
|
||||
- 下一步:提交本轮修复并在需要时重新抓取 PR review,确认 open threads 是否随新提交收敛。
|
||||
|
||||
@ -0,0 +1,77 @@
|
||||
# Documentation Full Coverage Governance Status History Through RP-016
|
||||
|
||||
以下内容从 active tracking 中迁出,用于保留 `DOCUMENTATION-FULL-COVERAGE-GOV-RP-001` 到
|
||||
`DOCUMENTATION-FULL-COVERAGE-GOV-RP-016` 的阶段性状态、治理结论与恢复背景。默认 `boot` 只需要读取 active
|
||||
tracking 中的最新摘要;若需要追溯已完成波次的详细背景,再回到本归档文件。
|
||||
|
||||
## 阶段里程碑
|
||||
|
||||
### `RP-001` 到 `RP-007`
|
||||
|
||||
- 建立长期 active topic `documentation-full-coverage-governance`,并在 `ai-plan/public/README.md` 中将当前分支
|
||||
`docs/sdk-update-documentation` 映射到该 topic。
|
||||
- 明确消费属性边界:
|
||||
- `GFramework.Ecs.Arch.Abstractions` 是可打包直接消费模块,需要 README 与文档入口。
|
||||
- `GFramework.Core.SourceGenerators.Abstractions`、
|
||||
`GFramework.Godot.SourceGenerators.Abstractions`、`GFramework.SourceGenerators.Common`
|
||||
都按 `IsPackable=false` 的内部支撑模块处理。
|
||||
- 收口 `Core` / `Core.Abstractions` README、landing page 与类型族级 XML inventory。
|
||||
- 收口 `Ecs.Arch` / `Ecs.Arch.Abstractions` README、landing page、抽象页与 `UseArch(...)` 早于
|
||||
`Initialize()` 的采用约束。
|
||||
- 收口 `Cqrs` family 的 runtime / abstractions / source generator 入口,并为缺失的内部类型补齐 XML 注释。
|
||||
- 收口 `Game` family 的 README、landing page、抽象页与类型族级 XML inventory。
|
||||
- 将 `Game` family 从“文档存在但入口失真”推进到“runtime / abstractions / source generator 都有当前可审计入口”。
|
||||
|
||||
### `RP-008` 到 `RP-013`
|
||||
|
||||
- 消化 PR #271 的文档 follow-up,修正 `gframework-pr-review` 脚本与 skill 中的 WSL Git 策略,使其与
|
||||
`AGENTS.md` 保持一致。
|
||||
- 将 `Godot` family 的核心恢复摘要迁回 active topic,避免默认恢复路径继续依赖 archive 细节。
|
||||
- 重写 `GFramework.Godot/README.md` 与 `GFramework.Godot.SourceGenerators/README.md`,补齐当前包关系、
|
||||
子系统地图、最小接入路径与站内文档入口。
|
||||
- 更新根 `README.md`、`docs/zh-CN/source-generators/index.md`、`docs/zh-CN/api-reference/index.md`,把
|
||||
`GFramework.Godot.SourceGenerators` 的 owner 与能力边界收敛到当前源码口径。
|
||||
- 完成 `Godot` docs surface 的 validation-only 巡检,确认 landing、tutorial、API reference 与 README
|
||||
当前保持一致叙述,没有出现新的入口漂移。
|
||||
|
||||
### `RP-014` 到 `RP-016`
|
||||
|
||||
- 重写 `docs/zh-CN/godot/storage.md`,补齐 frontmatter、`GodotFileStorage` 的路径语义、repository 分工与
|
||||
`GodotYamlConfigLoader` 分流边界。
|
||||
- 重写 `docs/zh-CN/godot/setting.md`,改回当前 `ISettingsModel` / `RegisterApplicator(...)` 口径,并补齐
|
||||
`LocalizationMap` fallback 与当前 consumer wiring。
|
||||
- 对 `Godot` docs surface 再做一轮 validation-only 巡检,确认 `storage.md`、`setting.md`、landing、README、
|
||||
tutorial 与 API reference 没有新的回漂。
|
||||
- 重写 `docs/zh-CN/game/data.md`、`storage.md`、`serialization.md`、`setting.md`,把 `Game` persistence docs
|
||||
surface 收口到当前 repository / storage / serializer / settings 责任边界。
|
||||
- 将 `DataRepository`、`UnifiedSettingsDataRepository`、`SaveRepository<TSaveData>` 与 `FileStorage` /
|
||||
`ScopedStorage` / `SettingsModel<TRepository>` 的分工,统一回写到 README、源码与 `PersistenceTests` 已验证的采用路径。
|
||||
|
||||
## 已确认的长期事实
|
||||
|
||||
- 已归档的 `documentation-governance-and-refresh` 只作为历史证据保留,不再作为默认 `boot` 入口。
|
||||
- `Godot` family 当前核心页面集包括:
|
||||
- `docs/zh-CN/godot/index.md`
|
||||
- `docs/zh-CN/godot/architecture.md`
|
||||
- `docs/zh-CN/godot/scene.md`
|
||||
- `docs/zh-CN/godot/ui.md`
|
||||
- `docs/zh-CN/godot/storage.md`
|
||||
- `docs/zh-CN/godot/setting.md`
|
||||
- `docs/zh-CN/godot/signal.md`
|
||||
- `docs/zh-CN/godot/extensions.md`
|
||||
- `docs/zh-CN/godot/logging.md`
|
||||
- `docs/zh-CN/tutorials/godot-integration.md`
|
||||
- `Game` persistence docs surface 当前最值得优先复核的页面集包括:
|
||||
- `docs/zh-CN/game/data.md`
|
||||
- `docs/zh-CN/game/storage.md`
|
||||
- `docs/zh-CN/game/serialization.md`
|
||||
- `docs/zh-CN/game/setting.md`
|
||||
- `GFramework.Cqrs` 在当前 WSL / dotnet 环境下仍会读取失效的 fallback package folder,并在标准 build 中触发
|
||||
`MSB4276` / `MSB4018`;这是已知环境阻塞,不是本 topic 当前文档回归。
|
||||
- 当前 WSL 会话里 `git.exe` 可解析但不可执行,仓库默认 Git 策略应继续优先使用显式
|
||||
`--git-dir` / `--work-tree` 绑定。
|
||||
|
||||
## 关联归档
|
||||
|
||||
- 早期详细验证历史:`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/traces/documentation-full-coverage-governance-trace-history-through-rp-016.md`
|
||||
@ -0,0 +1,92 @@
|
||||
# Documentation Full Coverage Governance Trace History Through RP-016
|
||||
|
||||
以下内容从 active trace 中迁出,用于保留 `DOCUMENTATION-FULL-COVERAGE-GOV-RP-001` 到
|
||||
`DOCUMENTATION-FULL-COVERAGE-GOV-RP-016` 的阶段时间线、关键决策与主要验证结果。默认 `boot` 只需要读取
|
||||
active trace 中的最新恢复点;若需要追溯旧阶段的执行顺序,再回到本归档文件。
|
||||
|
||||
## 2026-04-22
|
||||
|
||||
### `RP-001`
|
||||
|
||||
- 新建 active topic `documentation-full-coverage-governance`。
|
||||
- 在 `ai-plan/public/README.md` 中将当前 worktree 绑定到该 topic。
|
||||
- 盘点可消费模块与内部支撑模块的边界,作为后续 README / docs 治理基线。
|
||||
|
||||
### `RP-002`
|
||||
|
||||
- 完成 `Core` / `Core.Abstractions` README、landing page 与类型族级 XML inventory 的第一轮收口。
|
||||
- 运行 `docs` 站点构建与局部文档校验,结果通过。
|
||||
|
||||
### `RP-003`
|
||||
|
||||
- 完成 `Ecs.Arch` / `Ecs.Arch.Abstractions` 文档刷新。
|
||||
- 确认 `UseArch(...)` 必须早于 `Initialize()` 的采用顺序,并将该约束写回文档。
|
||||
- 运行 `docs` 站点构建,结果通过。
|
||||
|
||||
### `RP-004`
|
||||
|
||||
- 完成 `Cqrs` family landing、generator topic 与 API 参考入口刷新。
|
||||
- 为 `GFramework.Cqrs` 与 `GFramework.Cqrs.SourceGenerators` 缺失的内部类型补齐 XML 注释。
|
||||
- `GFramework.Cqrs.SourceGenerators` Release build 通过。
|
||||
- `GFramework.Cqrs` Release build 仍受环境级 fallback package folder 问题阻塞,记录为已知非回归风险。
|
||||
|
||||
## 2026-04-23
|
||||
|
||||
### `RP-005`
|
||||
|
||||
- 完成 `Game` family README、landing page、抽象页与类型族级 XML inventory 刷新。
|
||||
- 文档校验与 `docs` 站点构建通过。
|
||||
|
||||
### `RP-006`
|
||||
|
||||
- 更新 `AGENTS.md` 的 WSL Git 回退顺序:
|
||||
- 优先显式 `--git-dir` / `--work-tree` 绑定。
|
||||
- `git.exe` 仅在当前会话可执行时作为 fallback。
|
||||
- `docs` 站点构建通过。
|
||||
|
||||
### `RP-007`
|
||||
|
||||
- 完成 `Game` family validation-only 巡检。
|
||||
- 确认 `config-system.md`、`scene.md`、`ui.md` 与 `source-generators/index.md` 当前没有新的采用漂移。
|
||||
|
||||
### `RP-008` 到 `RP-013`
|
||||
|
||||
- 消化 PR #271 的 review follow-up,修正 `gframework-pr-review` 脚本与 skill 中的 Git 策略。
|
||||
- 将 `Godot` family 的核心恢复摘要迁回 active topic。
|
||||
- 重写 `GFramework.Godot/README.md` 与 `GFramework.Godot.SourceGenerators/README.md`。
|
||||
- 更新根 `README.md`、`docs/zh-CN/source-generators/index.md` 与 `docs/zh-CN/api-reference/index.md` 的
|
||||
`Godot` 入口与 owner 描述。
|
||||
- 针对 `Godot` docs surface 执行 validation-only 巡检,确认当前 landing / topic / tutorial / API reference
|
||||
与 README 保持一致。
|
||||
|
||||
### `RP-014`
|
||||
|
||||
- 重写 `docs/zh-CN/godot/storage.md` 与 `docs/zh-CN/godot/setting.md`。
|
||||
- 运行 `scan_module_evidence.py Godot`、相关文档校验与 `docs` 站点构建,结果通过。
|
||||
|
||||
### `RP-015`
|
||||
|
||||
- 再次执行 `Godot` docs surface validation-only 巡检。
|
||||
- 确认 `storage.md`、`setting.md`、landing、README、tutorial 与 API reference 没有新的回漂。
|
||||
|
||||
### `RP-016`
|
||||
|
||||
- 重写 `docs/zh-CN/game/data.md`、`storage.md`、`serialization.md`、`setting.md`。
|
||||
- 运行 `scan_module_evidence.py Game`、相关文档校验与 `docs` 站点构建,结果通过。
|
||||
- 当前 `Game` persistence docs surface 已回到与源码、README 和 `PersistenceTests` 一致的责任边界叙述。
|
||||
|
||||
## 主要验证汇总
|
||||
|
||||
- `cd docs && bun run build`
|
||||
- 多轮执行通过;仅保留既有 VitePress 大 chunk warning,无构建失败。
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh ...`
|
||||
- 针对本 topic 涉及的 landing / topic / abstractions 页面多轮执行通过。
|
||||
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Godot`
|
||||
- 通过。
|
||||
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Game`
|
||||
- 通过。
|
||||
|
||||
## 归档关联
|
||||
|
||||
- 状态归档:`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-validation-history-through-rp-007.md`
|
||||
@ -3,7 +3,7 @@
|
||||
## 目标
|
||||
|
||||
建立一个长期 active topic,持续治理 `GFramework` 的 README、`docs/zh-CN`、站点导航、XML 文档和 API
|
||||
参考链路,避免历史上的阶段性刷新完成后再次回漂。
|
||||
参考链路,避免阶段性刷新完成后再次回漂。
|
||||
|
||||
- 用源码、测试、`*.csproj` 和必要的 `ai-libs/` 证据校正文档
|
||||
- 以模块族为单位闭环 README、landing page、专题页、教程入口和 API 参考链路
|
||||
@ -12,184 +12,97 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`DOCUMENTATION-FULL-COVERAGE-GOV-RP-016`
|
||||
- 恢复点编号:`DOCUMENTATION-FULL-COVERAGE-GOV-RP-023`
|
||||
- 当前阶段:`Phase 5 - Governance Maintenance`
|
||||
- 当前焦点:
|
||||
- 保持 `Game` family 的 persistence docs surface 与当前 `README`、源码、`PersistenceTests` 使用同一套 owner / adoption path 叙述
|
||||
- 将 `data.md`、`storage.md`、`serialization.md`、`setting.md` 视为 `Game` family 当前需要一起巡检的核心页面集,而不是分散的旧 API 手册页
|
||||
- 重点观察 `DataRepository` / `UnifiedSettingsDataRepository` / `SaveRepository<TSaveData>`、`FileStorage` / `ScopedStorage` 与 `SettingsModel<TRepository>` 的职责边界是否再次回漂
|
||||
- 在 `Game` runtime public API 或 README 再次变动前,优先做 targeted 巡检,不重复改写已稳定的 landing page
|
||||
- 保持 landing page / API 导航页中的仓库 README 入口可点击,避免读者在 docs 站点里遇到裸路径文本
|
||||
- 继续按 `origin/main` 分支 diff 阈值做小批量文档治理,优先处理低风险导航 / 渲染热点
|
||||
- 保持 `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/`
|
||||
|
||||
## 当前状态摘要
|
||||
|
||||
- 已归档的 `documentation-governance-and-refresh` 仅保留为历史证据,不再作为默认 `boot` 入口
|
||||
- `2026-04-23` 的 `Game` persistence docs wave 新增结论:
|
||||
- `docs/zh-CN/game/storage.md` 之前仍停留在旧版“通用存储 API 手册”写法,没有反映 `FileStorage` / `ScopedStorage` 与上层 repository 的分工,也没有强调当前同步 API 只是异步阻塞包装
|
||||
- `docs/zh-CN/game/data.md` 之前缺少 `DataRepository`、`UnifiedSettingsDataRepository` 与 `SaveRepository<TSaveData>` 三层分工,以及 `PersistenceTests` 已覆盖的备份 / 批量事件 / 存档迁移语义
|
||||
- `docs/zh-CN/game/serialization.md` 之前仍沿用“业务层手工 Serialize 再写回 storage”的旧示例,没有反映当前 `FileStorage` 已直接复用注入的 `ISerializer`
|
||||
- `docs/zh-CN/game/setting.md` 虽然已回到 `ISettingsModel` / `RegisterApplicator(...)` 口径,但缺少 frontmatter,且还没有和新的 `Game` persistence docs surface 使用同一套结构
|
||||
- `2026-04-23` 的 `Game` persistence docs wave 治理动作:
|
||||
- 重写 `docs/zh-CN/game/storage.md`,将其改为 `FileStorage` / `ScopedStorage` 的职责、路径语义、作用域复用与 repository 边界页
|
||||
- 重写 `docs/zh-CN/game/data.md`,补齐 `DataRepository`、`UnifiedSettingsDataRepository`、`SaveRepository<TSaveData>` 与 `DataRepositoryOptions` / `SaveConfiguration` 的当前契约
|
||||
- 重写 `docs/zh-CN/game/serialization.md`,收敛到 `JsonSerializer` 的配置生命周期、运行时类型序列化与和 storage / repository 的分工
|
||||
- 重写 `docs/zh-CN/game/setting.md`,使其与 `SettingsModel<TRepository>`、`SettingsSystem`、迁移缓存和统一设置仓库的当前实现保持一致
|
||||
- 本轮已消化的 PR #271 review follow-up:
|
||||
- 为 `.agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py` 补齐 WSL worktree 下的显式 Linux Git 绑定,避免 `git.exe` 在当前会话触发 `Exec format error`
|
||||
- 同步更新 `.agents/skills/gframework-pr-review/SKILL.md`,改为与 `AGENTS.md` 一致的 Git 策略,并把命令示例统一到 `.agents/...` 路径
|
||||
- 为 `docs/zh-CN/source-generators/cqrs-handler-registry-generator.md` 补充 marker 类型放置与命名约定说明
|
||||
- 从 `docs/zh-CN/abstractions/ecs-arch-abstractions.md` 删除误放的 source-generator 内部模块提醒,并微调 `docs/zh-CN/ecs/index.md` 的边界说明语序
|
||||
- 为 `ai-plan/public/archive/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md` 的归档验证补写结果态
|
||||
- 将 RP-001 至 RP-007 的详细验证历史迁入 `ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-validation-history-through-rp-007.md`
|
||||
- `2026-04-23` 再次执行 `$gframework-pr-review` 后,确认 PR `#271` 已关闭,latest reviewed commit `df91d3706ba9db71737e803ef2f40f4841ecbbf1` 仍显示 `2` 条 open thread,但两条都对应已在当前 HEAD 满足的 `ai-plan` 变更,属于 closed PR 上未自动收敛的陈旧线程信号
|
||||
- 本轮已确认的消费属性结论:
|
||||
- `GFramework.Ecs.Arch.Abstractions`:可打包直接消费模块,需要 README 和文档入口
|
||||
- `GFramework.Core.SourceGenerators.Abstractions`:`IsPackable=false`,按内部支撑模块处理
|
||||
- `GFramework.Godot.SourceGenerators.Abstractions`:`IsPackable=false`,按内部支撑模块处理
|
||||
- `GFramework.SourceGenerators.Common`:`IsPackable=false`,按内部支撑模块处理
|
||||
- 本轮已确认的 `Godot` family 恢复摘要:
|
||||
- `docs/zh-CN/godot/index.md`、`architecture.md`、`scene.md`、`ui.md`、`storage.md`、`setting.md`、`signal.md`、`extensions.md`、`logging.md` 与 `docs/zh-CN/tutorials/godot-integration.md` 是当前需要保留的核心页面集
|
||||
- `GFramework.Godot.SourceGenerators` 继续作为 `[GetNode]`、`[BindNodeSignal]`、`AutoLoads` 与 `InputActions` 的 owner;`GFramework.Godot.SourceGenerators.Abstractions` 仍按内部支撑模块处理
|
||||
- `Godot` Scene / UI 采用边界已经稳定:当前没有 `GodotSceneRouter` 或 `GodotUiRouter`;`GodotSceneFactory` 在 provider 缺失时会回退到 `SceneBehaviorFactory`,而 `GodotUiFactory` 仍要求 `IUiPageBehaviorProvider`
|
||||
- `2026-04-23` 的 validation-only 巡检新增结论:
|
||||
- 根 `README.md`、`docs/zh-CN/godot/index.md`、`docs/zh-CN/tutorials/godot-integration.md`、`docs/zh-CN/source-generators/index.md` 与 `docs/zh-CN/api-reference/index.md` 当前仍保持同一套 `Godot` owner / adoption path 叙述,没有发现新的入口漂移
|
||||
- `scan_module_evidence.py Godot` 显示 `docs/zh-CN/godot/storage.md` 与 `setting.md` 仍属于 `Godot` runtime docs surface,应并入 active topic 的最小恢复摘要,避免后续 `boot` 漏掉当前 landing page 的关键入口
|
||||
- `2026-04-23` 的子页巡检新增结论:
|
||||
- `docs/zh-CN/godot/storage.md` 之前仍停留在旧版 API 手册写法,缺少 frontmatter、`IStorage` / repository 边界和 `GodotYamlConfigLoader` 的分流说明
|
||||
- `docs/zh-CN/godot/setting.md` 之前仍把 `GodotAudioSettings` / `GodotGraphicsSettings` 描述成直接持有设置数据对象的旧构造方式,没有反映当前 `ISettingsModel` + `RegisterApplicator(...)` 接法
|
||||
- `2026-04-23` 的交叉链接巡检新增结论:
|
||||
- `GFramework.Godot/README.md` 仍停留在旧版简略描述,缺少当前包关系、子系统地图、最小接入路径与 `docs/zh-CN` 入口
|
||||
- `GFramework.Godot.SourceGenerators/README.md` 虽有示例,但没有覆盖 `AutoScene`、`AutoUiPage`、`AutoRegisterExportedCollections` 等当前生成器分组,也没有把运行时 / 生成器边界说清
|
||||
- `docs/zh-CN/api-reference/index.md` 的 `Godot` 行此前只把生成器入口指向泛化的 `source-generators/index.md`,不利于从 API 参考直接落到 `Godot` 专题页
|
||||
- `2026-04-23` 的后续入口巡检新增结论:
|
||||
- 根 `README.md` 的模块地图仍把 `GFramework.Godot.SourceGenerators` 写成“Godot 场景专用源码生成器”,范围过窄,不符合当前 `project.godot` 元数据、节点注入、信号绑定、Scene / UI 包装与导出集合注册的真实职责
|
||||
- `docs/zh-CN/source-generators/index.md` 的选包说明此前只提到 `AutoLoad / Input Action` 与节点 / 信号样板,没有把 Scene / UI 包装与导出集合注册辅助纳入同一入口
|
||||
- 本轮已完成的治理动作:
|
||||
- 新建 `GFramework.Ecs.Arch.Abstractions/README.md`
|
||||
- 在根 `README.md` 中补齐 `GFramework.Ecs.Arch.Abstractions` 入口,并声明内部支撑模块 owner
|
||||
- 为抽象接口栏目补齐 `Ecs.Arch.Abstractions` 页面与 sidebar 入口
|
||||
- 将 `docs/zh-CN/api-reference/index.md` 重写为模块到 XML / README / 教程的阅读链路入口
|
||||
- 为 `GFramework.Core/README.md` 补齐 `Services`、`Configuration`、`Environment`、`Pool`、`Rule`、`Time` 等当前目录映射
|
||||
- 为 `GFramework.Core.Abstractions/README.md` 补齐契约族地图与 XML 阅读重点
|
||||
- 将 `docs/zh-CN/abstractions/core-abstractions.md` 从过时的接口摘录页重写为契约边界 / 包关系 / 最小接入路径页面
|
||||
- 为 `docs/zh-CN/core/index.md` 补齐 frontmatter、能力域导航和 API / XML 阅读入口
|
||||
- 为 `GFramework.Core/README.md`、`GFramework.Core.Abstractions/README.md` 补齐类型族级 XML 覆盖基线入口
|
||||
- 为 `docs/zh-CN/core/index.md`、`docs/zh-CN/abstractions/core-abstractions.md` 增加“类型族 -> XML 覆盖状态 -> 代表类型”的 inventory
|
||||
- 基于顶层目录轻量盘点确认:`Core` / `Core.Abstractions` 当前公开 / 内部类型声明都已带 XML 注释,成员级审计留待后续波次
|
||||
- 重写 `docs/zh-CN/ecs/index.md`,收敛当前 ECS family 的包边界、采用顺序和 XML inventory
|
||||
- 重写 `docs/zh-CN/ecs/arch.md`,明确 `UseArch(...)` 需早于 `Initialize()` 的真实接入时机
|
||||
- 刷新 `GFramework.Ecs.Arch/README.md`,使运行时 README 与源码 / 测试一致
|
||||
- 为 `GFramework.Ecs.Arch.Abstractions/README.md` 与 `docs/zh-CN/abstractions/ecs-arch-abstractions.md` 补齐类型族级 XML inventory
|
||||
- 重写 `docs/zh-CN/core/cqrs.md`,将其收敛为 `Cqrs` family landing,并补齐运行时 / 契约层 / 生成器的 XML inventory
|
||||
- 新建 `docs/zh-CN/source-generators/cqrs-handler-registry-generator.md`,为 `Cqrs.SourceGenerators` 补齐站内专题入口
|
||||
- 更新 `docs/zh-CN/source-generators/index.md`、`docs/zh-CN/api-reference/index.md` 与 VitePress sidebar,使 `Cqrs` family 的 generator 入口可导航
|
||||
- 为 `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` 与 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 中缺失的内部类型补齐 XML 注释,使本轮轻量 inventory 达到声明级闭环
|
||||
- 为 `GFramework.Game/README.md`、`GFramework.Game.Abstractions/README.md`、`GFramework.Game.SourceGenerators/README.md` 补齐 `Game` family 的类型族级 XML inventory
|
||||
- 为 `docs/zh-CN/game/index.md` 补齐 frontmatter,并增加 `Game` / `Game.Abstractions` / `Game.SourceGenerators` 的 XML 覆盖基线入口
|
||||
- 将 `docs/zh-CN/abstractions/game-abstractions.md` 从失真的旧接口摘录页重写为契约边界 / 包关系 / 最小接入路径页面
|
||||
- 基于顶层目录轻量盘点确认:`GFramework.Game` 为 `56/56`、`GFramework.Game.Abstractions` 为 `80/80`、`GFramework.Game.SourceGenerators` 为 `2/2`,当前公开 / 内部类型声明都已带 XML 注释
|
||||
- 更新 `AGENTS.md` 的 WSL Git 策略,将显式 `--git-dir` / `--work-tree` 绑定提升为高于 `git.exe` 的默认优先级
|
||||
- 记录当前环境偏差:本会话 `git.exe` 可解析但执行会触发 `Exec format error`,而 plain Linux `git` 会命中 worktree 路径翻译错误,需要显式仓库绑定
|
||||
- 完成 `Game` family 巡检,确认 `docs/zh-CN/game/config-system.md`、`scene.md`、`ui.md` 与 `docs/zh-CN/source-generators/index.md` 的核心采用说明、包关系与交叉引用仍与当前源码 / README 一致,没有发现需要立刻修正的回漂
|
||||
- 将 `Godot` family 的最小恢复摘要迁回 active topic,保留核心页面集、生成器 owner、Scene / UI 真实边界与归档指针,避免长期治理默认恢复路径继续依赖 archive 明细
|
||||
- 重写 `GFramework.Godot/README.md`,补齐包定位、相邻包关系、子系统地图、最小接入路径与站内文档入口,并明确它不拥有生成器职责
|
||||
- 重写 `GFramework.Godot.SourceGenerators/README.md`,按 `project.godot` 元数据、节点注入 / 信号绑定、行为包装与批量注册四组能力重建入口
|
||||
- 更新 `docs/zh-CN/api-reference/index.md` 的 `Godot` 模块映射,使 API 参考能直接落到 `Godot` 专用生成器专题页,而不是仅回到总览页
|
||||
- 修正根 `README.md` 中 `GFramework.Godot.SourceGenerators` 的模块描述,使其与当前生成器职责边界一致
|
||||
- 扩充 `docs/zh-CN/source-generators/index.md` 的 Godot 选包说明,把 Scene / UI 包装与导出集合注册辅助纳入入口摘要
|
||||
- 重写 `docs/zh-CN/godot/storage.md`,补齐 frontmatter、`GodotFileStorage` 的路径语义、repository 分工与 `GodotYamlConfigLoader` 分流边界
|
||||
- 重写 `docs/zh-CN/godot/setting.md`,改回当前 `ISettingsModel` / `RegisterApplicator(...)` 口径,并补上 `LocalizationMap` fallback 与 `CoreGrid` 注册示例
|
||||
- `2026-04-23` 再次通过 `$gframework-boot` 恢复当前 worktree 后,按 `Godot` docs surface 执行 validation-only 巡检,确认 `GFramework.Godot/README.md`、`docs/zh-CN/godot/index.md`、`storage.md`、`setting.md`、`docs/zh-CN/source-generators/index.md`、`docs/zh-CN/api-reference/index.md` 与 `docs/zh-CN/tutorials/godot-integration.md` 仍保持一致的 owner / adoption path 叙述,没有出现自 `RP-014` 之后的新漂移
|
||||
|
||||
## Inventory(第一版)
|
||||
|
||||
| 模块族 | 当前状态 | 当前证据 | 下一动作 |
|
||||
| --- | --- | --- | --- |
|
||||
| `Core` / `Core.Abstractions` | `README / landing / 类型族级 XML inventory 已收口,成员级审计待补齐` | 根 README、模块 README、`docs/zh-CN/core/**`、`docs/zh-CN/abstractions/core-abstractions.md` 已对齐当前目录与类型族基线 | 进入巡检;如有新 API 变更,再追加成员级 XML 审计 |
|
||||
| `Cqrs` / `Cqrs.Abstractions` / `Cqrs.SourceGenerators` | `README / landing / generator topic / 类型族级 XML inventory 已收口,成员级审计待补齐` | `GFramework.Cqrs/README.md`、`GFramework.Cqrs.Abstractions/README.md`、`GFramework.Cqrs.SourceGenerators/README.md`、`docs/zh-CN/core/cqrs.md`、`docs/zh-CN/source-generators/cqrs-handler-registry-generator.md`、`docs/zh-CN/api-reference/index.md` 已对齐当前源码与测试 | 转入巡检;下一波切到 `Game` family 的 XML / 教程链路审计 |
|
||||
| `Game` / `Game.Abstractions` / `Game.SourceGenerators` | `README / landing / abstractions / persistence topic pages / 类型族级 XML inventory 已收口,成员级审计待补齐` | `GFramework.Game/README.md`、`GFramework.Game.Abstractions/README.md`、`GFramework.Game.SourceGenerators/README.md`、`docs/zh-CN/game/index.md`、`docs/zh-CN/abstractions/game-abstractions.md`、`docs/zh-CN/game/data.md`、`storage.md`、`serialization.md`、`setting.md` 已对齐当前源码、README 与 `PersistenceTests` | 转入巡检;优先观察后续分支是否再次把 `Game` persistence docs 写回旧 API 手册口径 |
|
||||
| `Godot` / `Godot.SourceGenerators` | `README / 生成器 README / landing / topic / tutorial / API reference 入口已重新对齐,成员级 XML 审计不在本轮范围` | `GFramework.Godot/README.md`、`GFramework.Godot.SourceGenerators/README.md`、`docs/zh-CN/godot/index.md`、`architecture.md`、`scene.md`、`ui.md`、`storage.md`、`setting.md`、`signal.md`、`extensions.md`、`logging.md`、`docs/zh-CN/tutorials/godot-integration.md` | 进入巡检周期,优先观察后续分支是否再次把 README / API 入口写回过时边界 |
|
||||
| `Ecs.Arch` / `Ecs.Arch.Abstractions` | `README / landing / abstractions / 类型族级 XML inventory 已收口,成员级审计待补齐` | `GFramework.Ecs.Arch/README.md`、`GFramework.Ecs.Arch.Abstractions/README.md`、`docs/zh-CN/ecs/**`、`docs/zh-CN/abstractions/ecs-arch-abstractions.md` 已对齐当前源码与测试 | 转入巡检;后续仅在运行时公共 API 变动时补成员级 XML 细审 |
|
||||
| `SourceGenerators.Common` 与 `*.SourceGenerators.Abstractions` | `已判定为内部支撑` | `*.csproj` 明确 `IsPackable=false` | 由所属模块 README 与生成器栏目说明 owner,不建独立采用页 |
|
||||
|
||||
## 缺口分级
|
||||
|
||||
- `P0`
|
||||
- 错误采用路径、错误包关系、错误 API / 生命周期语义
|
||||
- 站点导航死链、空 landing page、明显错误的模块 owner
|
||||
- `P1`
|
||||
- 直接消费模块缺 README 或缺对应 docs 入口
|
||||
- README / docs 示例与源码实现不一致
|
||||
- 教程仍引用已经过时的默认接线方式
|
||||
- `P2`
|
||||
- 结构重复、交叉链接不足、API 参考链路过薄
|
||||
- 站内页面存在事实正确但组织方式不利于定位的内容
|
||||
- `Core`、`Ecs.Arch`、`Cqrs`、`Game`、`Godot` 五个模块族当前都已有 README / landing / topic / API 参考层级的已验证入口。
|
||||
- `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<ISettingsDataRepository>`。
|
||||
- 结合当前 PR 已改动的 `docs/zh-CN/godot/storage.md` 做同类巡检后,确认 `SaveRepository<TSaveData>`
|
||||
也会在 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<T>`、`Result<T>`、`Nullable<T>` 已统一改为真实
|
||||
泛型写法,避免在 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 元数据,不是本地
|
||||
文件缺陷。
|
||||
|
||||
## 当前风险
|
||||
|
||||
- 当前 `Core` / `Core.Abstractions` 只完成了类型族级 XML 基线,不等于成员级契约全审计
|
||||
- 缓解措施:后续只在共享抽象或高风险生命周期接口发生改动时补成员级细审,不在本轮扩张范围
|
||||
- `Godot` family 的详细治理历史仍保留在 archive,active topic 只回填了最小恢复摘要
|
||||
- 缓解措施:active topic 记录核心页面集、owner、运行时边界与 archive 指针;只有在需要阶段级历史时再读取归档材料
|
||||
- 新功能分支若修改 README / docs / 公共 API 却不挂文档 topic,仍可能回漂
|
||||
- 缓解措施:将本 topic 作为长期 active topic 保留,并在后续巡检中记录回漂来源
|
||||
- VitePress 页面不能直接链接到 `docs/` 目录之外的模块 `README.md`
|
||||
- 缓解措施:站内页面用模块路径文本或站内 API 入口表达,仓库级 README 仍保留仓库文件链接
|
||||
- `GFramework.Cqrs` 在当前 WSL / dotnet 环境下,本地 build 仍会读取失效的 fallback package folder 配置,导致无法完成该项目的标准编译验证
|
||||
- 缓解措施:本轮先以 `GFramework.Cqrs.SourceGenerators` 编译通过和 docs site build 通过作为有效验证,并在后续环境治理或构建脚本清理时单独处理 `RestoreFallbackFolders` / 资产文件问题
|
||||
- 当前 WSL 会话中 `git.exe` 虽然可解析,但不能执行
|
||||
- 缓解措施:把显式 `--git-dir` / `--work-tree` 绑定上升为仓库默认回退策略,并仅把 `git.exe` 保留为可执行时的次级 fallback
|
||||
- 当前 `Core` / `Core.Abstractions`、`Ecs.Arch`、`Cqrs`、`Game` 的 XML 治理仍以“类型声明级基线”为主,不等于成员级契约全审计。
|
||||
- `GFramework.Cqrs` 在当前 WSL / dotnet 环境下仍会读取失效的 fallback package folder,并在标准 build 中触发
|
||||
`MSB4276` / `MSB4018`;这是已知环境阻塞,不属于本轮文档回归。
|
||||
- 当前 WSL 会话里 `git.exe` 可解析但不能执行,应继续使用显式 `--git-dir` / `--work-tree` 绑定作为默认 Git 策略。
|
||||
|
||||
## 验证说明
|
||||
## 归档指针
|
||||
|
||||
- 详细验证历史已归档到 `ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-validation-history-through-rp-007.md`
|
||||
- 最新 PR review 结论:
|
||||
- `2026-04-23` `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/gframework-current-pr-review.json`
|
||||
- 结果:通过;PR `#271` 已关闭,latest reviewed commit 为 `df91d3706ba9db71737e803ef2f40f4841ecbbf1`,当前 `2` 条 open thread 都是已被本地文件满足的陈旧信号,不再构成本轮阻塞
|
||||
- 最新构建结论:
|
||||
- `2026-04-23` `cd docs && bun run build`
|
||||
- 结果:通过;在重写 `docs/zh-CN/game/data.md`、`storage.md`、`serialization.md`、`setting.md` 后再次验证通过,仅保留既有 VitePress 大 chunk warning,无构建失败
|
||||
- 最新 `Game` persistence docs wave 结论:
|
||||
- `2026-04-23` 基于 `GFramework.Game` 源码、`GFramework.Game/README.md`、`JsonSerializerTests`、`SettingsModelTests` 与 `PersistenceTests`
|
||||
- 结果:通过;`docs/zh-CN/game/data.md`、`storage.md`、`serialization.md`、`setting.md` 当前已回到同一套 `Game` runtime 持久化采用路径,不再沿用旧版 API 手册叙述
|
||||
- 最新稳定性巡检结论:
|
||||
- `2026-04-23` 重新执行 `Godot` docs surface 巡检
|
||||
- 结果:通过;根入口链路保持稳定,并额外发现 `docs/zh-CN/godot/storage.md`、`setting.md` 两页存在旧版叙述残留,当前已按源码口径完成最小修复
|
||||
- 最新 validation-only 巡检结论:
|
||||
- `2026-04-23` 通过 `$gframework-boot` 恢复后重新执行 `Godot` docs surface 巡检
|
||||
- 结果:通过;`README / landing / topic / tutorial / API reference` 当前仍保持同一套 `Godot` owner / adoption path 叙述,本轮无需新增文档补丁
|
||||
- 最新恢复治理结论:
|
||||
- `2026-04-23` 重新读取 `ai-plan/public/archive/documentation-governance-and-refresh/**`
|
||||
- 结果:通过;确认 `Godot` family 适合把最小恢复摘要迁回 active topic,但不需要把整段归档历史重新放回默认 `boot` 路径
|
||||
- 已完成的针对性校验:
|
||||
- `2026-04-23` `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Godot`:通过
|
||||
- `2026-04-23` `python3 -B -c "from pathlib import Path; compile(Path('.agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py').read_text(encoding='utf-8'), '.agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py', 'exec')"`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators/cqrs-handler-registry-generator.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/ecs/index.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/abstractions/ecs-arch-abstractions.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-links.sh GFramework.Godot/README.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-code-blocks.sh GFramework.Godot/README.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-links.sh GFramework.Godot.SourceGenerators/README.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-code-blocks.sh GFramework.Godot.SourceGenerators/README.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/api-reference/index.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-links.sh README.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-code-blocks.sh README.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators/index.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/index.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/tutorials/godot-integration.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/storage.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/setting.md`:通过
|
||||
- `2026-04-23` `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Godot`:通过(boot 后复核)
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/storage.md`:通过(boot 后复核)
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/setting.md`:通过(boot 后复核)
|
||||
- `2026-04-23` `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Game`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/game/data.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/game/storage.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/game/serialization.md`:通过
|
||||
- `2026-04-23` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/game/setting.md`:通过
|
||||
- `2026-04-23` `cd docs && bun run build`:通过(本轮 `Game` persistence docs wave 复核;仅保留既有 VitePress 大 chunk warning)
|
||||
- 详细验证历史(`RP-001` 到 `RP-007`):
|
||||
`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-001` 到 `RP-016`):
|
||||
`ai-plan/public/documentation-full-coverage-governance/archive/traces/documentation-full-coverage-governance-trace-history-through-rp-016.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 '`[^`]*<[^`]*`|`[^`]*>[^`]*`' 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 '`[^`]*<[^`]*`|`[^`]*>[^`]*`' 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.Game` 的 persistence runtime、README 或公共 API,优先复核 `docs/zh-CN/game/data.md`、`storage.md`、`serialization.md`、`setting.md` 与 landing page 是否仍保持同一套职责边界
|
||||
2. 当 `Godot` / `Game` family 再次出现交叉入口漂移时,沿用当前 README -> landing -> topic page -> API reference 的最小修复顺序
|
||||
3. 仅在需要阶段级细节时再读取 `documentation-governance-and-refresh` archive,而不是把 archive 重新当作默认 `boot` 入口
|
||||
1. 若继续执行文档治理批处理,优先改做标题锚点、站内链接和少量非导航型裸路径引用的逐页复核,而不是继续按统一模板机械替换。
|
||||
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` 与相关专题页是否仍保持一致。
|
||||
|
||||
@ -1,642 +1,57 @@
|
||||
# Documentation Full Coverage Governance Trace
|
||||
|
||||
## 2026-04-22
|
||||
## 2026-04-23
|
||||
|
||||
### 当前恢复点:RP-001
|
||||
### 当前恢复点:RP-023
|
||||
|
||||
- 按长期治理计划新建 active topic `documentation-full-coverage-governance`
|
||||
- 在 `ai-plan/public/README.md` 中将当前分支 `docs/sdk-update-documentation` 映射到该 topic
|
||||
- 复核已知缺口模块的 `*.csproj` 后确认:
|
||||
- `GFramework.Ecs.Arch.Abstractions` 是可打包消费模块,需要独立 README
|
||||
- `GFramework.Core.SourceGenerators.Abstractions`、`GFramework.Godot.SourceGenerators.Abstractions`、
|
||||
`GFramework.SourceGenerators.Common` 都是 `IsPackable=false` 的内部支撑模块
|
||||
- 基于该结论,本轮没有为内部支撑模块新增独立 README,而是在根 README 与 abstractions / API 入口中明确其 owner
|
||||
- 按当前使用反馈继续执行 `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)
|
||||
|
||||
- 新主题的完成条件采用长期治理口径:`P0` 清零、无 README 缺失、无导航死链,并完成连续两轮稳定巡检
|
||||
- 本轮先做治理基础设施与 inventory,不把整个长期计划伪装成单轮完成
|
||||
- `api-reference` 页面改为“模块 -> README / docs / XML / tutorial”的阅读链路入口,避免继续维护失真的伪签名列表
|
||||
- `Ecs.Arch` family 被列为高优先 backlog:抽象层入口已补齐,但 runtime docs 仍需按源码重写
|
||||
- `Core` / `Core.Abstractions` 波次先收口 README、landing page 和 abstractions 页的目录映射,再补显式 XML 覆盖 inventory
|
||||
- VitePress 站内页面不直接链接仓库根模块 `README.md`;站内仅保留可构建的 docs 链接,模块 README 以文本路径或仓库 README 承接
|
||||
- 继续使用 `origin/main` 作为 `$gframework-batch-boot 75` 的固定基线,并以“分支累计 diff 文件数”作为主状态指标。
|
||||
- 对文档治理类批次,优先选择“导航可达性 / 渲染一致性”这类不改变产品语义的低风险切片。
|
||||
- 在 docs 页面里出现仓库内 README 路径时,优先使用可点击的相对链接,而不是裸路径代码片段。
|
||||
- 当 docs 页需要跳转到 `docs/` 外部的 README 时,使用 GitHub `main` 分支 blob 外链,而不是跨出 `docs/` 根目录的相对路径。
|
||||
- 第二批继续沿用同一外链策略,避免在同一 docs surface 中混用“裸路径 / 相对死链 / GitHub 外链”三套入口风格。
|
||||
|
||||
### 当前恢复点:RP-002
|
||||
### 当前验证(RP-023)
|
||||
|
||||
- 完成 `Core` / `Core.Abstractions` 的类型族级 XML inventory:
|
||||
- `GFramework.Core/README.md`
|
||||
- `GFramework.Core.Abstractions/README.md`
|
||||
- `docs/zh-CN/core/index.md`
|
||||
- `docs/zh-CN/abstractions/core-abstractions.md`
|
||||
- 通过顶层目录轻量盘点确认:
|
||||
- `GFramework.Core` 当前各目录族的公开 / 内部类型声明都已带 XML 注释
|
||||
- `GFramework.Core.Abstractions` 当前各契约目录族的公开 / 内部类型声明都已带 XML 注释
|
||||
- 这轮 inventory 明确限定为“类型声明级基线”,不把结果表述成成员级 XML 合规审计
|
||||
|
||||
### 当前决策(RP-002)
|
||||
|
||||
- XML inventory 同时落在模块 README 和站内 landing page:
|
||||
- README 提供仓库侧入口,方便从包目录直接恢复上下文
|
||||
- docs landing 提供更细的类型族 / 代表类型 / 阅读重点表格,方便站内导航
|
||||
- `Core` 波次在补齐基线后转入巡检,不继续在本轮展开成员级 ``<param>`` / ``<returns>`` 审计
|
||||
- 下一恢复点切换到 `Ecs` 波次,优先处理仍明显失真的 runtime docs
|
||||
|
||||
### 当前验证
|
||||
|
||||
- 文档校验:
|
||||
- `validate-all.sh docs/zh-CN/abstractions/index.md`:通过
|
||||
- `validate-all.sh docs/zh-CN/abstractions/ecs-arch-abstractions.md`:通过
|
||||
- `validate-all.sh docs/zh-CN/api-reference/index.md`:通过
|
||||
- `validate-all.sh docs/zh-CN/core/index.md`:通过
|
||||
- `validate-all.sh docs/zh-CN/abstractions/core-abstractions.md`:通过
|
||||
- 导航热点巡检:
|
||||
- `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` 专题页,均已修复。
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过
|
||||
- `DOTNET_CLI_HOME=/tmp/dotnet-home dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release -p:RestoreFallbackFolders=`:通过,`0 Warning(s) / 0 Error(s)`
|
||||
- `dotnet build GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj -c Release -p:RestoreFallbackFolders=`:通过,`0 Warning(s) / 0 Error(s)`
|
||||
- `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-002)
|
||||
### 归档摘要(RP-022)
|
||||
|
||||
- 文档校验:
|
||||
- `validate-all.sh docs/zh-CN/core/index.md`:通过
|
||||
- `validate-all.sh docs/zh-CN/abstractions/core-abstractions.md`:通过
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过;仅保留 VitePress 大 chunk warning,无构建失败
|
||||
- 为 `.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/` 站点构建。
|
||||
|
||||
### 当前恢复点:RP-003
|
||||
### 归档指针
|
||||
|
||||
- 完成 `Ecs.Arch` 波次的运行时文档刷新:
|
||||
- `docs/zh-CN/ecs/index.md`
|
||||
- `docs/zh-CN/ecs/arch.md`
|
||||
- `GFramework.Ecs.Arch/README.md`
|
||||
- 为 `Ecs.Arch.Abstractions` 补齐与运行时页同粒度的 XML inventory:
|
||||
- `GFramework.Ecs.Arch.Abstractions/README.md`
|
||||
- `docs/zh-CN/abstractions/ecs-arch-abstractions.md`
|
||||
- 明确记录一个关键采用事实:
|
||||
- `UseArch(...)` 必须早于 `Initialize()` 调用
|
||||
- 该结论以 `ArchExtensions` 的模块注册方式和 `ExplicitRegistrationTests` 为证据
|
||||
- 将 `Ecs.Arch` family 从“入口存在但失真”推进到“README / landing / abstractions / XML inventory 已对齐源码与测试”
|
||||
|
||||
### 当前决策(RP-003)
|
||||
|
||||
- `Ecs` 波次继续采用与 `Core` 相同的治理粒度:
|
||||
- 模块 README 承担仓库入口
|
||||
- `docs/zh-CN/ecs/index.md` 承担模块族 landing
|
||||
- `docs/zh-CN/ecs/arch.md` 承担运行时默认实现专题页
|
||||
- `docs/zh-CN/abstractions/ecs-arch-abstractions.md` 承担契约边界专题页
|
||||
- `EnableStatistics` 当前仅保留在公开配置面上;文档不再把它写成已验证的运行时行为
|
||||
- 下一恢复点切换到 `Cqrs` 波次,优先解决入口分散和 API / XML 阅读链路不统一的问题
|
||||
|
||||
### 当前验证(RP-003)
|
||||
|
||||
- 文档校验:
|
||||
- `validate-all.sh docs/zh-CN/ecs/index.md`:通过
|
||||
- `validate-all.sh docs/zh-CN/ecs/arch.md`:通过
|
||||
- `validate-all.sh docs/zh-CN/abstractions/ecs-arch-abstractions.md`:通过
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过;仅保留 VitePress 大 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/traces/documentation-full-coverage-governance-trace-history-through-rp-016.md`
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 在 `Cqrs` 波次核对模块 README、`docs/zh-CN/core/cqrs.md` 与 `docs/zh-CN/source-generators/**` 的真实 owner
|
||||
2. 决定 `Cqrs` family 是补 dedicated landing 还是拆分现有入口页
|
||||
|
||||
### 当前恢复点:RP-004
|
||||
|
||||
- 完成 `Cqrs` 波次的模块族入口刷新:
|
||||
- 重写 `docs/zh-CN/core/cqrs.md`
|
||||
- 新建 `docs/zh-CN/source-generators/cqrs-handler-registry-generator.md`
|
||||
- 更新 `docs/zh-CN/source-generators/index.md`
|
||||
- 更新 `docs/zh-CN/api-reference/index.md`
|
||||
- 更新 `docs/.vitepress/config.mts`
|
||||
- 将 `Cqrs` family 从“README 已存在但 generator 入口分散”推进到“runtime / abstractions / source generator 都有明确站内入口”
|
||||
- 为 `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` 与
|
||||
`GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 中缺失的内部类型补齐 XML 注释
|
||||
- 基于轻量扫描确认:
|
||||
- `GFramework.Cqrs.Abstractions/Cqrs/` 当前类型声明级 XML 覆盖为 `20/20`
|
||||
- `GFramework.Cqrs` 根入口与 `Internal/` 已补到 `19/19`
|
||||
- `GFramework.Cqrs.SourceGenerators/Cqrs/` 当前类型声明级 XML 覆盖为 `3/3`
|
||||
|
||||
### 当前决策(RP-004)
|
||||
|
||||
- `docs/zh-CN/core/cqrs.md` 继续保留在 `Core` 栏目,但其角色调整为 `Cqrs` family landing,而不再只是 runtime 简介页
|
||||
- `Cqrs.SourceGenerators` 不单独新建一级导航栏目,而是在 `source-generators` 栏目内补一个专用专题页,保持站点 taxonomy 稳定
|
||||
- generator 入口以“专题页 + API reference 链接 + sidebar”三点联动,而不是只在 `source-generators/index.md` 留一个段落链接
|
||||
- XML inventory 仍维持“类型声明级基线”口径,不在本轮扩展成成员级 `param/returns/exception` 细审
|
||||
|
||||
### 当前验证(RP-004)
|
||||
|
||||
- 文档校验:
|
||||
- `validate-all.sh docs/zh-CN/core/cqrs.md`:通过
|
||||
- `validate-all.sh docs/zh-CN/source-generators/cqrs-handler-registry-generator.md`:通过
|
||||
- 轻量 XML inventory:
|
||||
- `GFramework.Cqrs/Internal/`:`14/14`
|
||||
- `GFramework.Cqrs.Abstractions/Cqrs/`:`20/20`
|
||||
- `GFramework.Cqrs.SourceGenerators/Cqrs/`:`3/3`
|
||||
- 构建校验:
|
||||
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release -p:RestoreFallbackFolders=`:通过
|
||||
- `cd docs && bun run build`:通过;仅保留 VitePress 大 chunk warning,无构建失败
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`:失败;当前 WSL / dotnet 环境仍引用失效的 Windows fallback package folder,并在多目标 inner build 阶段触发 `MSB4276` / `MSB4018`
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 切换到 `Game` family 波次,按 `Core` / `Ecs` / `Cqrs` 已验证模板继续补 XML inventory 与教程链路
|
||||
2. 把 `GFramework.Cqrs` 的本地构建阻塞留给后续环境治理或构建脚本清理,不在本 topic 内扩张为环境修复任务
|
||||
|
||||
### 当前恢复点:RP-005
|
||||
|
||||
- 完成 `Game` 波次的模块族入口刷新:
|
||||
- 更新 `GFramework.Game/README.md`
|
||||
- 更新 `GFramework.Game.Abstractions/README.md`
|
||||
- 更新 `GFramework.Game.SourceGenerators/README.md`
|
||||
- 更新 `docs/zh-CN/game/index.md`
|
||||
- 重写 `docs/zh-CN/abstractions/game-abstractions.md`
|
||||
- 将 `Game` family 从“README / 页面存在但缺少可审计 XML 入口,且 abstractions 页失真”推进到“runtime / abstractions / source generator 都有声明级 XML inventory 与真实采用边界”
|
||||
- 基于轻量扫描确认:
|
||||
- `GFramework.Game` 当前类型声明级 XML 覆盖为 `56/56`
|
||||
- `GFramework.Game.Abstractions` 当前类型声明级 XML 覆盖为 `80/80`
|
||||
- `GFramework.Game.SourceGenerators` 当前类型声明级 XML 覆盖为 `2/2`
|
||||
|
||||
### 当前决策(RP-005)
|
||||
|
||||
- `docs/zh-CN/abstractions/game-abstractions.md` 不再维护虚构接口摘录,而是与源码中的 `Config` / `Data` / `Setting` / `Scene` / `UI` / `Routing` 契约分组保持一致
|
||||
- `Game.SourceGenerators` 继续以 `README + docs/zh-CN/game/config-system.md + docs/zh-CN/source-generators/index.md` 组成入口,不额外新增只为凑数量的专题页
|
||||
- `docs/zh-CN/game/index.md` 补 frontmatter,并承担 `Game` family 的 XML 基线入口;更细的类型族说明继续留在模块 README 与 abstractions 页
|
||||
|
||||
### 当前验证(RP-005)
|
||||
|
||||
- 文档校验:
|
||||
- `validate-all.sh docs/zh-CN/abstractions/game-abstractions.md`:通过
|
||||
- `validate-all.sh docs/zh-CN/game/index.md`:通过
|
||||
- 轻量 XML inventory:
|
||||
- `GFramework.Game`:`56/56`
|
||||
- `GFramework.Game.Abstractions`:`80/80`
|
||||
- `GFramework.Game.SourceGenerators`:`2/2`
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过;仅保留 VitePress 大 chunk warning,无构建失败
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 进入 `Game` family 巡检,优先检查 `config-system.md`、`scene.md`、`ui.md` 与 `source-generators/index.md` 的交叉引用是否回漂
|
||||
2. 评估是否需要把 `Godot` family 的关键 XML inventory 摘要迁回 active topic,减少对 archive 的依赖
|
||||
|
||||
### 当前恢复点:RP-006
|
||||
|
||||
- 更新 `AGENTS.md` 的 WSL Git 规则:
|
||||
- 将显式 `git --git-dir=<...> --work-tree=<...>` 绑定提升为高于 `git.exe` 的默认优先级
|
||||
- 明确 plain Linux `git` 命中 worktree 路径翻译错误时,应先切到显式绑定而不是直接改用 `git.exe`
|
||||
- 明确 `git.exe` 只有在当前会话可执行时才作为次级 fallback
|
||||
- 记录本次恢复任务的环境偏差:
|
||||
- `git.exe` 在当前 WSL 会话中可解析,但执行会触发 `Exec format error`
|
||||
- plain `git` 会把 worktree 元数据路径翻译错并报“not a git repository”
|
||||
- 显式 `--git-dir` / `--work-tree` 绑定是本次已验证可用的 Git 操作方式
|
||||
|
||||
### 当前决策(RP-006)
|
||||
|
||||
- 把 Git 回退顺序写进 `AGENTS.md`,而不是只留在一次性的聊天上下文里
|
||||
- 不额外扩张 `gframework-boot` skill,因为它本身不内嵌 Git 选择逻辑,继续由 `AGENTS.md` 作为唯一准则
|
||||
- 继续把 `git.exe` 保留为 fallback,而不是完全删除,避免在可执行的 WSL 会话里丢掉可用路径
|
||||
|
||||
### 当前验证(RP-006)
|
||||
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过;仅保留 VitePress 大 chunk warning,无构建失败
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 继续 `Game` family 巡检,优先检查 `config-system.md`、`scene.md`、`ui.md` 与 `source-generators/index.md` 的交叉引用是否回漂
|
||||
2. 评估是否需要把 `Godot` family 的关键 XML inventory 摘要迁回 active topic,减少对 archive 的依赖
|
||||
|
||||
### 当前恢复点:RP-007
|
||||
|
||||
- 完成 `Game` family 巡检:
|
||||
- 复核 `docs/zh-CN/game/config-system.md`
|
||||
- 复核 `docs/zh-CN/game/scene.md`
|
||||
- 复核 `docs/zh-CN/game/ui.md`
|
||||
- 复核 `docs/zh-CN/source-generators/index.md`
|
||||
- 对照 `GFramework.Game`、`GFramework.Game.Abstractions`、`GFramework.Game.SourceGenerators` README 与相关源码 / 测试后,未发现需要立刻修正的采用语义回漂
|
||||
- 重点确认的真实语义包括:
|
||||
- `GameConfigBootstrap` / `RegisterAllGeneratedConfigTables(...)` / `GFrameworkConfigSchemaDirectory` 的配置入口仍与文档示例一致
|
||||
- `SceneRouterBase` 仍通过 `SemaphoreSlim` 串行化切换,并拒绝重复 `sceneKey` 入栈
|
||||
- `UiRouterBase` 仍将 `Page` 层与 `Overlay` / `Modal` / `Toast` / `Topmost` 分为两套入口,且 `Show(..., UiLayer.Page)` 会直接拒绝
|
||||
|
||||
### 当前决策(RP-007)
|
||||
|
||||
- 本轮不为“巡检通过”硬造文档改动,先把结论写回 active topic,保持恢复点准确
|
||||
- `Game` family 暂时转入稳定巡检,不在没有源码变化的情况下重复改写 landing page
|
||||
- 默认下一步切到 `Godot` family 摘要是否回迁,减少长期治理对 archive topic 的依赖
|
||||
|
||||
### 当前验证(RP-007)
|
||||
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过;仅保留 VitePress 大 chunk warning,无构建失败
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 评估是否需要把 `Godot` family 的关键 XML inventory 摘要迁回 active topic
|
||||
2. 若不需要迁回,则继续抽查 README / landing page / API reference 之间的 cross-link 是否出现新的漂移
|
||||
|
||||
### 当前恢复点:RP-008
|
||||
|
||||
- 使用 `$gframework-pr-review` 抓取当前分支 PR `#271` 后,确认 latest head review threads 仍有 `4` 条 open:
|
||||
- `docs/zh-CN/source-generators/cqrs-handler-registry-generator.md` 的 marker 类型约定说明缺口
|
||||
- `docs/zh-CN/ecs/index.md` 的边界说明语序问题
|
||||
- `docs/zh-CN/abstractions/ecs-arch-abstractions.md` 误放的 source-generator 内部模块提醒
|
||||
- `ai-plan/public/documentation-full-coverage-governance/todos/documentation-full-coverage-governance-tracking.md` 的验证历史过长,以及
|
||||
`ai-plan/public/archive/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md` 缺少显式结果态
|
||||
- 在当前 WSL 会话里,`gframework-pr-review` 脚本先命中了 `git.exe` 的 `Exec format error`
|
||||
- 已将 `.agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py` 改为优先使用 Linux `git` 的显式
|
||||
`--git-dir` / `--work-tree` 绑定,并仅在无法建立该绑定时回退到旧的可执行解析逻辑
|
||||
- 已同步更新 `.agents/skills/gframework-pr-review/SKILL.md`,使其 Git 策略与命令示例都与当前仓库状态一致
|
||||
- 已把 `DOCUMENTATION-FULL-COVERAGE-GOV-RP-001` 到 `RP-007` 的详细验证历史迁入
|
||||
`ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-validation-history-through-rp-007.md`
|
||||
|
||||
### 当前决策(RP-008)
|
||||
|
||||
- 继续把 latest-head unresolved threads 作为主信号,只修仍在本地成立的评论,不为已失效的历史 summary 做无意义回写
|
||||
- active tracking 只保留最新验证摘要与恢复点;详细验证历史留在 topic 自己的 archive,而不是继续堆在默认 boot 路径
|
||||
- `gframework-pr-review` 的脚本行为、技能文案与 `AGENTS.md` 必须保持同一套 WSL Git 策略,避免再次出现“文档说法正确但工具实现仍跑偏”的情况
|
||||
|
||||
### 当前验证(RP-008)
|
||||
|
||||
- PR review 抓取:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/gframework-current-pr-review.json`:通过
|
||||
- 脚本语法校验:
|
||||
- `python3 -B -c "from pathlib import Path; compile(Path('.agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py').read_text(encoding='utf-8'), '.agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py', 'exec')"`:通过
|
||||
- 文档校验:
|
||||
- `validate-all.sh docs/zh-CN/source-generators/cqrs-handler-registry-generator.md`:通过
|
||||
- `validate-all.sh docs/zh-CN/ecs/index.md`:通过
|
||||
- `validate-all.sh docs/zh-CN/abstractions/ecs-arch-abstractions.md`:通过
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过;仅保留既有 VitePress 大 chunk warning,无构建失败
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 提交本轮 PR review follow-up
|
||||
2. 推送当前分支后重新执行 `$gframework-pr-review`,观察 PR #271 的 open threads 是否收敛
|
||||
|
||||
### 当前恢复点:RP-009
|
||||
|
||||
- 按 `boot` 恢复 `documentation-full-coverage-governance` 主题
|
||||
- 重新读取 `AGENTS.md`、`.ai/environment/tools.ai.yaml`、`ai-plan/public/README.md` 与当前 topic 的 active todo / trace 后,确认当前 worktree `docs/sdk-update-documentation` 仍映射到本 topic
|
||||
- 当前 worktree Git 状态干净,且不存在 `ai-plan/private/` 的 worktree 私有恢复材料
|
||||
- 重新执行 `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/gframework-current-pr-review.json`
|
||||
- 抓取结果显示 PR `#271` 已关闭,latest reviewed commit 仍为 `df91d3706ba9db71737e803ef2f40f4841ecbbf1`
|
||||
- 当前 latest commit 仍显示 `2` 条 open thread,但两条都落在 `ai-plan` 文件上,且本地文件已经满足评论要求:
|
||||
- `ai-plan/public/archive/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md` 已包含显式 `结果:通过`
|
||||
- `ai-plan/public/documentation-full-coverage-governance/todos/documentation-full-coverage-governance-tracking.md` 已将 RP-001 至 RP-007 的详细验证明细迁入 archive
|
||||
- 因此本轮将 PR #271 follow-up 视为已完成,后续不再为 closed PR 上未自动收敛的陈旧 thread 状态追加仓库改动
|
||||
|
||||
### 当前决策(RP-009)
|
||||
|
||||
- `closed PR + stale open thread` 不再作为需要继续修改仓库内容的信号;除非后续 review 抓取显示新的 latest-head finding
|
||||
- `documentation-full-coverage-governance` 的默认下一步切回治理 backlog,优先判断是否把 `Godot` family 的关键 XML inventory 摘要迁回 active topic
|
||||
- 本轮 `boot` 不引入 subagent;关键恢复信号都能通过本地读取和单次 PR review 抓取直接确认
|
||||
|
||||
### 当前验证(RP-009)
|
||||
|
||||
- PR review 抓取:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/gframework-current-pr-review.json`:通过;PR `#271` 已关闭,latest reviewed commit 为 `df91d3706ba9db71737e803ef2f40f4841ecbbf1`,当前 `2` 条 open thread 都是已被本地文件满足的陈旧信号
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过;仅保留既有 VitePress 大 chunk warning,无构建失败
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 评估是否需要把 `Godot` family 的关键 XML inventory 摘要迁回 active topic
|
||||
2. 若不迁回,则在 active todo / trace 保留足够的 archive 指针,并继续抽查 README / landing page / API reference 的 cross-link 是否出现新的漂移
|
||||
|
||||
### 当前恢复点:RP-010
|
||||
|
||||
- 按 `boot` 恢复当前 topic 后,重新读取:
|
||||
- `AGENTS.md`
|
||||
- `.ai/environment/tools.ai.yaml`
|
||||
- `ai-plan/public/README.md`
|
||||
- `ai-plan/public/documentation-full-coverage-governance/todos/documentation-full-coverage-governance-tracking.md`
|
||||
- `ai-plan/public/documentation-full-coverage-governance/traces/documentation-full-coverage-governance-trace.md`
|
||||
- 确认当前任务状态属于 `resume`:
|
||||
- 当前分支仍为 `docs/sdk-update-documentation`
|
||||
- `ai-plan/public/README.md` 继续把本 worktree 映射到 `documentation-full-coverage-governance`
|
||||
- 当前 worktree 没有 `ai-plan/private/` 私有恢复材料
|
||||
- 为判断 `Godot` family 是否需要回填恢复摘要,补读归档主题:
|
||||
- `ai-plan/public/archive/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md`
|
||||
- `ai-plan/public/archive/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md`
|
||||
- `ai-plan/public/archive/documentation-governance-and-refresh/archive/todos/documentation-governance-and-refresh-history-through-2026-04-22.md`
|
||||
- 归档材料表明,`Godot` family 的可恢复关键信号已经稳定,且足以压缩成 active topic 里的最小摘要:
|
||||
- 核心页面集为 `docs/zh-CN/godot/index.md`、`architecture.md`、`scene.md`、`ui.md`、`signal.md`、`extensions.md`、`logging.md` 与 `docs/zh-CN/tutorials/godot-integration.md`
|
||||
- `GFramework.Godot.SourceGenerators` 继续作为 `[GetNode]`、`[BindNodeSignal]`、`AutoLoads`、`InputActions` 的 owner
|
||||
- `GFramework.Godot.SourceGenerators.Abstractions` 继续按 `IsPackable=false` 的内部支撑模块处理
|
||||
- `GodotSceneFactory` 在 provider 缺失时回退到 `SceneBehaviorFactory`,而 `GodotUiFactory` 仍要求 `IUiPageBehaviorProvider`
|
||||
- 因此本轮决定:
|
||||
- 不把整段 `documentation-governance-and-refresh` 历史重新迁回 active 路径
|
||||
- 只把足够让未来 `boot` 快速恢复的 `Godot` family 摘要写回 active todo
|
||||
- 继续把阶段级细节留在 archive,保持默认恢复入口轻量
|
||||
|
||||
### 当前决策(RP-010)
|
||||
|
||||
- `Godot` family 的“最小恢复摘要”应当留在 active topic,因为它已经属于长期治理 backlog 的默认上下文,而不仅仅是已完成项目的历史注脚
|
||||
- active topic 只保留对后续判断有用的事实:
|
||||
- 页面范围
|
||||
- generator owner
|
||||
- Scene / UI 真实运行时边界
|
||||
- archive 指针
|
||||
- `documentation-governance-and-refresh` archive 继续作为阶段级历史证据,不重新回到 `boot` 默认扫描路径
|
||||
- 下一步从“是否回填摘要”切换回“继续巡检 cross-link 漂移”,避免治理入口停留在已经完成的元问题上
|
||||
|
||||
### 当前验证(RP-010)
|
||||
|
||||
- 归档恢复检查:
|
||||
- `sed -n '1,260p' ai-plan/public/archive/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md`:通过
|
||||
- `sed -n '1,260p' ai-plan/public/archive/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md`:通过
|
||||
- `sed -n '1,240p' ai-plan/public/archive/documentation-governance-and-refresh/archive/todos/documentation-governance-and-refresh-history-through-2026-04-22.md`:通过
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 抽查 `Godot` 与 `Game` 相关 README / landing page / API reference 的 cross-link 是否出现新的漂移
|
||||
2. 当后续分支修改相关 README / docs / 公共 API 时,回到对应 module family 追加 targeted 巡检与验证
|
||||
|
||||
### 当前恢复点:RP-011
|
||||
|
||||
- 继续按 `boot` 恢复后的默认下一步执行 `Godot` / `Game` cross-link 巡检,并额外补读:
|
||||
- `GFramework.Godot/README.md`
|
||||
- `GFramework.Godot.SourceGenerators/README.md`
|
||||
- `docs/zh-CN/api-reference/index.md`
|
||||
- `docs/zh-CN/godot/index.md`
|
||||
- `docs/zh-CN/source-generators/index.md`
|
||||
- 结合 `GFramework.Godot.csproj`、`GFramework.Godot.SourceGenerators.csproj`、相关测试与 `scan_module_evidence.py` 输出,确认新的漂移点集中在入口 README:
|
||||
- `GFramework.Godot/README.md` 仍是旧版简略说明,没有记录当前包关系、子系统地图、最小接入路径与 `docs/zh-CN` 入口
|
||||
- `GFramework.Godot.SourceGenerators/README.md` 没有覆盖 `AutoScene`、`AutoUiPage`、`AutoRegisterExportedCollections` 这些当前已发布的生成器分组
|
||||
- `docs/zh-CN/api-reference/index.md` 的 `Godot` 映射仍只把生成器入口落到泛化总览页,恢复效率偏低
|
||||
- 因此本轮执行最小修复集:
|
||||
- 重写 `GFramework.Godot/README.md`
|
||||
- 重写 `GFramework.Godot.SourceGenerators/README.md`
|
||||
- 更新 `docs/zh-CN/api-reference/index.md` 的 `Godot` 行
|
||||
|
||||
### 当前决策(RP-011)
|
||||
|
||||
- 这轮不改 `docs/zh-CN/godot/**` landing / topic 页面,因为站内页面本身没有发现新的事实漂移,问题集中在仓库 README 与 API 入口的回退
|
||||
- `GFramework.Godot` README 必须和 `Game` / `Godot` 真实边界一致,明确它不是生成器 owner,也不引入虚构的 router 类型
|
||||
- `GFramework.Godot.SourceGenerators` README 采用“元数据 / 节点注入与信号绑定 / 行为包装 / 批量注册”四段式入口,避免读者只看到旧的三项能力
|
||||
- API 参考页对 `Godot` 生成器入口直接给出专题页链接,而不是仅要求读者再从总览页二次分流
|
||||
|
||||
### 当前验证(RP-011)
|
||||
|
||||
- 模块扫描:
|
||||
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Godot`:通过
|
||||
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Godot.SourceGenerators`:通过
|
||||
- 文档校验:
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-links.sh GFramework.Godot/README.md`:通过
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-code-blocks.sh GFramework.Godot/README.md`:通过
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-links.sh GFramework.Godot.SourceGenerators/README.md`:通过
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-code-blocks.sh GFramework.Godot.SourceGenerators/README.md`:通过
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/api-reference/index.md`:通过
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过;仅保留既有 VitePress 大 chunk warning,无构建失败
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 继续抽查根 `README.md`、`docs/zh-CN/source-generators/index.md` 与 `docs/zh-CN/tutorials/godot-integration.md` 是否仍把 `Godot` owner 写回旧边界
|
||||
2. 当后续分支继续修改 `Game` / `Godot` family 入口时,沿用当前 README -> landing -> API reference 的最小修复顺序
|
||||
|
||||
### 当前恢复点:RP-012
|
||||
|
||||
- 继续按 `boot` 恢复后的默认下一步执行 `Game` / `Godot` 入口巡检,并重新读取:
|
||||
- `README.md`
|
||||
- `docs/zh-CN/source-generators/index.md`
|
||||
- `docs/zh-CN/tutorials/godot-integration.md`
|
||||
- `docs/zh-CN/api-reference/index.md`
|
||||
- `GFramework.Godot/README.md`
|
||||
- `GFramework.Godot.SourceGenerators/README.md`
|
||||
- 巡检结果显示主体内容仍然稳定,但根入口摘要存在一处残留漂移:
|
||||
- 根 `README.md` 仍把 `GFramework.Godot.SourceGenerators` 写成“Godot 场景专用源码生成器”,与当前包实际覆盖的 `project.godot` 元数据、节点注入、信号绑定、Scene / UI 包装和导出集合注册职责不符
|
||||
- `docs/zh-CN/source-generators/index.md` 的选包描述同步缺少 Scene / UI 包装与导出集合注册辅助这组能力
|
||||
- 因此本轮执行最小修复集:
|
||||
- 更新根 `README.md` 的 `GFramework.Godot.SourceGenerators` 模块描述
|
||||
- 更新 `docs/zh-CN/source-generators/index.md` 的 Godot 选包摘要
|
||||
|
||||
### 当前决策(RP-012)
|
||||
|
||||
- 继续维持“只修新发现的入口漂移,不重写稳定页面”的治理节奏;这轮不改 `docs/zh-CN/tutorials/godot-integration.md`,因为教程与 README / 生成器专题页仍使用同一套职责边界
|
||||
- 根 `README.md` 作为仓库一级入口,必须与模块 README 保持同一粒度的职责摘要;如果根入口比模块 README 更旧,后续 `boot` 和人工恢复都会被误导
|
||||
- `source-generators/index.md` 的选包段落需要覆盖当前真实能力分组,但不重复展开各专题页细节,避免重新长成第二份 README
|
||||
|
||||
### 当前验证(RP-012)
|
||||
|
||||
- 文档校验:
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-links.sh README.md`:通过
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-code-blocks.sh README.md`:通过
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators/index.md`:通过
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过;仅保留既有 VitePress 大 chunk warning,无构建失败
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 继续抽查 `docs/zh-CN/tutorials/godot-integration.md`、`docs/zh-CN/godot/index.md` 与根 `README.md` 的职责摘要是否继续保持同一口径
|
||||
2. 当后续分支继续修改 `Game` / `Godot` family 入口时,沿用当前 README -> landing -> API reference 的最小修复顺序
|
||||
|
||||
### 当前恢复点:RP-013
|
||||
|
||||
- 使用 `$gframework-boot` 恢复当前 worktree 后,按 `documentation-full-coverage-governance` 的默认下一步执行一次
|
||||
validation-only 巡检,并补读:
|
||||
- `README.md`
|
||||
- `docs/zh-CN/godot/index.md`
|
||||
- `docs/zh-CN/tutorials/godot-integration.md`
|
||||
- `docs/zh-CN/source-generators/index.md`
|
||||
- `docs/zh-CN/api-reference/index.md`
|
||||
- `GFramework.Godot/README.md`
|
||||
- `.agents/skills/gframework-doc-refresh/SKILL.md`
|
||||
- 同时执行 `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Godot`,确认当前 `Godot`
|
||||
docs surface 除 `index.md`、`architecture.md`、`scene.md`、`ui.md`、`signal.md`、`extensions.md`、`logging.md`
|
||||
外,还应把 `storage.md` 与 `setting.md` 视为默认恢复集合的一部分
|
||||
- 巡检结论:
|
||||
- 根 `README.md`、`docs/zh-CN/godot/index.md`、`docs/zh-CN/tutorials/godot-integration.md`、
|
||||
`docs/zh-CN/source-generators/index.md` 与 `docs/zh-CN/api-reference/index.md` 当前仍保持同一套 `Godot`
|
||||
owner / adoption path 叙述,没有发现新的入口漂移
|
||||
- 本轮不需要改动稳定的 README / docs 页面,只需要把 active topic 的最小恢复摘要补齐到当前 landing page
|
||||
实际覆盖的页集合
|
||||
- 因此本轮执行的唯一修改是:
|
||||
- 更新 `ai-plan/public/documentation-full-coverage-governance/todos/documentation-full-coverage-governance-tracking.md`
|
||||
的恢复点、`Godot` 页面集合、稳定性巡检结论与下一步
|
||||
- 记录本条 `RP-013` trace,保证未来 `boot` 不会漏掉 `storage.md` / `setting.md`
|
||||
|
||||
### 当前决策(RP-013)
|
||||
|
||||
- 当前 topic 继续保持“巡检优先、最小修复”的节奏;验证通过时不为凑改动而重写稳定页面
|
||||
- `scan_module_evidence.py` 识别出的 docs surface 应优先反映到 active recovery artifact,而不是只留在一次性 chat
|
||||
上下文
|
||||
- `Godot` family 的后续巡检重点从“根入口是否还残留旧描述”切换为“storage / setting 子页是否和 landing / README
|
||||
保持同一口径”
|
||||
|
||||
### 当前验证(RP-013)
|
||||
|
||||
- 模块扫描:
|
||||
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Godot`:通过
|
||||
- 文档校验:
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/index.md`:通过
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/tutorials/godot-integration.md`:通过
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过;仅保留既有 VitePress 大 chunk warning,无构建失败
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 若后续分支继续调整 `GFramework.Godot` 运行时入口,优先复核 `docs/zh-CN/godot/storage.md`、`setting.md` 与根
|
||||
`README.md` / landing page 是否仍保持同一套职责边界
|
||||
2. 当后续分支再修改 `Godot` / `Game` family 的 README、docs 或公共 API 时,回到对应模块追加 targeted 巡检与验证
|
||||
|
||||
### 当前恢复点:RP-016
|
||||
|
||||
- 用户明确要求“继续下一步的文档治理,并形成足够体量的 PR”后,当前 topic 不再停留在 validation-only 巡检,
|
||||
而是切到一个可独立成波次的 `Game` persistence docs surface:
|
||||
- `docs/zh-CN/game/data.md`
|
||||
- `docs/zh-CN/game/storage.md`
|
||||
- `docs/zh-CN/game/serialization.md`
|
||||
- `docs/zh-CN/game/setting.md`
|
||||
- 先执行 `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Game`,确认 `Game` 的默认 docs surface
|
||||
包含 `data`、`storage`、`serialization`、`setting`、`scene`、`ui`、`config-system` 与 landing / API fallback
|
||||
- 结合 `GFramework.Game/README.md`、`FileStorage.cs`、`ScopedStorage.cs`、`DataRepository.cs`、
|
||||
`UnifiedSettingsDataRepository.cs`、`SaveRepository.cs`、`JsonSerializer.cs`、`SettingsModel.cs`、
|
||||
`SettingsSystem.cs`、`JsonSerializerTests.cs`、`SettingsModelTests.cs` 与 `PersistenceTests.cs`,确认四个旧页面存在持续性漂移:
|
||||
- `storage.md` 仍按旧版通用 API 手册组织,没有强调 `FileStorage` / `ScopedStorage` 与 repository 的职责边界
|
||||
- `data.md` 缺少 `DataRepository`、`UnifiedSettingsDataRepository` 与 `SaveRepository<TSaveData>` 三层分工,以及当前备份 /
|
||||
批量事件 / 存档迁移语义
|
||||
- `serialization.md` 仍沿用“业务层手工 Serialize 再写回 storage”的旧接法,没有反映当前 `FileStorage`
|
||||
已直接复用注入的 `ISerializer`
|
||||
- `setting.md` 虽已回到 `ISettingsModel` / `RegisterApplicator(...)` 口径,但结构仍未与当前 `Game` family 的 runtime topic
|
||||
页面统一,也缺少 frontmatter
|
||||
- 因此本轮执行的最小但成组修复集是:
|
||||
- 重写 `docs/zh-CN/game/storage.md`
|
||||
- 重写 `docs/zh-CN/game/data.md`
|
||||
- 重写 `docs/zh-CN/game/serialization.md`
|
||||
- 重写 `docs/zh-CN/game/setting.md`
|
||||
- 更新 active tracking 的恢复点、治理结论与下一步
|
||||
|
||||
### 当前决策(RP-016)
|
||||
|
||||
- 这轮不去扩张到 `Game` tutorial 或 root README,而是把同一子领域里仍残留的旧页一次性收口,形成清晰的 PR 边界
|
||||
- `Game` persistence docs surface 统一采用“当前公开入口 -> 最小接入路径 -> 当前边界 -> 继续阅读”的结构,
|
||||
不再维护分散的伪 API 手册页
|
||||
- 文档示例只保留可直接映射到当前框架类型、测试行为或已验证 consumer wiring 的内容,避免继续写虚构接线名
|
||||
|
||||
### 当前验证(RP-016)
|
||||
|
||||
- 模块扫描:
|
||||
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Game`:通过
|
||||
- 文档校验:
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/game/data.md`:通过
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/game/storage.md`:通过
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/game/serialization.md`:通过
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/game/setting.md`:通过
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过;仅保留既有 VitePress 大 chunk warning,无构建失败
|
||||
- 代码 / 测试证据:
|
||||
- `GFramework.Game/README.md`
|
||||
- `GFramework.Game/Storage/FileStorage.cs`
|
||||
- `GFramework.Game/Storage/ScopedStorage.cs`
|
||||
- `GFramework.Game/Data/DataRepository.cs`
|
||||
- `GFramework.Game/Data/UnifiedSettingsDataRepository.cs`
|
||||
- `GFramework.Game/Data/SaveRepository.cs`
|
||||
- `GFramework.Game/Serializer/JsonSerializer.cs`
|
||||
- `GFramework.Game/Setting/SettingsModel.cs`
|
||||
- `GFramework.Game/Setting/SettingsSystem.cs`
|
||||
- `GFramework.Game.Tests/Data/PersistenceTests.cs`
|
||||
- `GFramework.Game.Tests/Serializer/JsonSerializerTests.cs`
|
||||
- `GFramework.Game.Tests/Setting/SettingsModelTests.cs`
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 回填 tracking 的最新验证结果,并按仓库规则提交本轮 `Game` persistence docs wave
|
||||
2. 若后续分支继续调整 `GFramework.Game` 的 persistence runtime 或 README,优先复核这四个 topic page 与 landing page 的一致性
|
||||
|
||||
### 当前恢复点:RP-015
|
||||
|
||||
- 通过 `$gframework-boot` 恢复当前 worktree 后,继续按 `documentation-full-coverage-governance` 的默认下一步执行一次
|
||||
validation-only 巡检,并补读:
|
||||
- `GFramework.Godot/README.md`
|
||||
- `docs/zh-CN/godot/index.md`
|
||||
- `docs/zh-CN/godot/storage.md`
|
||||
- `docs/zh-CN/godot/setting.md`
|
||||
- `docs/zh-CN/source-generators/index.md`
|
||||
- `docs/zh-CN/api-reference/index.md`
|
||||
- `docs/zh-CN/tutorials/godot-integration.md`
|
||||
- `GFramework.Godot/Setting/GodotAudioSettings.cs`
|
||||
- `GFramework.Godot/Setting/GodotGraphicsSettings.cs`
|
||||
- `GFramework.Godot/Setting/GodotLocalizationSettings.cs`
|
||||
- `GFramework.Game.Tests/Setting/GodotLocalizationSettingsTests.cs`
|
||||
- 同时重新执行 `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Godot`,确认 `Godot`
|
||||
docs surface 仍然覆盖 landing、`storage.md`、`setting.md`、source-generators fallback、API reference 与
|
||||
CoreGrid 参考接线,没有新的默认恢复页缺口
|
||||
- 巡检结论:
|
||||
- `GFramework.Godot/README.md`、`docs/zh-CN/godot/index.md`、`storage.md`、`setting.md`、`docs/zh-CN/source-generators/index.md`、
|
||||
`docs/zh-CN/api-reference/index.md` 与 `docs/zh-CN/tutorials/godot-integration.md` 当前仍保持同一套
|
||||
`Godot` owner / adoption path 叙述,没有发现自 `RP-014` 之后的新入口漂移
|
||||
- `setting.md` 里关于 `ISettingsModel`、`RegisterApplicator(...)`、`LocalizationMap` fallback 的描述,仍与
|
||||
`GodotAudioSettings`、`GodotGraphicsSettings`、`GodotLocalizationSettings` 以及
|
||||
`GodotLocalizationSettingsTests` 保持一致
|
||||
- 因此本轮执行的唯一修改是:
|
||||
- 更新 active tracking 与 trace 的恢复点、巡检结论和验证结果
|
||||
|
||||
### 当前决策(RP-015)
|
||||
|
||||
- 当前 topic 继续维持“巡检优先、无漂移则只更新 recovery artifact”的治理节奏,不为凑改动重写稳定页面
|
||||
- `boot` 恢复本身也应留下可复用的恢复证据,避免下一次启动时重复判断“最近一次巡检是否已经覆盖 `storage.md` /
|
||||
`setting.md`”
|
||||
|
||||
### 当前验证(RP-015)
|
||||
|
||||
- 模块扫描:
|
||||
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Godot`:通过
|
||||
- 文档校验:
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/storage.md`:通过
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/setting.md`:通过
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过;仅保留既有 VitePress 大 chunk warning,无构建失败
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 若后续分支继续调整 `GFramework.Godot` 运行时入口,优先复核 `docs/zh-CN/godot/storage.md`、`setting.md` 与根
|
||||
`README.md` / landing page 是否仍保持同一套职责边界
|
||||
2. 当后续分支再修改 `Godot` / `Game` family 的 README、docs 或公共 API 时,回到对应模块追加 targeted 巡检与验证
|
||||
|
||||
### 当前恢复点:RP-014
|
||||
|
||||
- 继续沿用 `RP-013` 的 `Godot` docs surface 巡检范围,补读:
|
||||
- `docs/zh-CN/godot/storage.md`
|
||||
- `docs/zh-CN/godot/setting.md`
|
||||
- `GFramework.Godot/Storage/GodotFileStorage.cs`
|
||||
- `GFramework.Godot/Setting/GodotAudioSettings.cs`
|
||||
- `GFramework.Godot/Setting/GodotGraphicsSettings.cs`
|
||||
- `GFramework.Godot/Setting/GodotLocalizationSettings.cs`
|
||||
- `GFramework.Godot/Setting/Data/AudioBusMap.cs`
|
||||
- `GFramework.Godot/Setting/Data/LocalizationMap.cs`
|
||||
- `GFramework.Game.Tests/Setting/GodotLocalizationSettingsTests.cs`
|
||||
- `ai-libs/CoreGrid/scripts/module/UtilityModule.cs`
|
||||
- `ai-libs/CoreGrid/scripts/module/ModelModule.cs`
|
||||
- 巡检发现两处新的 topic 级漂移:
|
||||
- `docs/zh-CN/godot/storage.md` 仍按旧版 API 手册组织,缺少 frontmatter、当前 `IStorage` / repository 分工与
|
||||
`GodotYamlConfigLoader` 分流说明
|
||||
- `docs/zh-CN/godot/setting.md` 仍使用过时的“settings data 直接注入 applicator 构造函数”叙述,没有反映当前
|
||||
`ISettingsModel` + `RegisterApplicator(...)` 的真实接线方式
|
||||
- 因此本轮执行最小修复集:
|
||||
- 重写 `docs/zh-CN/godot/storage.md`
|
||||
- 重写 `docs/zh-CN/godot/setting.md`
|
||||
- 更新 active tracking 的恢复点、巡检结论与验证结果
|
||||
|
||||
### 当前决策(RP-014)
|
||||
|
||||
- 继续遵循“README / landing 稳定时,不重写稳定入口;只修新发现的 topic 漂移”的治理节奏
|
||||
- `storage.md` 应强调宿主路径语义与 repository 分工,而不是重复 `Game` 通用存储手册
|
||||
- `setting.md` 应强调 applicator 注册和运行时边界,而不是重新维护一份过时的设置 API 摘要
|
||||
|
||||
### 当前验证(RP-014)
|
||||
|
||||
- 模块扫描:
|
||||
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Godot`:通过
|
||||
- 文档校验:
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/storage.md`:通过
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/setting.md`:通过
|
||||
- 构建校验:
|
||||
- `cd docs && bun run build`:通过;仅保留既有 VitePress 大 chunk warning,无构建失败
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 若后续分支继续调整 `GFramework.Godot` 运行时入口,优先复核 `docs/zh-CN/godot/storage.md`、`setting.md` 与根
|
||||
`README.md` / landing page 是否仍保持同一套职责边界
|
||||
2. 当后续分支再修改 `Godot` / `Game` family 的 README、docs 或公共 API 时,回到对应模块追加 targeted 巡检与验证
|
||||
1. 提交并推送本地修正后,再次抓取 PR `#272`,确认 Greptile open thread 是否已在新 head commit 上消失。
|
||||
2. 若继续执行文档治理批处理,优先排查剩余的非导航型裸路径引用、标题锚点与站内链接热点,而不是扩成跨模块大波次。
|
||||
|
||||
@ -99,6 +99,6 @@ public sealed class DiagnosticsFeature
|
||||
1. 先读本页,确认你是否真的只需要契约层
|
||||
2. 再看 [`../core/index.md`](../core/index.md) 了解默认运行时怎么组织这些契约
|
||||
3. 回到模块 README:
|
||||
- `GFramework.Core.Abstractions/README.md`
|
||||
- `GFramework.Core/README.md`
|
||||
- [`GFramework.Core.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core.Abstractions/README.md)
|
||||
- [`GFramework.Core/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core/README.md)
|
||||
4. 需要统一导航时,再看 [`../api-reference/index.md`](../api-reference/index.md)
|
||||
|
||||
@ -90,5 +90,5 @@ var options = new ArchOptions
|
||||
1. 先读本页,确认你是否真的只需要契约层
|
||||
2. 如果需要默认实现,再看 [`../ecs/arch.md`](../ecs/arch.md)
|
||||
3. 回到对应模块 README:
|
||||
- `GFramework.Ecs.Arch.Abstractions/README.md`
|
||||
- `GFramework.Ecs.Arch/README.md`
|
||||
- [`GFramework.Ecs.Arch.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Ecs.Arch.Abstractions/README.md)
|
||||
- [`GFramework.Ecs.Arch/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Ecs.Arch/README.md)
|
||||
|
||||
@ -118,5 +118,5 @@ public sealed class ContinueGameCommandHandler
|
||||
- [`../game/scene.md`](../game/scene.md)
|
||||
- [`../game/ui.md`](../game/ui.md)
|
||||
4. 需要仓库侧入口时,回到:
|
||||
- `GFramework.Game.Abstractions/README.md`
|
||||
- `GFramework.Game/README.md`
|
||||
- [`GFramework.Game.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.Abstractions/README.md)
|
||||
- [`GFramework.Game/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game/README.md)
|
||||
|
||||
@ -21,7 +21,7 @@ description: GFramework 的 API 阅读入口,按模块映射 README、专题
|
||||
先读模块 README,再读对应 landing page:
|
||||
|
||||
- 入门入口:[`../getting-started/index.md`](../getting-started/index.md)
|
||||
- 根模块地图:仓库根 `README.md`
|
||||
- 根模块地图:仓库根 [`README.md`](https://github.com/GeWuYou/GFramework/blob/main/README.md)
|
||||
|
||||
### 想确认“这个功能属于哪个模块”
|
||||
|
||||
@ -29,11 +29,11 @@ description: GFramework 的 API 阅读入口,按模块映射 README、专题
|
||||
|
||||
| 模块族 | 模块 README | 站内入口 | XML 文档关注点 |
|
||||
| --- | --- | --- | --- |
|
||||
| `Core` / `Core.Abstractions` | `GFramework.Core/README.md`、`GFramework.Core.Abstractions/README.md` | [`../core/index.md`](../core/index.md)、[`../abstractions/core-abstractions.md`](../abstractions/core-abstractions.md) | 架构入口、生命周期、命令 / 查询 / 事件 / 状态 / 资源 / 日志 / 配置 / 并发契约 |
|
||||
| `Cqrs` / `Cqrs.Abstractions` / `Cqrs.SourceGenerators` | `GFramework.Cqrs/README.md`、`GFramework.Cqrs.Abstractions/README.md`、`GFramework.Cqrs.SourceGenerators/README.md` | [`../core/cqrs.md`](../core/cqrs.md)、[`../source-generators/cqrs-handler-registry-generator.md`](../source-generators/cqrs-handler-registry-generator.md) | request / notification / handler / pipeline / registry / fallback contract |
|
||||
| `Game` / `Game.Abstractions` / `Game.SourceGenerators` | `GFramework.Game/README.md`、`GFramework.Game.Abstractions/README.md`、`GFramework.Game.SourceGenerators/README.md` | [`../game/index.md`](../game/index.md)、[`../abstractions/game-abstractions.md`](../abstractions/game-abstractions.md) | 配置、数据、设置、场景、UI、存储、序列化契约 |
|
||||
| `Godot` / `Godot.SourceGenerators` | `GFramework.Godot/README.md`、`GFramework.Godot.SourceGenerators/README.md` | [`../godot/index.md`](../godot/index.md)、[`../source-generators/godot-project-generator.md`](../source-generators/godot-project-generator.md)、[`../source-generators/get-node-generator.md`](../source-generators/get-node-generator.md)、[`../source-generators/bind-node-signal-generator.md`](../source-generators/bind-node-signal-generator.md) | 节点扩展、场景 / UI 适配、配置 / 存储 / 设置接线、Godot 生成器入口 |
|
||||
| `Ecs.Arch` / `Ecs.Arch.Abstractions` | `GFramework.Ecs.Arch/README.md`、`GFramework.Ecs.Arch.Abstractions/README.md` | [`../ecs/index.md`](../ecs/index.md)、[`../ecs/arch.md`](../ecs/arch.md)、[`../abstractions/ecs-arch-abstractions.md`](../abstractions/ecs-arch-abstractions.md) | ECS 模块契约、系统适配、配置对象和运行时装配边界 |
|
||||
| `Core` / `Core.Abstractions` | [`GFramework.Core/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core/README.md)、[`GFramework.Core.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core.Abstractions/README.md) | [`../core/index.md`](../core/index.md)、[`../abstractions/core-abstractions.md`](../abstractions/core-abstractions.md) | 架构入口、生命周期、命令 / 查询 / 事件 / 状态 / 资源 / 日志 / 配置 / 并发契约 |
|
||||
| `Cqrs` / `Cqrs.Abstractions` / `Cqrs.SourceGenerators` | [`GFramework.Cqrs/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs/README.md)、[`GFramework.Cqrs.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs.Abstractions/README.md)、[`GFramework.Cqrs.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs.SourceGenerators/README.md) | [`../core/cqrs.md`](../core/cqrs.md)、[`../source-generators/cqrs-handler-registry-generator.md`](../source-generators/cqrs-handler-registry-generator.md) | request / notification / handler / pipeline / registry / fallback contract |
|
||||
| `Game` / `Game.Abstractions` / `Game.SourceGenerators` | [`GFramework.Game/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game/README.md)、[`GFramework.Game.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.Abstractions/README.md)、[`GFramework.Game.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.SourceGenerators/README.md) | [`../game/index.md`](../game/index.md)、[`../abstractions/game-abstractions.md`](../abstractions/game-abstractions.md) | 配置、数据、设置、场景、UI、存储、序列化契约 |
|
||||
| `Godot` / `Godot.SourceGenerators` | [`GFramework.Godot/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot/README.md)、[`GFramework.Godot.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot.SourceGenerators/README.md) | [`../godot/index.md`](../godot/index.md)、[`../source-generators/godot-project-generator.md`](../source-generators/godot-project-generator.md)、[`../source-generators/get-node-generator.md`](../source-generators/get-node-generator.md)、[`../source-generators/bind-node-signal-generator.md`](../source-generators/bind-node-signal-generator.md) | 节点扩展、场景 / UI 适配、配置 / 存储 / 设置接线、Godot 生成器入口 |
|
||||
| `Ecs.Arch` / `Ecs.Arch.Abstractions` | [`GFramework.Ecs.Arch/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Ecs.Arch/README.md)、[`GFramework.Ecs.Arch.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Ecs.Arch.Abstractions/README.md) | [`../ecs/index.md`](../ecs/index.md)、[`../ecs/arch.md`](../ecs/arch.md)、[`../abstractions/ecs-arch-abstractions.md`](../abstractions/ecs-arch-abstractions.md) | ECS 模块契约、系统适配、配置对象和运行时装配边界 |
|
||||
|
||||
## 先看 XML,还是先看教程
|
||||
|
||||
|
||||
@ -186,4 +186,4 @@ RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
|
||||
- 架构入口:[architecture](./architecture.md)
|
||||
- 上下文入口:[context](./context.md)
|
||||
- 生成器专题:[../source-generators/cqrs-handler-registry-generator.md](../source-generators/cqrs-handler-registry-generator.md)
|
||||
- 模块 README:`GFramework.Cqrs/README.md`
|
||||
- 模块 README:[`GFramework.Cqrs/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs/README.md)
|
||||
|
||||
@ -17,7 +17,7 @@ GFramework.Core 提供了一套完整的函数式编程工具,帮助开发者
|
||||
|
||||
### Option 类型
|
||||
|
||||
`Option<T>` 表示可能存在或不存在的值,用于替代 null 引用。它有两种状态:
|
||||
`Option<T>` 表示可能存在或不存在的值,用于替代 null 引用。它有两种状态:
|
||||
|
||||
- **Some**:包含一个值
|
||||
- **None**:不包含值
|
||||
@ -26,7 +26,7 @@ GFramework.Core 提供了一套完整的函数式编程工具,帮助开发者
|
||||
|
||||
### Result 类型
|
||||
|
||||
`Result<T>` 表示操作的结果,可能是成功值或失败异常。它有三种状态:
|
||||
`Result<T>` 表示操作的结果,可能是成功值或失败异常。它有三种状态:
|
||||
|
||||
- **Success**:操作成功,包含返回值
|
||||
- **Faulted**:操作失败,包含异常信息
|
||||
@ -586,14 +586,14 @@ public async Task<Result<Response>> ProcessRequestAsync(Request request)
|
||||
|
||||
### Option vs Nullable
|
||||
|
||||
**Q: Option 和 Nullable<T> 有什么区别?**
|
||||
**Q: Option 和 `Nullable<T>` 有什么区别?**
|
||||
|
||||
A:
|
||||
|
||||
- `Nullable<T>` 只能用于值类型,`Option<T>` 可用于任何类型
|
||||
- `Option<T>` 提供丰富的函数式操作(Map、Bind、Filter 等)
|
||||
- `Option<T>` 强制显式处理"无值"情况,更安全
|
||||
- `Option<T>` 可以与 Result 等其他函数式类型组合
|
||||
- `Nullable<T>` 只能用于值类型,`Option<T>` 可用于任何类型
|
||||
- `Option<T>` 提供丰富的函数式操作(Map、Bind、Filter 等)
|
||||
- `Option<T>` 强制显式处理"无值"情况,更安全
|
||||
- `Option<T>` 可以与 Result 等其他函数式类型组合
|
||||
|
||||
### Result vs Exception
|
||||
|
||||
|
||||
@ -71,7 +71,7 @@ dotnet add package GeWuYou.GFramework.Core.Abstractions
|
||||
|
||||
如果你已经知道模块归属,但想确认公开类型的契约边界,建议按下面顺序阅读:
|
||||
|
||||
1. 先看模块 README `GFramework.Core/README.md`,确认包关系和目录边界
|
||||
1. 先看模块 README [`GFramework.Core/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core/README.md),确认包关系和目录边界
|
||||
2. 再看本栏目对应专题页,确认采用顺序、生命周期与推荐接线方式
|
||||
3. 最后回到源码中的 XML 文档,重点核对这些类型族:
|
||||
- `Architecture` / `IArchitectureContext`
|
||||
@ -149,7 +149,7 @@ public sealed class CounterArchitecture : Architecture
|
||||
|
||||
## 对应模块入口
|
||||
|
||||
- `GFramework.Core/README.md`
|
||||
- `GFramework.Core.Abstractions/README.md`
|
||||
- `docs/zh-CN/api-reference/index.md`
|
||||
- 仓库根 `README.md`
|
||||
- [`GFramework.Core/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core/README.md)
|
||||
- [`GFramework.Core.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core.Abstractions/README.md)
|
||||
- [`docs/zh-CN/api-reference/index.md`](../api-reference/index.md)
|
||||
- 仓库根 [`README.md`](https://github.com/GeWuYou/GFramework/blob/main/README.md)
|
||||
|
||||
@ -140,5 +140,5 @@ ecsModule.Update(deltaTime);
|
||||
|
||||
- ECS 模块总览:[`index.md`](./index.md)
|
||||
- 抽象契约页:[`../abstractions/ecs-arch-abstractions.md`](../abstractions/ecs-arch-abstractions.md)
|
||||
- 仓库模块 README:`GFramework.Ecs.Arch/README.md`
|
||||
- 仓库模块 README:[`GFramework.Ecs.Arch/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Ecs.Arch/README.md)
|
||||
- 统一 API / XML 导航:[`../api-reference/index.md`](../api-reference/index.md)
|
||||
|
||||
@ -49,6 +49,21 @@ description: 以当前 GFramework.Game 源码与 PersistenceTests 为准,说
|
||||
- 保存、删除时会整文件回写,而不是只改单个 section 文件
|
||||
- 开启 `AutoBackup` 时,备份粒度也是整个统一文件,不是单个 section
|
||||
|
||||
当 `DataRepositoryOptions.BasePath = "settings"`,并保持默认文件名时,最小目录结构通常是:
|
||||
|
||||
```text
|
||||
settings/
|
||||
settings.json
|
||||
```
|
||||
|
||||
如果同时开启 `AutoBackup = true`,则同一路径下还会额外出现:
|
||||
|
||||
```text
|
||||
settings/
|
||||
settings.json
|
||||
settings.backup
|
||||
```
|
||||
|
||||
### `SaveRepository<TSaveData>`
|
||||
|
||||
适合:
|
||||
|
||||
@ -127,6 +127,6 @@ IStorage storage = new FileStorage("GameData", serializer);
|
||||
|
||||
## 对应模块入口
|
||||
|
||||
- `GFramework.Game/README.md`
|
||||
- `GFramework.Game.Abstractions/README.md`
|
||||
- 仓库根 `README.md`
|
||||
- [`GFramework.Game/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game/README.md)
|
||||
- [`GFramework.Game.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.Abstractions/README.md)
|
||||
- 仓库根 [`README.md`](https://github.com/GeWuYou/GFramework/blob/main/README.md)
|
||||
|
||||
@ -258,5 +258,5 @@ await sceneRouter.PopAsync();
|
||||
|
||||
1. [game/index.md](./index.md)
|
||||
2. [ui.md](./ui.md)
|
||||
3. `GFramework.Game/README.md`
|
||||
4. `GFramework.Game.Abstractions/README.md`
|
||||
3. [`GFramework.Game/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game/README.md)
|
||||
4. [`GFramework.Game.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.Abstractions/README.md)
|
||||
|
||||
@ -329,5 +329,5 @@ uiRouter.Hide(modalHandle, UiLayer.Modal);
|
||||
1. [game/index.md](./index.md)
|
||||
2. [scene.md](./scene.md)
|
||||
3. [../source-generators/auto-ui-page-generator.md](../source-generators/auto-ui-page-generator.md)
|
||||
4. `GFramework.Game/README.md`
|
||||
5. `GFramework.Game.Abstractions/README.md`
|
||||
4. [`GFramework.Game/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game/README.md)
|
||||
5. [`GFramework.Game.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.Abstractions/README.md)
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
对应文档:
|
||||
|
||||
- [`../core/cqrs.md`](../core/cqrs.md)
|
||||
- 仓库内模块入口:`GFramework.Cqrs/README.md`
|
||||
- 仓库内模块入口:[`GFramework.Cqrs/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs/README.md)
|
||||
|
||||
### 想做游戏运行时
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
对应文档:
|
||||
|
||||
- [`../game/index.md`](../game/index.md)
|
||||
- 仓库内模块入口:`GFramework.Game/README.md`
|
||||
- 仓库内模块入口:[`GFramework.Game/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game/README.md)
|
||||
|
||||
### 想接入 Godot
|
||||
|
||||
@ -76,7 +76,7 @@
|
||||
对应文档:
|
||||
|
||||
- [`../godot/index.md`](../godot/index.md)
|
||||
- 仓库内模块入口:`GFramework.Godot/README.md`
|
||||
- 仓库内模块入口:[`GFramework.Godot/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot/README.md)
|
||||
|
||||
## Source Generators 什么时候装
|
||||
|
||||
|
||||
@ -72,7 +72,7 @@ description: 以当前 GFramework.Godot 源码、测试与 CoreGrid 接线为准
|
||||
|
||||
## 最小接入路径
|
||||
|
||||
当前消费者 `ai-libs/CoreGrid` 的接法,是先注册 `SettingsModel<ISettingsDataRepository>`,再把 Godot applicator
|
||||
当前消费者 `ai-libs/CoreGrid` 的接法,是先注册 `SettingsModel<ISettingsDataRepository>`,再把 Godot applicator
|
||||
挂进去:
|
||||
|
||||
```csharp
|
||||
|
||||
@ -99,7 +99,7 @@ architecture.RegisterUtility<ISaveRepository<GameSaveData>>(new SaveRepository<G
|
||||
|
||||
- `GodotFileStorage` 负责底层 key -> 文件读写
|
||||
- `UnifiedSettingsDataRepository` 负责设置节聚合与持久化
|
||||
- `SaveRepository<TSaveData>` 负责存档结构和保存槽位语义
|
||||
- `SaveRepository<TSaveData>` 负责存档结构和保存槽位语义
|
||||
|
||||
不要把 `GodotFileStorage` 本身写成“设置系统”或“存档系统”的 owner。
|
||||
|
||||
|
||||
@ -220,4 +220,4 @@ public List<IntConfig>? Values { get; } = new();
|
||||
1. [/zh-CN/source-generators/index](./index.md)
|
||||
2. [/zh-CN/game/config-system](../game/config-system.md)
|
||||
3. [/zh-CN/source-generators/godot-project-generator](./godot-project-generator.md)
|
||||
4. `GFramework.Godot.SourceGenerators/README.md`
|
||||
4. [`GFramework.Godot.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot.SourceGenerators/README.md)
|
||||
|
||||
@ -189,4 +189,4 @@ private void OnAnyButtonPressed()
|
||||
1. [/zh-CN/source-generators/get-node-generator](./get-node-generator.md)
|
||||
2. [/zh-CN/source-generators/godot-project-generator](./godot-project-generator.md)
|
||||
3. [/zh-CN/godot/ui](../godot/ui.md)
|
||||
4. `GFramework.Godot.SourceGenerators/README.md`
|
||||
4. [`GFramework.Godot.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot.SourceGenerators/README.md)
|
||||
|
||||
@ -195,4 +195,4 @@ finally
|
||||
1. [context-get-generator.md](./context-get-generator.md)
|
||||
2. [logging-generator.md](./logging-generator.md)
|
||||
3. [../core/index.md](../core/index.md)
|
||||
4. `GFramework.Core.SourceGenerators/README.md`
|
||||
4. [`GFramework.Core.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core.SourceGenerators/README.md)
|
||||
|
||||
@ -195,4 +195,4 @@ public override void _Ready()
|
||||
1. [/zh-CN/source-generators/bind-node-signal-generator](./bind-node-signal-generator.md)
|
||||
2. [/zh-CN/source-generators/godot-project-generator](./godot-project-generator.md)
|
||||
3. [/zh-CN/godot/ui](../godot/ui.md)
|
||||
4. `GFramework.Godot.SourceGenerators/README.md`
|
||||
4. [`GFramework.Godot.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot.SourceGenerators/README.md)
|
||||
|
||||
@ -217,4 +217,4 @@ AutoLoad 名称也遵循同样的冲突处理策略。
|
||||
1. [/zh-CN/source-generators/get-node-generator](./get-node-generator.md)
|
||||
2. [/zh-CN/source-generators/bind-node-signal-generator](./bind-node-signal-generator.md)
|
||||
3. [/zh-CN/tutorials/godot-integration](../tutorials/godot-integration.md)
|
||||
4. `GFramework.Godot.SourceGenerators/README.md`
|
||||
4. [`GFramework.Godot.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot.SourceGenerators/README.md)
|
||||
|
||||
@ -101,7 +101,7 @@ GFramework 当前发布的生成器包是:
|
||||
|
||||
## 对应模块入口
|
||||
|
||||
- `GFramework.Core.SourceGenerators/README.md`
|
||||
- `GFramework.Game.SourceGenerators/README.md`
|
||||
- `GFramework.Cqrs.SourceGenerators/README.md`
|
||||
- `GFramework.Godot.SourceGenerators/README.md`
|
||||
- [`GFramework.Core.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core.SourceGenerators/README.md)
|
||||
- [`GFramework.Game.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.SourceGenerators/README.md)
|
||||
- [`GFramework.Cqrs.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs.SourceGenerators/README.md)
|
||||
- [`GFramework.Godot.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot.SourceGenerators/README.md)
|
||||
|
||||
@ -213,4 +213,4 @@ public sealed class DynamicPrioritySystem : IPrioritized
|
||||
1. [context-aware-generator.md](./context-aware-generator.md)
|
||||
2. [context-get-generator.md](./context-get-generator.md)
|
||||
3. [../core/index.md](../core/index.md)
|
||||
4. `GFramework.Core.SourceGenerators/README.md`
|
||||
4. [`GFramework.Core.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core.SourceGenerators/README.md)
|
||||
|
||||
@ -121,7 +121,7 @@ namespace MyGame.Services
|
||||
|
||||
**代码说明**:
|
||||
|
||||
- `Option<T>` 明确表示值可能不存在,避免 NullReferenceException
|
||||
- `Option<T>` 明确表示值可能不存在,避免 NullReferenceException
|
||||
- `Match` 强制处理两种情况,不会遗漏 null 检查
|
||||
- `Map` 和 `Bind` 实现链式转换,代码更简洁
|
||||
- `Filter` 可以安全地过滤值
|
||||
@ -250,7 +250,7 @@ namespace MyGame.Services
|
||||
|
||||
**代码说明**:
|
||||
|
||||
- `Result<T>` 将错误作为值返回,而不是抛出异常
|
||||
- `Result<T>` 将错误作为值返回,而不是抛出异常
|
||||
- `Result.Try` 自动捕获异常并转换为 Result
|
||||
- `Bind` 可以链接多个可能失败的操作
|
||||
- `Match` 强制处理成功和失败两种情况
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user