diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs
index 3391e6ad..4db622cb 100644
--- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs
+++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs
@@ -8,15 +8,12 @@ public sealed partial class CqrsHandlerRegistryGenerator
///
/// 生成程序集级 CQRS handler 注册器源码。
///
- ///
- /// 当前轮次的生成环境,用于决定 runtime 是否提供 CqrsReflectionFallbackAttribute 契约,以及是否需要在输出中发射对应的程序集级元数据。
- ///
///
/// 已整理并排序的 handler 注册描述。方法会据此生成 CqrsHandlerRegistry.g.cs,其中包含直接注册、实现类型反射注册、精确运行时类型查找等分支。
///
///
/// 当前轮次选定的程序集级 reflection fallback 元数据发射策略。
- /// 调用方必须先确保:若该策略包含 fallback handlers,则 已声明支持对应的 fallback attribute 契约;
+ /// 调用方必须先确保:若该策略包含 fallback handlers,则当前 runtime 已声明支持对应的 fallback attribute 契约;
/// 否则应在进入本方法前报告诊断并放弃生成,而不是输出会静默漏注册的半成品注册器。
///
/// 完整的注册器源代码文本。
@@ -28,13 +25,12 @@ public sealed partial class CqrsHandlerRegistryGenerator
/// 该方法本身不报告诊断;“fallback 必需但 runtime 契约缺失”的错误由调用方在进入本方法前处理。
///
private static string GenerateSource(
- GenerationEnvironment generationEnvironment,
IReadOnlyList registrations,
ReflectionFallbackEmissionSpec reflectionFallbackEmission)
{
var sourceShape = CreateGeneratedRegistrySourceShape(registrations);
var builder = new StringBuilder();
- AppendGeneratedSourcePreamble(builder, generationEnvironment, reflectionFallbackEmission);
+ AppendGeneratedSourcePreamble(builder, reflectionFallbackEmission);
AppendGeneratedRegistryType(builder, registrations, sourceShape);
return builder.ToString();
}
@@ -68,11 +64,9 @@ public sealed partial class CqrsHandlerRegistryGenerator
/// 发射生成文件头、nullable 指令以及注册器所需的程序集级元数据特性。
///
/// 生成源码构造器。
- /// 当前轮次的生成环境。
/// 需要写入程序集级 reflection fallback 特性的元数据策略。
private static void AppendGeneratedSourcePreamble(
StringBuilder builder,
- GenerationEnvironment generationEnvironment,
ReflectionFallbackEmissionSpec reflectionFallbackEmission)
{
builder.AppendLine("// ");
@@ -102,10 +96,14 @@ public sealed partial class CqrsHandlerRegistryGenerator
StringBuilder builder,
ReflectionFallbackEmissionSpec reflectionFallbackEmission)
{
- foreach (var attributeEmission in reflectionFallbackEmission.Attributes)
+ for (var index = 0; index < reflectionFallbackEmission.Attributes.Length; index++)
{
- AppendReflectionFallbackAttribute(builder, attributeEmission);
- builder.AppendLine();
+ if (index > 0)
+ {
+ builder.AppendLine();
+ }
+
+ AppendReflectionFallbackAttribute(builder, reflectionFallbackEmission.Attributes[index]);
}
}
diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs
index 688fbc9a..5ebe31b0 100644
--- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs
+++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs
@@ -296,7 +296,7 @@ public sealed partial class CqrsHandlerRegistryGenerator : IIncrementalGenerator
context.AddSource(
HintName,
- GenerateSource(generationEnvironment, registrations, reflectionFallbackEmission));
+ GenerateSource(registrations, reflectionFallbackEmission));
}
///
diff --git a/GFramework.Cqrs.Tests/Cqrs/ReflectionFallbackNotificationContainer.cs b/GFramework.Cqrs.Tests/Cqrs/ReflectionFallbackNotificationContainer.cs
index c80465ce..d54f0dfd 100644
--- a/GFramework.Cqrs.Tests/Cqrs/ReflectionFallbackNotificationContainer.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/ReflectionFallbackNotificationContainer.cs
@@ -13,6 +13,9 @@ internal sealed class ReflectionFallbackNotificationContainer
///
/// 获取可被直接引用、适合通过 元数据补扫的处理器类型。
///
+ ///
+ /// 可被生成注册器直接引用的 fallback 处理器类型,用于验证 runtime 会优先消费 元数据。
+ ///
public static Type DirectFallbackHandlerType => typeof(DirectFallbackGeneratedRegistryNotificationHandler);
///
diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
index 8b5b1b56..78441c5b 100644
--- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
@@ -1876,6 +1876,7 @@ public class CqrsHandlerRegistryGeneratorTests
var generatorErrors = execution.GeneratorDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
+ var generatedSource = execution.GeneratedSources[0].content;
Assert.Multiple(() =>
{
@@ -1885,13 +1886,24 @@ public class CqrsHandlerRegistryGeneratorTests
Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1));
Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs"));
Assert.That(
- execution.GeneratedSources[0].content,
+ generatedSource,
Does.Contain(
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(typeof(global::TestApp.Container.AlphaHandler), typeof(global::TestApp.Container.BetaHandler))]"));
Assert.That(
- execution.GeneratedSources[0].content,
+ generatedSource,
Does.Not.Contain(
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+AlphaHandler\", \"TestApp.Container+BetaHandler\")]"));
+ Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute()"));
+ Assert.That(
+ CountOccurrences(
+ generatedSource,
+ "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute"),
+ Is.EqualTo(1));
+ Assert.That(
+ CountOccurrences(
+ generatedSource,
+ "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\""),
+ Is.Zero);
});
}
@@ -1915,6 +1927,7 @@ public class CqrsHandlerRegistryGeneratorTests
var generatorErrors = execution.GeneratorDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
+ var generatedSource = execution.GeneratedSources[0].content;
Assert.Multiple(() =>
{
@@ -1924,13 +1937,32 @@ public class CqrsHandlerRegistryGeneratorTests
Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1));
Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs"));
Assert.That(
- execution.GeneratedSources[0].content,
+ generatedSource,
Does.Contain(
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(typeof(global::TestApp.Container.AlphaHandler))]"));
Assert.That(
- execution.GeneratedSources[0].content,
+ generatedSource,
Does.Contain(
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+BetaHandler\")]"));
+ Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute()"));
+ Assert.That(
+ CountOccurrences(
+ generatedSource,
+ "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute"),
+ Is.EqualTo(2));
+ Assert.That(
+ CountOccurrences(
+ generatedSource,
+ "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\""),
+ Is.EqualTo(1));
+ Assert.That(
+ generatedSource,
+ Does.Contain(
+ "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(typeof(global::TestApp.Container.AlphaHandler))]" +
+ Environment.NewLine +
+ "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+BetaHandler\")]" +
+ Environment.NewLine +
+ "[assembly: global::GFramework.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))]"));
});
}
@@ -1985,6 +2017,31 @@ public class CqrsHandlerRegistryGeneratorTests
return execution.GeneratedSources[0].content;
}
+ ///
+ /// 统计生成源码中某个固定片段的出现次数,用于锁定程序集级 fallback 特性的发射个数。
+ ///
+ /// 待统计的完整生成源码。
+ /// 需要计数的固定片段。
+ /// 在 中出现的次数。
+ private static int CountOccurrences(string text, string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ throw new ArgumentException("The search value must not be null or empty.", nameof(value));
+
+ var count = 0;
+ var startIndex = 0;
+
+ while (true)
+ {
+ var nextIndex = text.IndexOf(value, startIndex, global::System.StringComparison.Ordinal);
+ if (nextIndex < 0)
+ return count;
+
+ count++;
+ startIndex = nextIndex + value.Length;
+ }
+ }
+
///
/// 运行 CQRS handler registry generator,并返回生成输出及相关诊断。
///
diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
index e6905ac8..64365852 100644
--- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
+++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
@@ -66,6 +66,11 @@ CQRS 迁移与收敛。
- 当本轮 fallback 同时包含可直接引用与仅能按名称恢复的 handlers,且 runtime 同时支持 `Type[]`、`string[]` 和多实例特性时,生成器会拆分输出两段 fallback 元数据
- `GFramework.Cqrs.Tests` 已补充 mixed fallback metadata 回归,锁定 registrar 只对字符串条目执行定向 `Assembly.GetType(...)`
- `GFramework.SourceGenerators.Tests` 已补充 mixed fallback emission 回归,锁定 generator 会输出两个程序集级 fallback 特性实例而不是整体退回字符串
+- `2026-04-29` 已重新执行 `$gframework-pr-review`:
+ - 当前分支对应 `PR #302`,状态为 `OPEN`
+ - latest reviewed commit 当前剩余 `3` 条 open AI review threads:`2` 条 Greptile、`1` 条 CodeRabbit
+ - 本地核对后确认 `dotnet-format` 仍只有 `Restore operation failed` 噪音,没有附带当前仍成立的文件级格式诊断
+ - 已按 review triage 修正 generator source preamble 的多实例 fallback 特性排版、移除死参数,并补强 mixed/direct fallback 发射回归断言与 XML 文档
- 当前主线优先级:
- generator 覆盖面继续扩大
- dispatch/invoker 反射占比继续下降
@@ -116,6 +121,18 @@ CQRS 迁移与收敛。
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
- 结果:通过
- 备注:`18/18` 测试通过;本轮覆盖 mixed fallback metadata 的双特性发射路径
+- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/gframework-pr-review.json`
+ - 结果:通过
+ - 备注:确认当前分支对应 `PR #302`;latest head review 仍有 `3` 条 open AI threads,其中 MegaLinter 仅报告 `dotnet-format` restore failure 噪音
+- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
+ - 结果:通过
+ - 备注:`0 warning / 0 error`
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
+ - 结果:通过
+ - 备注:`18/18` 测试通过;本轮直接覆盖 fallback preamble 排版与特性个数断言收紧
+- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
+ - 结果:通过
+ - 备注:`13/13` 测试通过;本轮确认 mixed fallback metadata 的 registrar 消费路径未回归
## 下一步
diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
index 33aa44fa..cb008db1 100644
--- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
+++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
@@ -32,6 +32,22 @@
- `13/13` passed
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
- `18/18` passed
+- 随后按 `$gframework-pr-review` 重新拉取当前分支 PR 审查数据:
+ - 当前 worktree `feat/cqrs-optimization` 已对应 `PR #302`
+ - latest head commit 仍有 `3` 条 open AI review threads:Greptile 指向 generator preamble 的死参数与多实例 fallback 特性空行,CodeRabbit 指向 mixed/direct fallback 测试断言过宽
+ - MegaLinter 仍只暴露 `dotnet-format` 的 `Restore operation failed`,未给出本地仍成立的格式文件线索,因此按环境噪音处理
+- 本轮已继续收口 `RP-052` 的 follow-up:
+ - 在 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs` 中移除已不再参与判断的 `generationEnvironment` 透传参数
+ - 调整多实例 fallback 特性发射时的换行策略,避免最后一个 fallback 特性与 `CqrsHandlerRegistryAttribute` 之间保留多余空行
+ - 在 `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 中补强 direct/mixed fallback 发射断言,锁定特性实例个数、拒绝空 marker,并确保 mixed 场景的程序集级 preamble 排版稳定
+ - 在 `GFramework.Cqrs.Tests/Cqrs/ReflectionFallbackNotificationContainer.cs` 中为 `DirectFallbackHandlerType` 补齐 `` XML 文档
+- 本轮 review follow-up 验证已通过:
+ - `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
+ - `0 warning / 0 error`
+ - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
+ - `18/18` passed
+ - `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
+ - `13/13` passed
## 2026-04-20