Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,19 @@ jobs:
- name: Build
run: dotnet build GFramework.sln -c Release --no-restore

- name: Pack published modules
run: |
rm -rf ./packages
dotnet pack GFramework.sln \
-c Release \
--no-build \
--no-restore \
-o ./packages \
-p:IncludeSymbols=false
Comment thread
greptile-apps[bot] marked this conversation as resolved.

- name: Validate packed modules
run: bash scripts/validate-packed-modules.sh ./packages

# 运行单元测试,输出TRX格式结果到TestResults目录
# 顺序执行各测试项目,避免并发 dotnet test 进程导致“TRX 全绿但 step 仍返回失败”的假红状态
- name: Test All Projects
Expand Down
36 changes: 1 addition & 35 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,41 +82,7 @@ jobs:
-p:IncludeSymbols=false

- name: Validate packed modules
run: |
set -euo pipefail

expected_packages=(
"GeWuYou.GFramework"
"GeWuYou.GFramework.Core"
"GeWuYou.GFramework.Core.Abstractions"
"GeWuYou.GFramework.Core.SourceGenerators"
"GeWuYou.GFramework.Cqrs"
"GeWuYou.GFramework.Cqrs.Abstractions"
"GeWuYou.GFramework.Cqrs.SourceGenerators"
"GeWuYou.GFramework.Ecs.Arch"
"GeWuYou.GFramework.Ecs.Arch.Abstractions"
"GeWuYou.GFramework.Game"
"GeWuYou.GFramework.Game.Abstractions"
"GeWuYou.GFramework.Game.SourceGenerators"
"GeWuYou.GFramework.Godot"
"GeWuYou.GFramework.Godot.SourceGenerators"
)

mapfile -t actual_packages < <(
find ./packages -maxdepth 1 -type f -name '*.nupkg' -printf '%f\n' \
| sed -E 's/\.[0-9][0-9A-Za-z.-]*\.nupkg$//' \
| sort -u
)

printf '%s\n' "${expected_packages[@]}" | sort > expected-packages.txt
printf '%s\n' "${actual_packages[@]}" | sort > actual-packages.txt

echo "Expected packages:"
cat expected-packages.txt
echo "Actual packages:"
cat actual-packages.txt

diff -u expected-packages.txt actual-packages.txt
run: bash scripts/validate-packed-modules.sh ./packages

- name: Validate runtime-generator package boundaries
run: python3 scripts/validate-runtime-generator-boundaries.py --package-dir ./packages
Expand Down
3 changes: 3 additions & 0 deletions GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<!-- Keep benchmark infrastructure out of the published NuGet package set. -->
<IsPackable>false</IsPackable>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ CQRS 迁移与收敛。

## 当前恢复点

- 恢复点编号:`CQRS-REWRITE-RP-090`
- 恢复点编号:`CQRS-REWRITE-RP-091`
- 当前阶段:`Phase 8`
- 当前 PR 锚点:`PR #326`
- 当前 PR 锚点:`PR #331`
- 当前结论:
- `GFramework.Cqrs` 已完成对外部 `Mediator` 的生产级替代,当前主线已从“是否可替代”转向“仓库内部收口与能力深化顺序”
- `dispatch/invoker` 生成前移已扩展到 request / stream 路径,`RP-077` 已补齐 request invoker provider gate 与 stream gate 对称的 descriptor / descriptor entry runtime 合同回归
Expand All @@ -26,11 +26,15 @@ CQRS 迁移与收敛。
- 当前 `RP-088` 已补齐 request invoker reflection / generated-provider 对照,开始直接量化 dispatcher 预热 generated descriptor 的收益
- 当前 `RP-089` 已补齐 stream invoker reflection / generated-provider 对照,使 generated descriptor 预热收益从 request 扩展到 stream 路径
- 当前 `RP-090` 已收敛 `PR #326` benchmark review:统一 benchmark 最小宿主构建、冻结 GFramework 容器、限制 MediatR 扫描范围,并恢复 request startup cold-start 对照
- `ai-plan` active 入口现以 `PR #326` 和 `RP-090` 为唯一权威恢复锚点;`PR #323`、`PR #307` 与其他更早阶段细节均以下方归档或说明为准
- 当前 `RP-091` 已把 benchmark 项目发布面隔离与包清单校验前移到 PR:`GFramework.Cqrs.Benchmarks` 明确保持不可打包,`publish.yml` 与 `ci.yml` 复用同一份 packed-modules 校验脚本
- `ai-plan` active 入口现以 `RP-091` 为最新恢复锚点;`PR #331`、`PR #326`、`PR #323`、`PR #307` 与其他更早阶段细节均以下方归档或说明为准

## 当前活跃事实

- 当前分支对应 `PR #326`,状态为 `OPEN`
- 当前分支为 `fix/package-validation-guard`
- `GFramework.Cqrs.Benchmarks` 作为 benchmark 基础设施项目,必须持续排除在 NuGet / GitHub Packages 发布集合之外
- 发布工作流已有 packed modules 校验,但 PR 工作流此前没有等价的 solution pack 产物名单校验
- 本地 `dotnet pack GFramework.sln -c Release --no-restore -o <temp-dir>` 当前只产出 14 个预期包,未复现 benchmark `.nupkg`
- latest-head review 现仍有少量 open thread,但本地复核后,仍成立的问题已收敛到 benchmark 对照公平性、workflow 输入安全性与 active 文档压缩
- benchmark 场景现统一通过 `BenchmarkHostFactory` 构建最小宿主:GFramework 侧在 runtime 分发前显式 `Freeze()` 容器,MediatR 侧只扫描当前场景需要的 handler / behavior 类型
- `RequestStartupBenchmarks` 已恢复 `ColdStart_GFrameworkCqrs` 结果产出,不再命中 `No CQRS request handler registered`
Expand All @@ -41,12 +45,23 @@ CQRS 迁移与收敛。
## 当前风险

- 顶层 `GFramework.sln` / `GFramework.csproj` 在 WSL 下仍可能受 Windows NuGet fallback 配置影响,完整 solution 级验证成本高于模块级验证
- 若后续新增 benchmark / example / tooling 项目但未同步校验发布面,solution 级 `dotnet pack` 仍可能在 tag 发布前才暴露异常包
- `RequestStartupBenchmarks` 为了量化真正的单次 cold-start,引入了 `InvocationCount=1` / `UnrollFactor=1` 的专用 job;该配置会触发 BenchmarkDotNet 的 `MinIterationTime` 提示,后续若要做稳定基线比较,还需要决定是否引入批量外层循环或自定义 cold-start harness
- 仓库内部仍保留旧 `Command` / `Query` API、`LegacyICqrsRuntime` alias 与部分历史命名语义,后续若不继续分批收口,容易混淆“对外替代已完成”与“内部收口未完成”
- 若继续扩大 generated invoker 覆盖面,需要持续区分“可静态表达的合同”与 `PreciseReflectedRegistrationSpec` 等仍需保守回退的场景

## 最近权威验证

- `dotnet pack GFramework.sln -c Release --no-restore -o /tmp/gframework-pack-validation -p:IncludeSymbols=false`
- 结果:通过
- 备注:当前本地产物仅包含 14 个预期发布包,未生成 `GFramework.Cqrs.Benchmarks.*.nupkg`
- `bash scripts/validate-packed-modules.sh /tmp/gframework-pack-validation`
- 结果:通过
- 备注:共享脚本确认 actual package set 与预期 14 个发布包完全一致
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `python3 scripts/license-header.py --check`
- 结果:通过
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release -- --filter "*RequestStartupBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
- 结果:通过
- 备注:`ColdStart_GFrameworkCqrs` 已恢复出数,最新本地输出约 `220-292 us`,MediatR 对照约 `575-616 us`;当前仅剩 BenchmarkDotNet 对单次 cold-start 场景的 `MinIterationTime` 提示
Expand All @@ -55,7 +70,7 @@ CQRS 迁移与收敛。
- 备注:用于验证本轮 request invoker / pipeline / stream invoker 调整与 benchmark workflow 改动后的 Release 编译结果
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output <temporary-json-output>`
- 结果:通过
- 备注:确认当前分支对应 `PR #326`,本轮剩余 open AI feedback 以 workflow 输入安全、benchmark 对照公平性与 active 文档压缩为主
- 备注:确认当前分支对应 `PR #331`,本轮 latest-head open AI feedback 已收敛到 `dotnet pack --no-build`、共享包校验脚本跨平台兼容性与 active 文档 PR 锚点同步
- `python3 scripts/license-header.py --check`
- 结果:通过
- 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
Expand All @@ -64,9 +79,9 @@ CQRS 迁移与收敛。

## 下一推荐步骤

1. 重新运行 `$gframework-pr-review`,确认本轮 workflow / benchmark / active 文档修复是否已消化当前 latest-head open threads
2. 若 `PR #326` 仍剩基准语义类反馈,优先判断它们属于真实对照偏差还是有意保留的 benchmark 设计取舍
3. 若需要在 CI 中手动复核 benchmark,继续使用 workflow 的 `benchmark_filter` 输入按场景筛选,避免默认运行整个 benchmark 矩阵
1. 运行 `dotnet pack` 与新的 `scripts/validate-packed-modules.sh`,确认本轮共享校验脚本与 PR workflow 步骤在本地一致通过
2. 运行受影响的 Release build / 头部校验,确认 workflow 与脚本改动未引入新的命名、文件头或 shell 语法问题
3. 创建修复 PR 时,将重点放在“发布面保护前移到 PR”而不是“扩充 expected package 列表”

## 活跃文档

Expand Down
31 changes: 31 additions & 0 deletions ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,37 @@

## 2026-05-06

### 阶段:PR #331 review 收尾补丁(CQRS-REWRITE-RP-091)

- 使用 `$gframework-pr-review` 拉取当前分支 `fix/package-validation-guard` 对应的 `PR #331` latest-head review 后,主线程只保留本地复核仍成立的问题:
- `.github/workflows/ci.yml` 的 `dotnet pack` 步骤缺少 `--no-build`,会在已完成 solution `Build` 后重复编译整仓库
- `scripts/validate-packed-modules.sh` 使用 GNU `find -printf`,在 macOS / BSD `find` 下无法运行
- `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` 的 active PR 锚点仍写成 `待创建`,与当前公开 PR 状态不一致
- 本轮决策:
- `ci.yml` 的 pack 步骤显式补上 `--no-build`,使其与前置 `Build` 步骤形成单次编译链路
- 共享包校验脚本改为使用 `find ... -exec basename {} \;`,避免依赖 GNU-only 选项
- active tracking 同步到 `PR #331`,并把这轮 PR review 的剩余问题描述更新为当前已核验的真实范围
- 预期结果:
- PR workflow 的 pack 阶段不再对同一 solution 重复编译
- `validate-packed-modules.sh` 可在 GNU / BSD `find` 环境下保持相同行为
- `cqrs-rewrite` active 恢复入口继续与当前公开 PR 保持一致

### 阶段:benchmark 发布面隔离与包清单校验前移(CQRS-REWRITE-RP-091)

- 针对 tag 发布中出现的 `GFramework.Cqrs.Benchmarks` 异常包名单,本轮先复核 benchmark 项目与 solution pack 的本地事实:
- `GFramework.Cqrs.Benchmarks.csproj` 已包含 `IsPackable=false` 与 `GeneratePackageOnBuild=false`
- 本地执行 `dotnet pack GFramework.sln -c Release --no-restore -o /tmp/gframework-sln-pack-probe -p:IncludeSymbols=false` 时,产物仅包含 14 个预期发布包
- 因此本轮不把 benchmark 包加入发布白名单,而是把“benchmark 永不发布”与“PR 前置完整包名单校验”同时固化
- 本轮决策:
- 为 `GFramework.Cqrs.Benchmarks` 补充注释,明确其 benchmark-only 的发布边界
- 新增 `scripts/validate-packed-modules.sh`,集中维护预期包集合与实际 `.nupkg` diff 逻辑
- `publish.yml` 改为调用共享脚本,避免发布工作流与 PR 工作流各自维护一份包名单
- `ci.yml` 新增 solution `dotnet pack` 与 packed modules 校验,把异常发布包从 tag 发布前移到普通 PR 阶段
- 预期结果:
- benchmark / example / tooling 一类新项目若意外进入发布面,会先在 PR 失败,而不是等到 tag 发布
- 发布与 PR 使用同一份包名单规则,减少后续名单漂移
- `GFramework.Cqrs.Benchmarks` 继续只服务于 benchmark workflow,不进入 NuGet / GitHub Packages

### 阶段:benchmark 对照宿主收敛与 startup cold-start 恢复(CQRS-REWRITE-RP-090)

- 使用 `$gframework-pr-review` 拉取 `PR #326` latest-head review 后,主线程确认仍有效的 benchmark 反馈集中在三类问题:
Expand Down
51 changes: 51 additions & 0 deletions scripts/validate-packed-modules.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail

package_dir="${1:-./packages}"

if [ ! -d "$package_dir" ]; then
echo "Package directory not found: $package_dir" >&2
exit 1
fi

expected_packages=(
"GeWuYou.GFramework"
"GeWuYou.GFramework.Core"
"GeWuYou.GFramework.Core.Abstractions"
"GeWuYou.GFramework.Core.SourceGenerators"
"GeWuYou.GFramework.Cqrs"
"GeWuYou.GFramework.Cqrs.Abstractions"
"GeWuYou.GFramework.Cqrs.SourceGenerators"
"GeWuYou.GFramework.Ecs.Arch"
"GeWuYou.GFramework.Ecs.Arch.Abstractions"
"GeWuYou.GFramework.Game"
"GeWuYou.GFramework.Game.Abstractions"
"GeWuYou.GFramework.Game.SourceGenerators"
"GeWuYou.GFramework.Godot"
"GeWuYou.GFramework.Godot.SourceGenerators"
)

work_dir="$(mktemp -d)"
trap 'rm -rf "$work_dir"' EXIT

expected_file="$work_dir/expected-packages.txt"
actual_file="$work_dir/actual-packages.txt"

mapfile -t actual_packages < <(
find "$package_dir" -maxdepth 1 -type f -name '*.nupkg' -exec basename {} \; \
| sed -E 's/\.[0-9][0-9A-Za-z.-]*\.nupkg$//' \
| sort -u
)
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Comment thread
greptile-apps[bot] marked this conversation as resolved.

printf '%s\n' "${expected_packages[@]}" | sort > "$expected_file"
printf '%s\n' "${actual_packages[@]}" > "$actual_file"

echo "Expected packages:"
cat "$expected_file"
echo "Actual packages:"
cat "$actual_file"

diff -u "$expected_file" "$actual_file"
Loading