Compare commits

...

106 Commits
v0.2.0 ... main

Author SHA1 Message Date
gewuyou
7ca21af92d
Merge pull request #341 from GeWuYou/feat/cqrs-optimization
Feat/cqrs optimization
2026-05-08 16:12:20 +08:00
gewuyou
769d036434 fix(cqrs): 收口PR341剩余review尾项
- 修复 request faulted ValueTask 回归测试对 pipeline 探测顺序的隐式依赖,补齐 HasRegistration 与 GetAll 的防御性 mock

- 更新 cqrs-rewrite tracking 与 trace,记录 PR #341 latest-head review 的 stale thread 复核结论与本轮验证结果
2026-05-08 15:06:24 +08:00
gewuyou
9bd8c34693 fix(cqrs): 收口PR审查遗留问题
- 修复 benchmark 宿主误激活同程序集其他 generated registry 的接线路径,收窄服务索引与 descriptor 基线

- 恢复 CqrsDispatcher.SendAsync 的 faulted ValueTask 失败语义,并补充相关回归测试

- 补充 legacy runtime alias 的防守式类型检查、stream lifetime 注释与 cqrs-rewrite 恢复文档验证记录
2026-05-08 14:10:06 +08:00
gewuyou
39ac61c095 fix(cqrs): 补齐流式生命周期基准矩阵
- 新增 stream handler 的 Singleton 和 Transient 生命周期 benchmark,并沿用 generated-provider 宿主接线

- 更新 CQRS benchmark README 与 active ai-plan 恢复点,记录 RP-108 的验证结果和下一步建议
2026-05-08 13:03:00 +08:00
gewuyou
24462b0035 perf(cqrs): 收口默认流式基准宿主
- 新增默认 stream benchmark 的 handwritten generated registry,并通过真实程序集注册路径接上 generated stream invoker provider

- 更新 StreamingBenchmarks 宿主接线、README 与 RP-107 recovery 文档,统一 request、pipeline、stream 默认宿主口径

- 更新 gframework-boot 与 gframework-batch-boot 技能,改为以上下文预算接近约 80% 为默认优先停止信号
2026-05-08 12:47:24 +08:00
gewuyou
c82e981b7e perf(cqrs): 收口请求管线基准宿主
- 新增 request pipeline benchmark 的 handwritten generated request registry,并通过真实程序集注册路径接上 generated invoker provider

- 更新 RequestPipelineBenchmarks 宿主接线与 benchmark README,统一默认 request 与 pipeline 场景的 generated-provider 口径

- 更新 CQRS 迁移 tracking 与 trace,记录 RP-106 的基线、验证结果与下一恢复点
2026-05-08 12:38:18 +08:00
gewuyou
d9547dae4b perf(cqrs): 收口默认请求基准宿主
- 新增 handwritten generated request registry,并让默认 RequestBenchmarks 通过真实程序集注册路径接上 generated invoker provider

- 补齐 benchmark 最小宿主所需的 CQRS runtime、registrar 与 registration service 基础设施接线

- 更新 CQRS 迁移 tracking 与 trace,记录 RP-105 的 benchmark 结论和当前恢复点
2026-05-08 12:23:05 +08:00
gewuyou
120a1487f5 perf(cqrs): 收口请求热路径常量开销
- 优化 CqrsDispatcher.SendAsync 的 direct-return ValueTask 路径,移除 dispatcher 自身的异步状态机开销

- 引入 MicrosoftDiContainer 冻结后服务键索引,收敛 HasRegistration(Type) 的重复描述符扫描

- 更新 cqrs-rewrite active tracking 与 trace,记录 RP-104 的基线、验证结果与下一批建议
2026-05-08 11:38:27 +08:00
gewuyou
4d6dbba6a0
Merge pull request #340 from GeWuYou/feat/cqrs-optimization
Feat/cqrs optimization
2026-05-08 11:13:33 +08:00
gewuyou
32eeb41f29 fix(cqrs): 修复 HasRegistration 评审回归
- 修复 HasRegistration(Type) 的服务键判定,避免将仅按具体类型注册的行为误判为接口已注册

- 补充 strict mock 场景与 HasRegistration 回归测试,并修复 PR #340 暴露的 stream context validation 失败

- 更新 IoC 与 benchmark 文档注释,同步 cqrs-rewrite tracking/trace 到 PR #340 / RP-103
2026-05-08 10:54:37 +08:00
gewuyou
5da4a5893b perf(cqrs): 收紧性能回归门槛并忽略基准产物
- 更新 BenchmarkDotNet 生成目录忽略规则,避免本地基准产物污染工作树

- 补充 CQRS benchmark 回归要求与性能目标,要求相关改动后复跑 request 基准

- 更新 cqrs-rewrite 跟踪文档并记录最新 request 基准结果
2026-05-08 10:30:24 +08:00
gewuyou
18018966f9 perf(cqrs): 优化请求分发热路径并补充 Mediator 对照基准
- 优化 dispatcher 在零 pipeline 场景下跳过空行为解析,减少请求热路径分配

- 修复 MicrosoftDiContainer 热路径的无效 debug 字符串构造,并新增非激活注册检测回归测试

- 新增基于 NuGet 的 Mediator 对照基准并更新 CQRS 重写跟踪文档
2026-05-08 09:41:27 +08:00
gewuyou
5dc2dd25b9
Merge pull request #339 from GeWuYou/feat/cqrs-optimization
feat(cqrs): 补齐流式管道行为接缝
2026-05-08 09:08:37 +08:00
gewuyou
e44c56fb46 fix(cqrs): 收口 PR339 流式管道评审问题
- 修复 MicrosoftDiContainer 中 request 与 stream 行为注册逻辑的重复实现并统一校验路径

- 补充流式管道注册入口与 continuation 缓存的 XML 契约说明,明确并发与冻结前调用约束

- 更新 cqrs-rewrite 跟踪文档并修正 ICqrsRequestInvokerProvider 的 XML 缩进格式问题
2026-05-08 08:49:19 +08:00
gewuyou
aebf1e974d feat(cqrs): 补齐流式管道行为接缝
- 新增 stream pipeline 契约、dispatcher executor 缓存与 generated invoker 兼容路径

- 补充 Architecture 与 IOC 的流式管道注册入口及对应回归测试

- 更新 CQRS 文档和 cqrs-rewrite 的 active tracking/trace
2026-05-08 08:20:48 +08:00
gewuyou
02a60df718
Merge pull request #335 from GeWuYou/dependabot/nuget/Meziantou.Analyzer-3.0.72
Bump Meziantou.Analyzer from 3.0.60 to 3.0.72
2026-05-07 22:00:58 +08:00
gewuyou
77820da820
Merge pull request #336 from GeWuYou/dependabot/nuget/Meziantou.Polyfill-1.0.123
Bump Meziantou.Polyfill from 1.0.121 to 1.0.123
2026-05-07 22:00:48 +08:00
gewuyou
55639c559c
Merge pull request #337 from GeWuYou/dependabot/nuget/GFramework.Cqrs.Benchmarks/Microsoft.Extensions.Logging-10.0.7
Bump Microsoft.Extensions.Logging from 10.0.0 to 10.0.7
2026-05-07 22:00:35 +08:00
gewuyou
042b74473f
Merge pull request #338 from GeWuYou/dependabot/nuget/GFramework.Core.Tests/multi-6f1e76e95e
Bump NUnit from 4.5.1 to 4.6.0
2026-05-07 21:59:59 +08:00
dependabot[bot]
55c2a1ae69
Bump NUnit from 4.5.1 to 4.6.0
---
updated-dependencies:
- dependency-name: NUnit
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: NUnit
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: NUnit
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: NUnit
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: NUnit
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: NUnit
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: NUnit
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-07 13:14:14 +00:00
dependabot[bot]
debc9f27ac
Bump Microsoft.Extensions.Logging from 10.0.0 to 10.0.7
---
updated-dependencies:
- dependency-name: Microsoft.Extensions.Logging
  dependency-version: 10.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-07 13:10:38 +00:00
dependabot[bot]
8f6e6e121e
Bump Meziantou.Polyfill from 1.0.121 to 1.0.123
---
updated-dependencies:
- dependency-name: Meziantou.Polyfill
  dependency-version: 1.0.123
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-07 13:09:51 +00:00
dependabot[bot]
d010026448
Bump Meziantou.Analyzer from 3.0.60 to 3.0.72
---
updated-dependencies:
- dependency-name: Meziantou.Analyzer
  dependency-version: 3.0.72
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-07 13:08:44 +00:00
gewuyou
54b79d99d3
Merge pull request #334 from GeWuYou/feat/cqrs-optimization
Feat/Implement CQRS runtime integration for legacy compatibility
2026-05-07 20:47:08 +08:00
gewuyou
ffb0a8aff5 fix(core): 收窄 legacy bridge 上下文回退异常边界
- 修复 LegacyCqrsDispatchHelper 仅在上下文缺失时回退,避免吞掉真实 InvalidOperationException

- 补充 CommandExecutor 与 QueryExecutor 相关回归测试,覆盖 fallback 与异常冒泡语义

- 更新 cqrs-rewrite 跟踪与追踪文档,记录 PR #334 本轮复核与验证结果
2026-05-07 20:35:47 +08:00
gewuyou
44d1a89a0b test(core): 补强 legacy bridge 上下文断言
- 补充 AsyncQueryExecutor 与 CommandExecutor bridge 测试的上下文保留断言

- 优化 RecordingCqrsRuntime 的 bridge 执行模拟与响应类型诊断

- 更新 cqrs-rewrite active tracking 与 trace 的 RP-097 验证记录
2026-05-07 20:17:46 +08:00
gewuyou
cca413042f chore(cqrs-rewrite): 同步PR334评审复核状态
- 更新 active tracking 与 trace 到 RP-096,记录 latest-head review 的最新权威结论

- 补充 PR #334 当前 stale open thread、CI 测试与 MegaLinter 噪音的本地复核结果
2026-05-07 19:52:14 +08:00
gewuyou
dc3bd3744e fix(core): 收口 legacy bridge 同步评审问题
- 修复 legacy 同步 bridge 的 runtime 等待方式,统一通过共享 helper 隔离同步上下文并收口重复 dispatch-context 解析逻辑

- 补充 legacy async command bridge 的取消可见性,并更新 ICqrsRuntime 与相关入口的契约说明

- 新增 bridge 回归测试并更新 cqrs-rewrite active tracking,覆盖同步上下文隔离、测试容器释放与取消语义
2026-05-07 19:00:49 +08:00
gewuyou
6056159866 fix(core): 收口 legacy cqrs bridge 评审问题
- 修复 legacy bridge 测试装配与清理流程,改用 InternalsVisibleTo 和显式 handler 注册,补齐共享计数器重置与生命周期说明

- 优化 CommandExecutor、QueryExecutor 与相关模块的 runtime 契约,补充 XML 文档、nullable 注解和显式依赖解析

- 更新 legacy 异步 bridge 的取消语义、兼容文档回退边界以及 cqrs-rewrite active tracking/trace
2026-05-07 17:54:05 +08:00
gewuyou
d7293aa475 refactor(core): 统一旧版命令查询到Cqrs运行时
- 重构 Core 兼容命令查询入口,使 legacy SendCommand/SendQuery 通过内部 bridge request 复用统一 CQRS runtime

- 新增 legacy bridge handler 与真实启动路径回归测试,验证默认架构初始化会自动接入统一 pipeline

- 更新 Core 与 CQRS 文档及 cqrs-rewrite 跟踪,记录 Mediator 尚未吸收的能力差距与后续收口方向
2026-05-07 17:20:14 +08:00
gewuyou
017e689abd feat(cqrs): 补齐请求生命周期基准矩阵
- 新增 request handler Singleton 与 Transient 生命周期 benchmark,并说明 Scoped 对照的宿主前置条件

- 更新 benchmark README,补充当前覆盖范围与后续扩展方向

- 更新 cqrs-rewrite active tracking 与 trace,记录 RP-092 验证结果和沙箱外 benchmark 权威结论
2026-05-07 14:20:50 +08:00
gewuyou
2c58d8b69e
Merge pull request #333 from GeWuYou/refactor/single-context-priority
docs(ai-plan): 归档 single-context-priority 主题
2026-05-07 13:24:46 +08:00
GeWuYou
14cd1fc9a0 chore(benchmark): 删除错误的任务 2026-05-07 13:08:55 +08:00
GeWuYou
577c89fdf3 chore(benchmark): 归档已完成任务,删除错误的任务 2026-05-07 12:44:57 +08:00
gewuyou
a692190a77 docs(ai-plan): 归档 single-context-priority 主题
- 更新 public index,只保留仍处于活跃状态的 topic 与分支映射

- 归档 single-context-priority 主题目录到 public archive

- 补充 ai-plan-governance 跟踪与 trace,记录本次归档校正与验证结果
2026-05-07 12:23:18 +08:00
gewuyou
c3df2b2c96
Merge pull request #332 from GeWuYou/refactor/single-context-priority
Refactor/single context priority
2026-05-07 11:34:50 +08:00
gewuyou
ee8b6a4deb fix(core): 修复上下文销毁解绑与并发一致性
- 修复 GameContext 的别名字典与当前活动上下文同步边界,避免解绑与读取路径出现状态漂移
- 修复 Architecture.Destroy() 缺少全局解绑的问题,并补充相关生命周期 XML 文档
- 更新回归测试、CQRS 注册断言与 single-context-priority 跟踪记录
2026-05-07 10:43:07 +08:00
gewuyou
ff04a4fbad fix(core): 补齐架构销毁后的上下文解绑
- 修复 Architecture 销毁后 GameContext 仍保留活动上下文的问题

- 补充生命周期回归测试并验证失败初始化后的解绑路径

- 收口生成器文档中的多架构表述并更新 ai-plan 追踪
2026-05-07 10:03:16 +08:00
gewuyou
e3fa0db992 refactor(core): 收敛单活动上下文与预冻结查询
- 收敛 GameContext 为单活动上下文模型并保留类型别名兼容查找

- 统一 MicrosoftDiContainer 预冻结实例读取路径并补充 CQRS 注册阶段提示

- 更新 Core 测试、上下文文档与 ai-plan 追踪记录
2026-05-07 08:58:09 +08:00
gewuyou
c2d22285ed
Merge pull request #331 from GeWuYou/fix/package-validation-guard
fix(release): 前移发布包清单校验
2026-05-06 21:34:59 +08:00
gewuyou
e3d6aa5111 fix(release): 修复发布校验链路的审查遗留问题
- 修复 PR workflow 中 dotnet pack 重复构建整个 solution 的问题

- 优化 packed modules 校验脚本的 find 实现以兼容 BSD 环境

- 更新 cqrs-rewrite 活跃跟踪与追踪文档中的当前 PR 锚点和审查结论
2026-05-06 21:27:21 +08:00
gewuyou
30ddb841a9 fix(release): 前移发布包清单校验
- 修复 benchmark 项目误入发布面的风险,明确 GFramework.Cqrs.Benchmarks 保持不可打包。

- 新增共享 packed modules 校验脚本,并让 publish 与 CI 工作流复用同一份发布包名单规则。

- 更新 CQRS active tracking 与 trace,记录本轮发布校验前移的恢复点与验证结果。
2026-05-06 21:12:42 +08:00
gewuyou
c65c131d6a
Merge pull request #330 from GeWuYou/fix/microsoft-di-container-disposal
fix(core): 修复容器释放与基准资源泄漏
2026-05-06 20:47:32 +08:00
gewuyou
f0a2978882 fix(core): 修复容器并发释放重复销毁锁
- 修复 MicrosoftDiContainer 在并发 Dispose 场景下可能重复执行底层读写锁销毁的问题

- 补充 IocContainerLifetimeTests 回归用例以覆盖并发释放时的单次锁销毁约束

- 更新 microsoft-di-container-disposal 追踪文档记录剩余 PR review 处理结果
2026-05-06 20:39:38 +08:00
gewuyou
3233151207 fix(ioc): 修复容器释放竞态与清理路径
- 修复 MicrosoftDiContainer 在等待线程与并发 Dispose 场景下泄露底层锁异常的问题
- 更新 IIocContainer 释放契约文档并移除 Clear 中不可达的 provider 释放逻辑
- 新增 benchmark cleanup helper、并发释放回归测试与 ai-plan 恢复入口
2026-05-06 20:23:16 +08:00
gewuyou
0ec8aa076b fix(core): 修复容器释放与基准资源泄漏
- 修复 MicrosoftDiContainer 的 IDisposable 释放逻辑、根 ServiceProvider 清理与释放后访问保护
- 更新 CQRS benchmarks 的容器 cleanup,并补齐 RequestStartupBenchmarks 的冷启动容器释放路径
- 补充 Core 容器生命周期回归测试并归档 issue 327 的 ai-plan topic
2026-05-06 19:08:48 +08:00
gewuyou
588800bb7b
Merge pull request #329 from GeWuYou/chore/archive-completed-ai-plan-topics
chore(ai-plan): 归档已完成专题
2026-05-06 17:22:16 +08:00
gewuyou
ee41206965 chore(ai-plan): 归档已完成专题
- 更新 ai-plan 公共索引,移除 semantic-release-versioning、runtime-generator-boundary 和 github-issue-review-skill 的活跃入口与分支映射
- 归档 三个已完成 topic 的 tracking 与 trace 文档到 ai-plan/public/archive/ 下
2026-05-06 16:59:35 +08:00
gewuyou
db89918333
Merge pull request #328 from GeWuYou/feat/github-issue-review-skill
feat(skills): 新增 GitHub issue 分诊 skill
2026-05-06 16:51:02 +08:00
gewuyou
f25ccccad2 fix(skills): 修复 issue review skill 评审问题
- 修复 issue-review 脚本的代理回退、GitHub Token 认证与 JSON 输出契约

- 调整非 bug issue 的澄清判定并补充 docs、feature 分诊回归测试

- 更新 skill 示例占位符与 ai-plan 跟踪记录,收敛 PR #328 follow-up
2026-05-06 16:25:29 +08:00
gewuyou
ab9829044f feat(skills): 新增 GitHub issue 分诊 skill
- 新增 gframework-issue-review skill,支持抓取 issue 元数据、评论、timeline 与分诊摘要。

- 补充 JSON 输出、唯一 open issue 自动解析与 WSL Linux git 绑定兼容处理。

- 更新 ai-plan 恢复入口并增加脚本级测试与验证记录。
2026-05-06 15:40:48 +08:00
gewuyou
109bce6e9e
Merge pull request #326 from GeWuYou/feat/cqrs-optimization
Test/Add comprehensive CQRS benchmarking suite with reflection and generated invoker paths
2026-05-06 14:29:06 +08:00
gewuyou
6d619b9a1f fix(cqrs): 收敛 benchmark review 收尾问题
- 修复 benchmark workflow 过滤器输入的 shell 注入风险

- 统一 request 与 stream invoker 基准中 MediatR handler 的生命周期基线

- 更新 request pipeline benchmark 的缓存清理与空行为类型声明

- 压缩 cqrs-rewrite active 跟踪与 trace,记录本轮 PR review 收尾结论
2026-05-06 12:57:56 +08:00
gewuyou
2cb6216d05 fix(cqrs): 修复 benchmark 对照宿主与冷启动基线
- 新增 BenchmarkHostFactory 统一 benchmark 最小宿主构建,并限制 MediatR 扫描到当前场景所需类型

- 修复 GFramework benchmark 容器未冻结导致的首次 handler 解析缺口,恢复 RequestStartupBenchmarks 冷启动结果

- 优化 request、pipeline、notification、stream 与 invoker benchmark 的生命周期对齐,减少无关程序集扫描噪音

- 更新 cqrs-rewrite 跟踪与追踪文档,记录 PR #326 benchmark review 收敛、根因和验证结果
2026-05-06 12:09:20 +08:00
gewuyou
f71791ae98 ci(cqrs): 新增手动 benchmark 工作流
- 新增仅支持 workflow_dispatch 的 Benchmark workflow,默认只验证 benchmark 项目 Release build
- 补充可选 benchmark_filter 输入与 BenchmarkDotNet 工件上传,支持按场景手动执行基准测试
- 更新 cqrs-rewrite 跟踪与 trace,记录手动 benchmark workflow 的用途与当前 startup benchmark 残留风险
2026-05-06 11:48:15 +08:00
gewuyou
2ac02c1a6f fix(cqrs): 收敛 benchmark review 修复
- 修复 RequestStartupBenchmarks 的 baseline 分组、初始化阶段对齐与 MediatR 重复注册问题
- 新增共享 dispatcher cache helper,并统一 benchmark 宿主的 MediatR logging/license 过滤配置
- 更新 cqrs-rewrite 跟踪与 trace,记录 PR #326 锚点、验证去重和 startup benchmark 的残留运行风险
2026-05-06 11:07:33 +08:00
gewuyou
449eeb9606 feat(cqrs): 补齐 stream invoker 基准对照
- 新增 stream generated invoker benchmark 与手写 registry,对照 reflection runtime、generated runtime 和 MediatR 的完整枚举开销

- 更新 benchmark README,补充 generated stream invoker provider 的场景说明与后续扩展方向

- 更新 cqrs-rewrite 跟踪与 trace,记录 RP-089 的基线、验证结果和下一批建议
2026-05-06 09:46:52 +08:00
gewuyou
c01abac06e
Merge pull request #325 from GeWuYou/feat/ai-first-config
fix(game-config): 收紧开放对象关键字边界
2026-05-06 09:40:08 +08:00
gewuyou
6e1eaf8f5c test(cqrs): 补充请求调用器生成路径基准
- 新增 request reflection 与 generated invoker provider 的 steady-state 对照基准

- 引入 handwritten generated registry/provider 以走通真实 registrar 与 dispatcher 预热链路

- 更新 benchmark README 与 cqrs-rewrite RP-088 跟踪记录
2026-05-06 09:36:48 +08:00
gewuyou
e0bbf13d88 test(cqrs): 补充请求启动阶段基准
- 新增 request initialization 与 cold-start 基准并对齐当前 runtime 启动口径

- 通过清理 dispatcher 静态缓存隔离 GFramework.Cqrs 首次分发测量结果

- 更新 benchmark README 与 cqrs-rewrite RP-087 跟踪记录
2026-05-06 09:30:17 +08:00
gewuyou
f776d09f68 fix(ai-first-config): 收口开放对象评审跟进
- 修复 Runtime、Generator 与 Tooling 中开放对象关键字校验的不可达 additionalProperties 分支

- 补充 Tooling 对 additionalProperties false 的正向回归测试

- 更新游戏配置接入文档与 ai-plan 跟踪,记录 PR #325 的核验结论和验证结果
2026-05-06 09:25:59 +08:00
gewuyou
a8f98e467d test(cqrs): 补充请求管道数量矩阵基准
- 新增 request pipeline 0/1/4 数量矩阵基准并保持 GFramework.Cqrs 与 MediatR 对照

- 更新 benchmark README 说明当前场景覆盖与后续扩展方向

- 补充 cqrs-rewrite 跟踪与 trace 的 RP-086 恢复点和验证记录
2026-05-06 09:23:07 +08:00
gewuyou
e6f98cb4af test(cqrs): 补充流式请求基准场景
- 新增 StreamingBenchmarks 并对齐 baseline、GFramework.Cqrs 与 MediatR 的完整枚举对照

- 更新 benchmark README 与 CQRS ai-plan 恢复点,记录 stream 场景落地
2026-05-06 09:14:33 +08:00
gewuyou
96729ddcf1 test(cqrs): 补充基准与生成器回归基础设施
- 新增独立的 GFramework.Cqrs.Benchmarks 项目并引入 request、notification 对比场景

- 补充 request 与 stream invoker provider 的 mixed direct/reflected 顺序回归测试

- 更新 solution、meta-package 排除规则与 CQRS ai-plan 恢复点
2026-05-06 08:57:59 +08:00
gewuyou
cb6dd8a510 fix(game-config): 收紧开放对象关键字边界
- 修复 Runtime、Generator 与 Tooling 对 patternProperties、propertyNames、unevaluatedProperties 的静默接受风险

- 补充三端对称回归测试与 reader-facing 文档边界说明

- 更新 ai-plan 恢复点、验证记录与下一步指针
2026-05-06 08:47:42 +08:00
gewuyou
a8c6c11e9e
Merge pull request #324 from GeWuYou/fix/runtime-generator-boundary
fix(game): 剥离运行时模块对生成器依赖
2026-05-05 13:14:24 +08:00
gewuyou
d9ceb83c2c fix(runtime-generator-boundary): 修复边界校验回归问题
- 修复 runtime-generator 边界校验对独立与带参数 attribute 的漏报问题,并过滤注释示例误报

- 新增 Python 回归测试覆盖独立、限定名、多 attribute 与文档示例场景

- 更新贡献文档与 ai-plan 记录,移除面向用户文档中的内部治理段落并补充验证结果
2026-05-05 13:06:18 +08:00
gewuyou
7288114e33 fix(game): 剥离运行时模块对生成器依赖
- 修复 GFramework.Game 对 SourceGenerators.Abstractions 的项目引用并移除未使用的枚举生成 attribute

- 新增 runtime-generator 边界校验脚本并接入 CI 与发布打包校验

- 更新 AGENTS、贡献文档与 ai-plan 跟踪,明确运行时模块禁止依赖生成器能力
2026-05-05 12:39:10 +08:00
gewuyou
c69942d66e
Merge pull request #323 from GeWuYou/feat/cqrs-optimization
Feat/cqrs optimization
2026-05-04 21:03:25 +08:00
gewuyou
212d5b1cce docs(cqrs): 同步 PR 恢复锚点
- 更新 CQRS active tracking 的当前 PR 锚点为 PR #323
- 补充 PR review 收敛 trace 与最新验证结果
2026-05-04 20:56:19 +08:00
gewuyou
b1f406ad99 test(cqrs): 补齐 request handler gate 回归
- 新增缺少 IRequestHandler 合同时的 generator 静默跳过覆盖

- 更新 CQRS 恢复文档与本轮验证记录
2026-05-04 19:17:48 +08:00
gewuyou
61cc1be1e5 test(cqrs): 补齐外部 contract gate 回归
- 新增缺少 ILogger 合同时的 generator 静默跳过覆盖

- 新增缺少 IServiceCollection 合同时的 generator 静默跳过覆盖

- 更新 CQRS 恢复文档与本轮验证记录
2026-05-04 19:15:32 +08:00
gewuyou
915d93d06d test(cqrs): 扩展 registry gate 回归
- 新增缺少 notification handler 合同时的 generator 静默跳过覆盖

- 新增缺少 stream handler 合同时的 generator 静默跳过覆盖

- 新增缺少 registry attribute 合同时的 generator 静默跳过覆盖

- 更新 CQRS 恢复文档与本轮验证记录
2026-05-04 19:12:49 +08:00
gewuyou
e17fa15a01 test(cqrs): 补齐 registry gate 回归
- 新增缺少 ICqrsHandlerRegistry 时的 generator 静默跳过覆盖

- 更新 CQRS 恢复文档与本轮验证记录
2026-05-04 19:01:47 +08:00
gewuyou
857ce08edb test(cqrs): 补齐 fallback 元数据回归
- 新增 mixed fallback 禁用多实例 attribute 时的字符串回退覆盖

- 补充 runtime AttributeUsage 变体测试辅助方法

- 更新 CQRS 恢复文档与本轮验证记录
2026-05-04 18:55:04 +08:00
gewuyou
0ac53a4cee test(cqrs): 补齐 request invoker 合同回归
- 新增 request invoker descriptor 缺失时的 generator 回归覆盖

- 新增 request invoker descriptor entry 缺失时的 generator 回归覆盖

- 更新 CQRS 恢复文档与本轮验证记录
2026-05-04 18:49:26 +08:00
gewuyou
ac95202f9c
Merge pull request #322 from GeWuYou/fix/release-notes-pr-links 2026-05-04 16:05:33 +08:00
gewuyou
478072acc3 fix(release): 修复 git-cliff PR 元数据令牌
- 修复 auto-tag 中 git-cliff 使用 PAT_TOKEN 导致 PR 读取权限不受 job permissions 约束的问题

- 修复 semantic-release trace 中重复日期标题触发 MD024 的问题

- 更新 SEMREL-RP-007 跟踪记录,说明发布说明生成的 token 分工与后续恢复点
2026-05-04 14:19:40 +08:00
gewuyou
53870c1f92 fix(release): 修复发布说明 PR 链接缺失
- 修复 release notes 生成 job 缺少 PR 读取权限的问题

- 更新 semantic-release 主题恢复点与验证记录

- 补充当前修复分支到 ai-plan 启动映射
2026-05-04 10:19:58 +08:00
dependabot[bot]
64c5ecb3ca chore(deps): bump peter-evans/create-pull-request from 7 to 8
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7 to 8.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v7...v8)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-04 09:59:20 +08:00
dependabot[bot]
2ccacb8102 Bump Meziantou.Analyzer from 3.0.58 to 3.0.60
---
updated-dependencies:
- dependency-name: Meziantou.Analyzer
  dependency-version: 3.0.60
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-04 09:59:03 +08:00
dependabot[bot]
ee998503b3 Bump Meziantou.Polyfill from 1.0.120 to 1.0.121
---
updated-dependencies:
- dependency-name: Meziantou.Polyfill
  dependency-version: 1.0.121
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-04 09:58:51 +08:00
gewuyou
69ea92c149
Merge pull request #319 from GeWuYou/build/semantic-release-rules
build(release): 支持依赖与安全提交触发补丁发布
2026-05-04 09:58:33 +08:00
gewuyou
c5ca161cb5 build(release): 修复发布说明类型映射
- 修复 release-notes-generator 的 Conventional Commits 类型映射

- 补充 SEMREL-RP-006 的验证结果与 PR review 恢复点
2026-05-04 08:14:41 +08:00
gewuyou
53f8baf2ef build(release): 支持依赖与安全提交触发补丁发布
- 更新 semantic-release 规则,将 deps 与 security 提交映射为 patch 发布

- 补充 AGENTS 与贡献文档中的提交类型语义

- 记录 SEMREL-RP-005 验证结果与分支恢复入口
2026-05-03 23:00:33 +08:00
gewuyou
fe1a875785
Merge pull request #317 from GeWuYou/chore/license-headers
Chore/license headers
2026-05-03 22:59:31 +08:00
gewuyou
4153ea59b8 docs(agents): 补充文件头治理规则
- 补充 AGENTS.md 中的许可证文件头规则

- 修复新增治理文件自身缺失的文件头
2026-05-03 21:00:03 +08:00
gewuyou
ff553977e3 chore(license): 补齐 Apache-2.0 文件头治理
- 新增许可证文件头检查与修复脚本

- 补充维护者手动修复 PR 工作流和 CI 校验

- 更新贡献指南中的文件头说明

- 补齐仓库维护源码和配置文件的许可证声明
2026-05-03 19:39:49 +08:00
gewuyou
a0591afa18
Merge pull request #316 from GeWuYou/docs/godot-logging-composition-archive
docs(godot): 归档 Godot logging 主题
2026-05-03 19:23:43 +08:00
gewuyou
d5d34a626c docs(godot): 修复日志组合文档示例
- 修复组合 logger 示例重复创建文件 appender 的生命周期问题

- 更新 Core logging 文档中的平台无关路径示例

- 补充 Godot 日志页面的路径解析指引承接
2026-05-03 19:07:20 +08:00
gewuyou
230cd0e5d1 docs(godot): 归档 Godot logging 主题
- 补充 GodotLogAppender 与 Core appender 组合示例

- 更新 Godot logging 文档中的文件输出接入说明

- 归档 godot-logging-core-sink 恢复材料并清理 boot 索引
2026-05-03 18:54:33 +08:00
gewuyou
6fa1c20d75
Merge pull request #315 from GeWuYou/feat/godot-logging-core-sink
Feat/godot logging core sink
2026-05-03 15:14:33 +08:00
gewuyou
64e5d8d11d test(godot): 补强日志反射断言
- 补充 GodotLogger 结构化属性反射目标的显式断言

- 优化 反射返回类型不匹配时的测试失败定位

- 更新 godot logging core sink 跟踪与执行 trace
2026-05-03 14:04:59 +08:00
gewuyou
3ced56be8b chore(godot): 处理 Godot 日志 PR 反馈
- 修复 GodotLogAppender 测试对结构化属性顺序的依赖

- 移除 GodotLogger 未使用的私有格式化包装方法

- 更新 ai-plan 默认索引和 trace 恢复记录,避免归档主题与重复标题干扰 boot
2026-05-03 13:24:24 +08:00
gewuyou
1009fee4a4 feat(godot): 新增 Godot 日志 Appender
新增 GodotLogAppender 作为 Core ILogAppender 的 Godot 控制台落点

重构 GodotLogger 输出路径以复用 appender 管线并保持现有 ILogger 入口

补充 Godot appender 渲染测试、文档说明与 active topic 恢复记录
2026-05-03 11:03:58 +08:00
gewuyou
40cce565e6 docs(ai-plan): 启动 Godot logging Core sink 主题
- 归档 Godot logging 合规收尾主题并保留验证结果

- 新增 Godot logging Core sink active topic 恢复入口

- 更新 public boot 索引和当前分支映射
2026-05-03 10:51:16 +08:00
gewuyou
918a61f3b2
Merge pull request #314 from GeWuYou/feat/godot-logging-compliance-polish
Feat/godot logging compliance polish
2026-05-03 10:07:03 +08:00
gewuyou
c967b4df3d fix(godot): 修复日志 review 反馈
- 修复 DeferredLogger 格式化重载提前 string.Format 的热路径问题

- 修复 GodotLogger 默认 options 分配与结构化属性无效 key 处理

- 补充 Godot logging XML 文档、回归测试和 appsettings 接入示例

- 更新 Godot logging PR review 跟踪与验证记录
2026-05-03 09:00:41 +08:00
gewuyou
b4b3538b21 fix(godot): 收敛日志配置评审问题
- 修复 GodotLog 配置源生命周期、Shutdown 释放与延迟 logger 并发发布问题

- 修复 Godot logger 配置归一化、无效数字级别校验和未知级别颜色回退

- 优化 Godot 日志模板缓存边界、内部文档和 update-namespaces 脚本失败传播

- 补充 Godot logging 回归测试、用户文档与 active ai-plan 恢复记录
2026-05-02 22:43:07 +08:00
gewuyou
a52f3c6fec feat(godot): 补齐 GodotLogger 接入文件
- 新增 GodotLogger 的配置加载、延迟入口和宿主输出适配实现。
- 复制 ai-libs/GodotLogger 的 MIT 许可证到 third-party-licenses/GodotLogger/LICENSE。
- 补充 active tracking 与 trace,保留下一阶段对齐点。
2026-05-02 21:39:26 +08:00
gewuyou
748bb714fb feat(godot): 收敛 GodotLogger 宿主能力
- 新增 GodotLog、DeferredLogger 和配置自动发现、热重载接线。
- 修复已缓存 logger 的级别判定与输出路径,使动态配置生效。
- 更新文档与追踪记录,明确当前收敛边界和恢复点。
2026-05-02 21:33:28 +08:00
GeWuYou
36e1ae5f32 feat(godot): add configurable logger templates 2026-05-02 19:33:00 +08:00
gewuyou
6aa741114f ci: include third-party licenses in compliance bundle 2026-05-02 19:10:44 +08:00
gewuyou
5306c98470 chore(ai-plan): archive release summary notes recovery point 2026-05-02 19:10:18 +08:00
gewuyou
35a62e6bfb
Merge pull request #313 from GeWuYou/feat/release-summary-notes
fix(release): 修复发布说明变更汇总标题
2026-05-01 23:36:13 +08:00
gewuyou
43094fba83 fix(release): 修复发布说明变更汇总标题
- 修复 git-cliff 模板的 What's Changed 归属,让分类变更列表承担完整变更清单语义

- 保留 每条变更末尾的作者与 PR 链接,避免新增独立 PR 索引造成重复展示

- 更新 semantic-release-versioning 恢复文档与当前分支映射
2026-05-01 23:29:04 +08:00
1150 changed files with 19836 additions and 1371 deletions

View File

@ -12,6 +12,10 @@ 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. Treat `AGENTS.md` as the source of truth. This skill extends `gframework-boot`; it does not replace it.
Context budget is a first-class stop signal. Do not keep batching merely because a file-count threshold still has
headroom if the active conversation, loaded repo artifacts, validation output, and pending recovery updates suggest the
agent is approaching its safe working-context limit.
## Startup Workflow ## Startup Workflow
1. Execute the normal `gframework-boot` startup sequence first: 1. Execute the normal `gframework-boot` startup sequence first:
@ -28,6 +32,11 @@ Treat `AGENTS.md` as the source of truth. This skill extends `gframework-boot`;
- repeated test refactor pattern - repeated test refactor pattern
- module-by-module documentation refresh - module-by-module documentation refresh
- other repetitive multi-file cleanup - other repetitive multi-file cleanup
4. Before the first implementation batch, estimate whether the current task is likely to stay below roughly 80% of the
agent's safe working-context budget through one more full batch cycle:
- include already loaded `AGENTS.md`, skills, `ai-plan` files, recent command output, active diffs, and expected validation output
- if another batch would probably push the conversation near the limit, plan to stop after the current batch even if
branch-size thresholds still have room
## Baseline Selection ## Baseline Selection
@ -67,8 +76,15 @@ For shorthand numeric thresholds, use a fixed default baseline:
Choose one primary stop condition before the first batch and restate it to the user. Choose one primary stop condition before the first batch and restate it to the user.
When the user does not explicitly override the priority order, use:
1. context-budget safety
2. semantic batch boundary / reviewability
3. the user-requested local metric such as files, lines, warnings, or time
Common stop conditions: Common stop conditions:
- the next batch would likely push the agent above roughly 80% of its safe working-context budget
- branch diff vs baseline approaches a file-count threshold - branch diff vs baseline approaches a file-count threshold
- warnings-only build reaches a target count - warnings-only build reaches a target count
- a specific hotspot list is exhausted - a specific hotspot list is exhausted
@ -76,6 +92,9 @@ Common stop conditions:
If multiple stop conditions exist, rank them and treat one as primary. If multiple stop conditions exist, rank them and treat one as primary.
Treat file-count or line-count thresholds as coarse repository-scope signals, not as a proxy for AI context health.
When they disagree with context-budget safety, context-budget safety wins.
## Shorthand Stop-Condition Syntax ## Shorthand Stop-Condition Syntax
`gframework-batch-boot` may be invoked with shorthand numeric thresholds when the user clearly wants a branch-size stop `gframework-batch-boot` may be invoked with shorthand numeric thresholds when the user clearly wants a branch-size stop
@ -108,6 +127,7 @@ When shorthand is used:
- current branch and active topic - current branch and active topic
- selected baseline - selected baseline
- current stop-condition metric - current stop-condition metric
- current context-budget posture and whether one more batch is safe
- next candidate slices - next candidate slices
2. Keep the critical path local. 2. Keep the critical path local.
3. Delegate only bounded slices with explicit ownership: 3. Delegate only bounded slices with explicit ownership:
@ -128,6 +148,7 @@ When shorthand is used:
- integrate or verify the result - integrate or verify the result
- rerun the required validation - rerun the required validation
- recompute the primary stop-condition metric - recompute the primary stop-condition metric
- reassess whether one more batch would likely push the agent near or beyond roughly 80% context usage
- decide immediately whether to continue or stop - decide immediately whether to continue or stop
7. Do not require the user to manually trigger every round unless: 7. Do not require the user to manually trigger every round unless:
- the next slice is ambiguous - the next slice is ambiguous
@ -158,6 +179,7 @@ For multi-batch work, keep recovery artifacts current.
Stop the loop when any of the following becomes true: Stop the loop when any of the following becomes true:
- the next batch would likely push the agent near or beyond roughly 80% of its safe working-context budget
- the primary stop condition has been reached or exceeded - the primary stop condition has been reached or exceeded
- the remaining slices are no longer low-risk - the remaining slices are no longer low-risk
- validation failures indicate the task is no longer repetitive - validation failures indicate the task is no longer repetitive
@ -165,6 +187,7 @@ Stop the loop when any of the following becomes true:
When stopping, report: When stopping, report:
- whether context budget was the deciding factor
- which baseline was used - which baseline was used
- the exact metric value at stop time - the exact metric value at stop time
- completed batches - completed batches

View File

@ -36,14 +36,18 @@ Treat `AGENTS.md` as the source of truth. Use this skill to enforce a startup se
- `simple`: one concern, one file or module, no parallel discovery required - `simple`: one concern, one file or module, no parallel discovery required
- `medium`: a small number of modules, some read-only exploration helpful, critical path still easy to keep local - `medium`: a small number of modules, some read-only exploration helpful, critical path still easy to keep local
- `complex`: cross-module design, migration, large refactor, or work likely to exceed one context window - `complex`: cross-module design, migration, large refactor, or work likely to exceed one context window
11. Apply the delegation policy from `AGENTS.md`: 11. Estimate the current context-budget posture before substantive execution:
- account for loaded startup artifacts, active `ai-plan` files, visible diffs, open validation output, and likely next-step output volume
- if the task already appears near roughly 80% of a safe working-context budget, prefer closing the current batch,
refreshing recovery artifacts, and stopping at the next natural semantic boundary instead of starting a fresh broad slice
12. Apply the delegation policy from `AGENTS.md`:
- Keep the critical path local - Keep the critical path local
- Use `explorer` with `gpt-5.1-codex-mini` for narrow read-only questions, tracing, inventory, and comparisons - Use `explorer` with `gpt-5.1-codex-mini` for narrow read-only questions, tracing, inventory, and comparisons
- Use `worker` with `gpt-5.4` only for bounded implementation tasks with explicit ownership - Use `worker` with `gpt-5.4` only for bounded implementation tasks with explicit ownership
- Do not delegate purely for ceremony; delegate only when it materially shortens the task or controls context growth - Do not delegate purely for ceremony; delegate only when it materially shortens the task or controls context growth
12. Before editing files, tell the user what you read, how you classified the task, whether subagents will be used, 13. Before editing files, tell the user what you read, how you classified the task, whether subagents will be used,
and the first implementation step. and the first implementation step.
13. Proceed with execution, validation, and documentation updates required by `AGENTS.md`. 14. Proceed with execution, validation, and documentation updates required by `AGENTS.md`.
## Task Tracking ## Task Tracking
@ -69,6 +73,8 @@ For multi-step, cross-module, or interruption-prone work, maintain the repositor
first, then search the mapped active topics before scanning the broader public area. first, then search the mapped active topics before scanning the broader public area.
- If the current branch and the mapped active topics describe the same feature area, prefer resuming those topics first. - If the current branch and the mapped active topics describe the same feature area, prefer resuming those topics first.
- If the repository state suggests in-flight work but no recovery document matches, reconstruct the safest next step from code, tests, and Git state before asking the user for clarification. - If the repository state suggests in-flight work but no recovery document matches, reconstruct the safest next step from code, tests, and Git state before asking the user for clarification.
- If the current turn already carries heavy recovery context, broad diffs, or long validation output, prefer a
recovery-point update and a clean stop over starting another large slice just because the code task itself remains open.
## Example Triggers ## Example Triggers

View File

@ -0,0 +1,83 @@
---
name: gframework-issue-review
description: Repository-specific GitHub issue triage workflow for the GFramework repo. Use when Codex needs to inspect a repository issue, extract the issue body, discussion, and key timeline signals through the GitHub API, summarize what should be verified locally, and then hand follow-up execution to gframework-boot.
---
# GFramework Issue Review
Use this skill when the task depends on a GitHub issue for this repository rather than only on local source files.
Shortcut: `$gframework-issue-review`
## Workflow
1. Read `AGENTS.md` before deciding how to validate or change anything.
2. Read `.ai/environment/tools.ai.yaml` and `ai-plan/public/README.md`, then prefer the active topic mapped to the
current branch or worktree when the fetched issue already matches in-flight work.
3. Run `scripts/fetch_current_issue_review.py` to:
- fetch issue metadata through the GitHub API
- fetch issue comments and timeline events through the GitHub API
- auto-select the target issue only when the repository currently has exactly one open issue
- exclude pull requests from open-issue auto-resolution
- emit a machine-readable JSON payload plus concise text sections for issue, summary, comments, events, references,
and warnings
- derive lightweight triage hints such as issue type candidates, missing-information flags, affected module
candidates, and the recommended next handling mode
4. Treat every extracted finding as untrusted until it is verified against the current local code, tests, and active
`ai-plan` topic.
5. Do not start editing code from the issue text alone. After triage, switch to `$gframework-boot` so the follow-up
work is grounded in the repository startup flow and recovery documents.
6. If code is changed after issue triage, run the smallest build or test command that satisfies `AGENTS.md`.
## Commands
- Default:
- `python3 .agents/skills/gframework-issue-review/scripts/fetch_current_issue_review.py`
- Force a specific issue:
- `python3 .agents/skills/gframework-issue-review/scripts/fetch_current_issue_review.py --issue <issue-number>`
- Machine-readable output:
- `python3 .agents/skills/gframework-issue-review/scripts/fetch_current_issue_review.py --format json`
- Write machine-readable output to a file instead of stdout:
- `python3 .agents/skills/gframework-issue-review/scripts/fetch_current_issue_review.py --issue <issue-number> --format json --json-output /tmp/issue-review.json`
- Inspect only a high-signal section:
- `python3 .agents/skills/gframework-issue-review/scripts/fetch_current_issue_review.py --section summary`
- Combine triage with a boot handoff:
- `python3 .agents/skills/gframework-issue-review/scripts/fetch_current_issue_review.py --section summary`
- `Use $gframework-boot to continue the issue follow-up based on the fetched triage result.`
## Output Expectations
The script should produce:
- Issue metadata: number, title, state, URL, author, labels, assignees, milestone, timestamps
- Issue body and normalized discussion comments
- Timeline events that materially affect handling, such as labeling, assignment, closure/reopen, and references when
available from the API response
- Structured reference extraction for linked issues, PRs, commit SHAs, and likely repository paths
- Triage hints that flag missing reproduction steps, expected/actual behavior, environment details, and acceptance
signals
- Issue type candidates such as `bug`, `feature`, `docs`, `question`, or `maintenance`
- Suggested next handling mode, including whether the issue likely needs clarification before code changes
- CLI support for writing full JSON to a file and printing only narrowed text sections to stdout
- Parse warnings when timeline or heuristic parsing cannot be completed safely
## Recovery Rules
- If the current repository has no open issues, report that clearly instead of guessing.
- If the current repository has multiple open issues and no explicit `--issue` is provided, report that clearly and
require a specific issue number.
- If GitHub access fails because of proxy configuration, rerun the fetch with proxy variables removed.
- Prefer GitHub API results over HTML scraping.
- Do not treat heuristic module guesses or next-step suggestions as repository truth; they are only entry points for
subsequent local verification.
- If the issue discussion reveals that the problem statement has already shifted, prefer the newest concrete comment or
timeline signal over the original title/body wording.
- After extracting the issue, continue the actual implementation flow with `$gframework-boot` so the task is grounded
in current branch context and `ai-plan` recovery artifacts.
## Example Triggers
- `Use $gframework-issue-review on the current repository issue`
- `Check the open GitHub issue and summarize what should be verified locally`
- `Inspect issue <issue-number> and tell me whether this looks like bug triage or a feature request`
- `先用 $gframework-issue-review 看当前 open issue再用 $gframework-boot 继续`

View File

@ -0,0 +1,4 @@
interface:
display_name: "GFramework Issue Review"
short_description: "Inspect the current repository issue and triage next steps"
default_prompt: "Use $gframework-issue-review to inspect the current repository issue through the GitHub API, summarize the issue body, discussion, and key timeline signals, highlight what must be verified locally, and then hand follow-up execution to $gframework-boot."

View File

@ -0,0 +1,858 @@
#!/usr/bin/env python3
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
"""
Fetch the current GFramework GitHub issue and extract the signals needed for
local follow-up work without relying on gh CLI.
"""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
import urllib.error
import urllib.request
from typing import Any
OWNER = "GeWuYou"
REPO = "GFramework"
WORKTREE_ROOT_DIRECTORY_NAME = "GFramework-WorkTree"
GIT_ENVIRONMENT_KEY = "GFRAMEWORK_WINDOWS_GIT"
GIT_DIR_ENVIRONMENT_KEY = "GFRAMEWORK_GIT_DIR"
WORK_TREE_ENVIRONMENT_KEY = "GFRAMEWORK_WORK_TREE"
REQUEST_TIMEOUT_ENVIRONMENT_KEY = "GFRAMEWORK_ISSUE_REVIEW_TIMEOUT_SECONDS"
GITHUB_TOKEN_ENVIRONMENT_KEYS = ("GFRAMEWORK_GITHUB_TOKEN", "GITHUB_TOKEN", "GH_TOKEN")
PROXY_ENVIRONMENT_KEYS = ("http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "all_proxy")
DEFAULT_REQUEST_TIMEOUT_SECONDS = 60
USER_AGENT = "codex-gframework-issue-review"
DISPLAY_SECTION_CHOICES = (
"issue",
"summary",
"comments",
"events",
"references",
"warnings",
)
ISSUE_TYPE_CANDIDATES = ("bug", "feature", "docs", "question", "maintenance")
ACTIVE_TOPIC_KEYWORDS: dict[str, tuple[str, ...]] = {
"ai-first-config-system": ("config", "configuration", "gameconfig", "settings"),
"coroutine-optimization": ("coroutine", "yield", "await", "scheduler"),
"cqrs-rewrite": ("cqrs", "command", "query", "eventbus", "event bus"),
"data-repository-persistence": ("repository", "serialization", "persistence", "data", "settings"),
"runtime-generator-boundary": ("source generator", "generator", "attribute", "packaging"),
"semantic-release-versioning": ("release", "version", "semantic-release", "tag", "publish"),
"documentation-full-coverage-governance": ("docs", "documentation", "readme", "vitepress", "api reference"),
}
ACTUAL_BEHAVIOR_PATTERNS = (
"actual",
"currently",
"instead",
"but",
"error",
"exception",
"fails",
"failed",
"wrong",
)
EXPECTED_BEHAVIOR_PATTERNS = (
"expected",
"should",
"want",
"would like",
"needs to",
)
REPRODUCTION_PATTERNS = (
"steps to reproduce",
"reproduce",
"reproduction",
"how to reproduce",
"minimal example",
"sample",
"demo",
)
ENVIRONMENT_PATTERNS = (
"windows",
"linux",
"macos",
"wsl",
"godot",
".net",
"sdk",
"version",
"environment",
)
ACCEPTANCE_PATTERNS = (
"acceptance",
"done when",
"definition of done",
"verified by",
"test plan",
)
FILE_PATH_PATTERN = re.compile(r"\b(?:[A-Za-z0-9_.-]+/)+[A-Za-z0-9_.-]+\b")
ISSUE_REFERENCE_PATTERN = re.compile(r"(?:^|\s)#(\d+)\b")
COMMIT_REFERENCE_PATTERN = re.compile(r"\b[0-9a-f]{7,40}\b")
LINE_BREAK_NORMALIZER = re.compile(r"\n{3,}")
def resolve_git_command() -> str:
"""Resolve the git executable to use for this repository."""
candidates = [
os.environ.get(GIT_ENVIRONMENT_KEY),
"git.exe",
"git",
]
for candidate in candidates:
if not candidate:
continue
if os.path.isabs(candidate):
if os.path.exists(candidate):
return candidate
continue
resolved_candidate = shutil.which(candidate)
if resolved_candidate:
return resolved_candidate
raise RuntimeError(f"No usable git executable found. Set {GIT_ENVIRONMENT_KEY} to override it.")
def find_repository_root(start_path: Path) -> Path | None:
"""Locate the repository root by walking parent directories for repo markers."""
for candidate in (start_path, *start_path.parents):
if (candidate / "AGENTS.md").exists() and (candidate / ".ai/environment/tools.ai.yaml").exists():
return candidate
return None
def resolve_worktree_git_dir(repository_root: Path) -> Path | None:
"""Resolve the main-repository worktree gitdir for this WSL worktree layout."""
if repository_root.parent.name != WORKTREE_ROOT_DIRECTORY_NAME:
return None
primary_repository_root = repository_root.parent.parent / REPO
candidate_git_dir = primary_repository_root / ".git" / "worktrees" / repository_root.name
return candidate_git_dir if candidate_git_dir.exists() else None
def resolve_git_invocation() -> list[str]:
"""Resolve the git command arguments, preferring explicit WSL worktree binding."""
configured_git_dir = os.environ.get(GIT_DIR_ENVIRONMENT_KEY)
configured_work_tree = os.environ.get(WORK_TREE_ENVIRONMENT_KEY)
linux_git = shutil.which("git")
if configured_git_dir and configured_work_tree and linux_git:
return [linux_git, f"--git-dir={configured_git_dir}", f"--work-tree={configured_work_tree}"]
repository_root = find_repository_root(Path.cwd())
if repository_root is not None and linux_git:
worktree_git_dir = resolve_worktree_git_dir(repository_root)
if worktree_git_dir is not None:
return [linux_git, f"--git-dir={worktree_git_dir}", f"--work-tree={repository_root}"]
root_git_dir = repository_root / ".git"
if root_git_dir.exists():
return [linux_git, f"--git-dir={root_git_dir}", f"--work-tree={repository_root}"]
return [resolve_git_command()]
def resolve_request_timeout_seconds() -> int:
"""Return the GitHub request timeout in seconds."""
configured_timeout = os.environ.get(REQUEST_TIMEOUT_ENVIRONMENT_KEY)
if not configured_timeout:
return DEFAULT_REQUEST_TIMEOUT_SECONDS
try:
parsed_timeout = int(configured_timeout)
except ValueError as error:
raise RuntimeError(
f"{REQUEST_TIMEOUT_ENVIRONMENT_KEY} must be an integer number of seconds."
) from error
if parsed_timeout <= 0:
raise RuntimeError(f"{REQUEST_TIMEOUT_ENVIRONMENT_KEY} must be greater than zero.")
return parsed_timeout
def run_command(args: list[str]) -> str:
"""Run a command and return stdout, raising on failure."""
process = subprocess.run(args, capture_output=True, text=True, check=False)
if process.returncode != 0:
stderr = process.stderr.strip()
raise RuntimeError(f"Command failed: {' '.join(args)}\n{stderr}")
return process.stdout.strip()
def get_current_branch() -> str:
"""Return the current git branch name."""
return run_command([*resolve_git_invocation(), "rev-parse", "--abbrev-ref", "HEAD"])
def resolve_github_token() -> str | None:
"""Return the first configured GitHub token for authenticated API requests."""
for environment_key in GITHUB_TOKEN_ENVIRONMENT_KEYS:
token = os.environ.get(environment_key)
if token:
return token
return None
def build_request_headers(accept: str) -> dict[str, str]:
"""Build GitHub request headers and include auth when a token is available."""
headers = {"Accept": accept, "User-Agent": USER_AGENT}
token = resolve_github_token()
if token:
headers["Authorization"] = f"Bearer {token}"
return headers
def has_proxy_environment() -> bool:
"""Return whether the current process is configured to use an outbound proxy."""
return any(os.environ.get(environment_key) for environment_key in PROXY_ENVIRONMENT_KEYS)
def perform_request(url: str, headers: dict[str, str], *, disable_proxy: bool) -> tuple[str, Any]:
"""Execute a single HTTP request and return decoded text plus response headers."""
opener = (
urllib.request.build_opener(urllib.request.ProxyHandler({}))
if disable_proxy
else urllib.request.build_opener()
)
request = urllib.request.Request(url, headers=headers)
with opener.open(request, timeout=resolve_request_timeout_seconds()) as response:
return response.read().decode("utf-8", "replace"), response.headers
def open_url(url: str, accept: str) -> tuple[str, Any]:
"""Open a URL, retrying without proxies only when the configured proxy path fails."""
headers = build_request_headers(accept)
try:
return perform_request(url, headers, disable_proxy=False)
except urllib.error.HTTPError:
raise
except (urllib.error.URLError, TimeoutError, OSError):
if not has_proxy_environment():
raise
return perform_request(url, headers, disable_proxy=True)
def fetch_json(url: str, accept: str = "application/vnd.github+json") -> tuple[Any, Any]:
"""Fetch a JSON payload and its response headers from GitHub."""
text, headers = open_url(url, accept=accept)
return json.loads(text), headers
def extract_next_link(headers: Any) -> str | None:
"""Extract the next-page link from GitHub pagination headers."""
link_header = headers.get("Link")
if not link_header:
return None
match = re.search(r'<([^>]+)>;\s*rel="next"', link_header)
return match.group(1) if match else None
def fetch_paged_json(url: str, accept: str = "application/vnd.github+json") -> list[dict[str, Any]]:
"""Fetch every page from a paginated GitHub API endpoint."""
items: list[dict[str, Any]] = []
next_url: str | None = url
while next_url:
payload, headers = fetch_json(next_url, accept=accept)
if not isinstance(payload, list):
raise RuntimeError(f"Expected list payload from GitHub API, got {type(payload).__name__}.")
items.extend(payload)
next_url = extract_next_link(headers)
return items
def collapse_whitespace(text: str) -> str:
"""Collapse repeated whitespace into single spaces while preserving paragraph intent."""
normalized = text.replace("\r\n", "\n").replace("\r", "\n")
normalized = LINE_BREAK_NORMALIZER.sub("\n\n", normalized)
normalized = re.sub(r"[ \t]+", " ", normalized)
normalized = re.sub(r" *\n *", "\n", normalized)
return normalized.strip()
def truncate_text(text: str, max_length: int) -> str:
"""Collapse whitespace and truncate long text for CLI display."""
collapsed = collapse_whitespace(text)
if max_length <= 0 or len(collapsed) <= max_length:
return collapsed
return collapsed[: max_length - 3].rstrip() + "..."
def filter_open_issue_candidates(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""Filter GitHub issue list responses down to non-PR issue items."""
return [item for item in items if not item.get("pull_request")]
def select_single_open_issue_number(items: list[dict[str, Any]]) -> int:
"""Resolve the target issue number when the repository has exactly one open issue."""
issues = filter_open_issue_candidates(items)
if not issues:
raise RuntimeError("No open GitHub issues found for this repository. Pass --issue <number> to inspect one.")
if len(issues) > 1:
numbers = ", ".join(str(item.get("number")) for item in issues[:5])
suffix = "" if len(issues) <= 5 else ", ..."
raise RuntimeError(
"Multiple open GitHub issues found for this repository "
f"({len(issues)} total: {numbers}{suffix}). Pass --issue <number> to inspect one."
)
return int(issues[0]["number"])
def resolve_issue_number(issue_number: int | None) -> tuple[int, str]:
"""Resolve the issue number, auto-selecting only when exactly one open issue exists."""
if issue_number is not None:
return issue_number, "explicit"
open_items = fetch_paged_json(f"https://api.github.com/repos/{OWNER}/{REPO}/issues?state=open&per_page=100")
return select_single_open_issue_number(open_items), "auto-single-open-issue"
def fetch_issue_metadata(issue_number: int) -> dict[str, Any]:
"""Fetch normalized metadata for a GitHub issue."""
payload, _ = fetch_json(f"https://api.github.com/repos/{OWNER}/{REPO}/issues/{issue_number}")
if not isinstance(payload, dict):
raise RuntimeError("Failed to fetch GitHub issue metadata.")
if payload.get("pull_request"):
raise RuntimeError(f"Item #{issue_number} is a pull request, not a plain issue.")
labels = []
for label in payload.get("labels", []):
if isinstance(label, dict) and label.get("name"):
labels.append(str(label["name"]))
assignees = []
for assignee in payload.get("assignees", []):
login = assignee.get("login")
if login:
assignees.append(str(login))
milestone_title = None
milestone = payload.get("milestone")
if isinstance(milestone, dict) and milestone.get("title"):
milestone_title = str(milestone["title"])
return {
"number": int(payload["number"]),
"title": str(payload["title"]),
"state": str(payload["state"]).upper(),
"url": str(payload["html_url"]),
"author": str(payload.get("user", {}).get("login") or ""),
"created_at": str(payload.get("created_at") or ""),
"updated_at": str(payload.get("updated_at") or ""),
"labels": labels,
"assignees": assignees,
"milestone": milestone_title,
"body": str(payload.get("body") or ""),
}
def fetch_issue_comments(issue_number: int) -> list[dict[str, Any]]:
"""Fetch issue comments for the selected issue."""
return fetch_paged_json(f"https://api.github.com/repos/{OWNER}/{REPO}/issues/{issue_number}/comments?per_page=100")
def fetch_issue_timeline(issue_number: int) -> list[dict[str, Any]]:
"""Fetch issue timeline events when GitHub exposes them to the current client."""
return fetch_paged_json(
f"https://api.github.com/repos/{OWNER}/{REPO}/issues/{issue_number}/timeline?per_page=100",
accept="application/vnd.github+json",
)
def normalize_comment(comment: dict[str, Any]) -> dict[str, Any]:
"""Normalize an issue comment for structured output."""
return {
"id": int(comment.get("id") or 0),
"author": str(comment.get("user", {}).get("login") or ""),
"created_at": str(comment.get("created_at") or ""),
"updated_at": str(comment.get("updated_at") or ""),
"body": str(comment.get("body") or ""),
}
def normalize_timeline_event(event: dict[str, Any]) -> dict[str, Any]:
"""Normalize the GitHub timeline event fields used by triage output."""
actor = str(event.get("actor", {}).get("login") or "")
created_at = str(event.get("created_at") or event.get("submitted_at") or "")
event_type = str(event.get("event") or event.get("__typename") or "unknown")
label_name = ""
assignee = ""
source_issue_number: int | None = None
source_issue_url = ""
commit_id = ""
label = event.get("label")
if isinstance(label, dict) and label.get("name"):
label_name = str(label["name"])
assignee_payload = event.get("assignee")
if isinstance(assignee_payload, dict) and assignee_payload.get("login"):
assignee = str(assignee_payload["login"])
source = event.get("source")
if isinstance(source, dict):
issue_payload = source.get("issue")
if isinstance(issue_payload, dict):
if issue_payload.get("number"):
source_issue_number = int(issue_payload["number"])
if issue_payload.get("html_url"):
source_issue_url = str(issue_payload["html_url"])
commit_id_value = event.get("commit_id")
if isinstance(commit_id_value, str):
commit_id = commit_id_value
return {
"event": event_type,
"actor": actor,
"created_at": created_at,
"label": label_name,
"assignee": assignee,
"commit_id": commit_id,
"source_issue_number": source_issue_number,
"source_issue_url": source_issue_url,
}
def gather_text_blocks(issue: dict[str, Any], comments: list[dict[str, Any]]) -> list[str]:
"""Return the issue body plus discussion comment bodies for heuristic parsing."""
blocks = [issue.get("body", "")]
blocks.extend(comment.get("body", "") for comment in comments)
return [block for block in blocks if block]
def has_any_pattern(text_blocks: list[str], patterns: tuple[str, ...]) -> bool:
"""Return whether any normalized text block contains any requested pattern."""
lowered_blocks = [collapse_whitespace(block).lower() for block in text_blocks]
return any(pattern in block for block in lowered_blocks for pattern in patterns)
def choose_issue_type_candidates(issue: dict[str, Any], text_blocks: list[str]) -> list[str]:
"""Infer lightweight issue-type candidates from labels and discussion text."""
labels = [label.lower() for label in issue.get("labels", [])]
text = "\n".join(text_blocks).lower()
candidates: list[str] = []
if any(label in {"bug", "regression"} for label in labels) or "bug" in text or "error" in text or "fails" in text:
candidates.append("bug")
if any(label in {"feature", "enhancement"} for label in labels) or "feature" in text or "support" in text:
candidates.append("feature")
if any(label in {"documentation", "docs"} for label in labels) or "documentation" in text or "readme" in text:
candidates.append("docs")
if any(label in {"question", "help wanted"} for label in labels) or "?" in issue.get("title", ""):
candidates.append("question")
if any(label in {"chore", "maintenance", "refactor"} for label in labels) or "cleanup" in text or "refactor" in text:
candidates.append("maintenance")
if not candidates:
candidates.append("question" if issue.get("body", "").strip().endswith("?") else "bug")
ordered_candidates: list[str] = []
for candidate in ISSUE_TYPE_CANDIDATES:
if candidate in candidates:
ordered_candidates.append(candidate)
return ordered_candidates
def extract_references_from_text(text: str) -> dict[str, list[str]]:
"""Extract issue, commit, and file-path references from one text block."""
issue_numbers = sorted({match.group(1) for match in ISSUE_REFERENCE_PATTERN.finditer(text)}, key=int)
commit_shas = sorted({match.group(0) for match in COMMIT_REFERENCE_PATTERN.finditer(text)})
file_paths = sorted({match.group(0) for match in FILE_PATH_PATTERN.finditer(text)})
return {
"issues": [f"#{number}" for number in issue_numbers],
"commit_shas": commit_shas,
"file_paths": file_paths,
}
def merge_reference_values(values: list[dict[str, list[str]]]) -> dict[str, list[str]]:
"""Merge extracted reference lists while preserving sorted unique output."""
merged: dict[str, set[str]] = {"issues": set(), "commit_shas": set(), "file_paths": set()}
for value in values:
for key in merged:
merged[key].update(value.get(key, []))
return {
"issues": sorted(merged["issues"], key=lambda item: int(item[1:])),
"commit_shas": sorted(merged["commit_shas"]),
"file_paths": sorted(merged["file_paths"]),
}
def build_references(issue: dict[str, Any], comments: list[dict[str, Any]], events: list[dict[str, Any]]) -> dict[str, Any]:
"""Build structured references from issue text and timeline context."""
extracted = [extract_references_from_text(issue.get("body", ""))]
extracted.extend(extract_references_from_text(comment.get("body", "")) for comment in comments)
merged = merge_reference_values(extracted)
referenced_by_timeline = sorted(
{
f"#{event['source_issue_number']}"
for event in events
if event.get("source_issue_number") is not None
},
key=lambda item: int(item[1:]),
)
pull_request_references = sorted(
{
issue_reference
for issue_reference in merged["issues"]
if issue_reference != f"#{issue['number']}"
},
key=lambda item: int(item[1:]),
)
return {
"issues": merged["issues"],
"pull_requests_or_issues": pull_request_references,
"commit_shas": merged["commit_shas"],
"file_paths": merged["file_paths"],
"timeline_cross_references": referenced_by_timeline,
}
def build_information_flags(
issue: dict[str, Any],
comments: list[dict[str, Any]],
issue_type_candidates: list[str],
) -> dict[str, bool]:
"""Derive missing-information and readiness flags with issue-type-aware heuristics."""
text_blocks = gather_text_blocks(issue, comments)
has_reproduction_steps = has_any_pattern(text_blocks, REPRODUCTION_PATTERNS)
has_expected_behavior = has_any_pattern(text_blocks, EXPECTED_BEHAVIOR_PATTERNS)
has_actual_behavior = has_any_pattern(text_blocks, ACTUAL_BEHAVIOR_PATTERNS)
has_environment_details = has_any_pattern(text_blocks, ENVIRONMENT_PATTERNS)
has_acceptance_signals = has_any_pattern(text_blocks, ACCEPTANCE_PATTERNS)
primary_issue_type = issue_type_candidates[0] if issue_type_candidates else "bug"
if primary_issue_type == "bug":
needs_clarification = not (
(has_actual_behavior and (has_reproduction_steps or has_environment_details))
or has_acceptance_signals
)
elif primary_issue_type in {"feature", "docs"}:
needs_clarification = not (has_expected_behavior or has_acceptance_signals)
elif primary_issue_type == "maintenance":
needs_clarification = not (has_expected_behavior or has_actual_behavior or has_acceptance_signals)
else:
needs_clarification = not (has_expected_behavior or has_actual_behavior or has_acceptance_signals)
return {
"has_reproduction_steps": has_reproduction_steps,
"has_expected_behavior": has_expected_behavior,
"has_actual_behavior": has_actual_behavior,
"has_environment_details": has_environment_details,
"has_acceptance_signals": has_acceptance_signals,
"needs_clarification": needs_clarification,
}
def choose_affected_topics(issue: dict[str, Any], comments: list[dict[str, Any]]) -> list[str]:
"""Map the issue discussion to likely active topics when obvious keyword matches exist."""
text = "\n".join(gather_text_blocks(issue, comments)).lower()
matches: list[str] = []
for topic, keywords in ACTIVE_TOPIC_KEYWORDS.items():
if any(keyword in text for keyword in keywords):
matches.append(topic)
return matches
def choose_next_action(
information_flags: dict[str, bool],
issue_type_candidates: list[str],
affected_topics: list[str],
) -> str:
"""Choose the next handling mode for boot handoff."""
if information_flags["needs_clarification"]:
return "clarify-issue-before-code"
if affected_topics:
return "resume-existing-topic-with-boot"
if "docs" in issue_type_candidates and issue_type_candidates[0] == "docs":
return "start-new-docs-topic-with-boot"
return "start-new-topic-with-boot"
def build_triage_hints(issue: dict[str, Any], comments: list[dict[str, Any]]) -> dict[str, Any]:
"""Build lightweight, reviewable triage hints for boot follow-up."""
text_blocks = gather_text_blocks(issue, comments)
issue_type_candidates = choose_issue_type_candidates(issue, text_blocks)
information_flags = build_information_flags(issue, comments, issue_type_candidates)
affected_topics = choose_affected_topics(issue, comments)
next_action = choose_next_action(information_flags, issue_type_candidates, affected_topics)
return {
"issue_type_candidates": issue_type_candidates,
"information_flags": information_flags,
"affected_active_topics": affected_topics,
"next_action": next_action,
"boot_handoff": {
"recommended_skill": "gframework-boot",
"mode": "resume" if affected_topics else "new",
"notes": (
"Use gframework-boot to verify the issue against local code and active ai-plan topics."
if not information_flags["needs_clarification"]
else "Use gframework-boot to record a clarification-first task before changing code."
),
},
}
def build_result(issue_number: int, branch: str, resolution_mode: str) -> dict[str, Any]:
"""Build the full issue review payload for the selected issue."""
parse_warnings: list[str] = []
issue = fetch_issue_metadata(issue_number)
raw_comments = fetch_issue_comments(issue_number)
comments = [normalize_comment(comment) for comment in raw_comments]
events: list[dict[str, Any]] = []
try:
raw_events = fetch_issue_timeline(issue_number)
events = [normalize_timeline_event(event) for event in raw_events]
except Exception as error: # noqa: BLE001
parse_warnings.append(f"Issue timeline could not be fetched or parsed: {error}")
references = build_references(issue, comments, events)
triage_hints = build_triage_hints(issue, comments)
return {
"issue": {
**issue,
"resolved_from_branch": branch,
"resolution_mode": resolution_mode,
},
"discussion": {
"comment_count": len(comments),
"comments": comments,
},
"events": {
"count": len(events),
"items": events,
},
"references": references,
"triage_hints": triage_hints,
"parse_warnings": parse_warnings,
}
def write_json_output(result: dict[str, Any], output_path: str) -> str:
"""Write the full JSON result to disk and return the destination path."""
destination_path = Path(output_path).expanduser()
destination_path.parent.mkdir(parents=True, exist_ok=True)
destination_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
return str(destination_path)
def summarize_events(events: list[dict[str, Any]]) -> list[str]:
"""Convert normalized events into concise text lines."""
lines: list[str] = []
for event in events:
summary = f"- {event['event']}"
details: list[str] = []
if event.get("actor"):
details.append(f"actor={event['actor']}")
if event.get("label"):
details.append(f"label={event['label']}")
if event.get("assignee"):
details.append(f"assignee={event['assignee']}")
if event.get("source_issue_number") is not None:
details.append(f"source_issue=#{event['source_issue_number']}")
if event.get("commit_id"):
details.append(f"commit={event['commit_id'][:12]}")
if event.get("created_at"):
details.append(f"at={event['created_at']}")
if details:
summary += " (" + ", ".join(details) + ")"
lines.append(summary)
return lines
def format_text(
result: dict[str, Any],
*,
sections: list[str] | None = None,
max_description_length: int = 400,
json_output_path: str | None = None,
) -> str:
"""Format the result payload into concise text output."""
lines: list[str] = []
selected_sections = set(sections or DISPLAY_SECTION_CHOICES)
issue = result["issue"]
triage_hints = result["triage_hints"]
discussion = result["discussion"]
events = result["events"]
references = result["references"]
if "issue" in selected_sections:
lines.append(f"Issue #{issue['number']}: {issue['title']}")
lines.append(f"State: {issue['state']}")
lines.append(f"Author: {issue['author']}")
lines.append(f"Labels: {', '.join(issue['labels']) if issue['labels'] else '(none)'}")
lines.append(f"Assignees: {', '.join(issue['assignees']) if issue['assignees'] else '(none)'}")
lines.append(f"Milestone: {issue['milestone'] or '(none)'}")
lines.append(f"Created: {issue['created_at']}")
lines.append(f"Updated: {issue['updated_at']}")
lines.append(f"Resolved from branch: {issue['resolved_from_branch'] or '(not branch-based)'}")
lines.append(f"Resolution mode: {issue['resolution_mode']}")
lines.append(f"URL: {issue['url']}")
if issue["body"]:
lines.append("Body:")
lines.append(truncate_text(issue["body"], max_description_length))
if "summary" in selected_sections:
lines.append("")
lines.append("Triage summary:")
lines.append("- Issue type candidates: " + ", ".join(triage_hints["issue_type_candidates"]))
information_flags = triage_hints["information_flags"]
lines.append(
"- Information flags: "
+ ", ".join(
[
f"repro={'yes' if information_flags['has_reproduction_steps'] else 'no'}",
f"expected={'yes' if information_flags['has_expected_behavior'] else 'no'}",
f"actual={'yes' if information_flags['has_actual_behavior'] else 'no'}",
f"environment={'yes' if information_flags['has_environment_details'] else 'no'}",
f"acceptance={'yes' if information_flags['has_acceptance_signals'] else 'no'}",
f"needs_clarification={'yes' if information_flags['needs_clarification'] else 'no'}",
]
)
)
lines.append(
"- Affected active topics: "
+ (", ".join(triage_hints["affected_active_topics"]) if triage_hints["affected_active_topics"] else "(none)")
)
lines.append(f"- Next action: {triage_hints['next_action']}")
lines.append(f"- Boot handoff: {triage_hints['boot_handoff']['notes']}")
if "comments" in selected_sections:
lines.append("")
lines.append(f"Discussion comments: {discussion['comment_count']}")
for comment in discussion["comments"]:
lines.append(f"- {comment['author']} at {comment['created_at']}")
lines.append(f" {truncate_text(comment['body'], max_description_length)}")
if "events" in selected_sections:
lines.append("")
lines.append(f"Timeline events: {events['count']}")
lines.extend(summarize_events(events["items"]))
if "references" in selected_sections:
lines.append("")
lines.append("References:")
lines.append("- Mentioned issues: " + (", ".join(references["issues"]) if references["issues"] else "(none)"))
lines.append(
"- Cross references: "
+ (
", ".join(references["timeline_cross_references"])
if references["timeline_cross_references"]
else "(none)"
)
)
lines.append(
"- Related issue/PR mentions: "
+ (
", ".join(references["pull_requests_or_issues"])
if references["pull_requests_or_issues"]
else "(none)"
)
)
lines.append("- Commit SHAs: " + (", ".join(references["commit_shas"]) if references["commit_shas"] else "(none)"))
lines.append("- File paths: " + (", ".join(references["file_paths"]) if references["file_paths"] else "(none)"))
if result["parse_warnings"] and "warnings" in selected_sections:
lines.append("")
lines.append("Warnings:")
for warning in result["parse_warnings"]:
lines.append(f"- {truncate_text(warning, max_description_length)}")
if json_output_path:
lines.append("")
lines.append(f"Full JSON written to: {json_output_path}")
return "\n".join(lines)
def parse_args() -> argparse.Namespace:
"""Parse CLI arguments."""
parser = argparse.ArgumentParser()
parser.add_argument("--branch", help="Override the current branch name.")
parser.add_argument("--issue", type=int, help="Fetch a specific issue number instead of auto-selecting one.")
parser.add_argument("--format", choices=("text", "json"), default="text")
parser.add_argument(
"--json-output",
help="Write the full JSON result to a file. When used with --format text, stdout stays concise and points to the file.",
)
parser.add_argument(
"--section",
action="append",
choices=DISPLAY_SECTION_CHOICES,
help="Limit text output to specific sections. Can be passed multiple times.",
)
parser.add_argument(
"--max-description-length",
type=int,
default=400,
help="Truncate long text bodies in text output to this many characters.",
)
return parser.parse_args()
def main() -> None:
"""Run the CLI entry point."""
args = parse_args()
branch = args.branch or get_current_branch()
issue_number, resolution_mode = resolve_issue_number(args.issue)
result = build_result(issue_number, branch, resolution_mode)
json_output_path: str | None = None
if args.json_output:
json_output_path = write_json_output(result, args.json_output)
if args.format == "json":
print(json.dumps(result, ensure_ascii=False, indent=2))
return
print(
format_text(
result,
sections=args.section,
max_description_length=args.max_description_length,
json_output_path=json_output_path,
)
)
if __name__ == "__main__":
try:
main()
except Exception as error: # noqa: BLE001
print(str(error), file=sys.stderr)
sys.exit(1)

View File

@ -0,0 +1,94 @@
#!/usr/bin/env python3
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
"""Regression tests for the GFramework issue review fetch helper."""
from __future__ import annotations
import importlib.util
from pathlib import Path
import unittest
SCRIPT_PATH = Path(__file__).with_name("fetch_current_issue_review.py")
MODULE_SPEC = importlib.util.spec_from_file_location("fetch_current_issue_review", SCRIPT_PATH)
if MODULE_SPEC is None or MODULE_SPEC.loader is None:
raise RuntimeError(f"Unable to load module from {SCRIPT_PATH}.")
MODULE = importlib.util.module_from_spec(MODULE_SPEC)
MODULE_SPEC.loader.exec_module(MODULE)
class SelectSingleOpenIssueNumberTests(unittest.TestCase):
"""Cover auto-resolution rules for open GitHub issues."""
def test_select_single_open_issue_number_filters_pull_requests(self) -> None:
"""Pull requests in the issues API must not block the single-open-issue path."""
selected = MODULE.select_single_open_issue_number(
[
{"number": 10, "pull_request": {"url": "https://example.test/pr/10"}},
{"number": 11},
]
)
self.assertEqual(selected, 11)
def test_select_single_open_issue_number_rejects_multiple_plain_issues(self) -> None:
"""Auto-resolution must stop when more than one plain issue is open."""
with self.assertRaisesRegex(RuntimeError, "Multiple open GitHub issues found"):
MODULE.select_single_open_issue_number([{"number": 11}, {"number": 12}])
class ExtractReferencesFromTextTests(unittest.TestCase):
"""Cover lightweight reference extraction used by the text and JSON output."""
def test_extract_references_from_text_finds_issue_commit_and_path_mentions(self) -> None:
"""The helper should retain the high-signal references needed for follow-up triage."""
references = MODULE.extract_references_from_text(
"See #123, commit abcdef1234567890, and GFramework.Core/Systems/Runner.cs for the failing path."
)
self.assertEqual(references["issues"], ["#123"])
self.assertEqual(references["commit_shas"], ["abcdef1234567890"])
self.assertEqual(references["file_paths"], ["GFramework.Core/Systems/Runner.cs"])
class BuildTriageHintsTests(unittest.TestCase):
"""Cover next-action classification for non-bug issue flows."""
def test_build_triage_hints_routes_docs_issue_to_docs_topic_without_bug_style_clarification(self) -> None:
"""Docs issues with a clear requested change should not be forced through bug-style clarification."""
triage_hints = MODULE.build_triage_hints(
{
"title": "Update documentation landing page",
"labels": ["docs"],
"body": "The guide should explain the landing-page layout for new contributors.",
},
[],
)
self.assertEqual(triage_hints["issue_type_candidates"][0], "docs")
self.assertEqual(triage_hints["affected_active_topics"], [])
self.assertFalse(triage_hints["information_flags"]["needs_clarification"])
self.assertEqual(triage_hints["next_action"], "start-new-docs-topic-with-boot")
def test_build_triage_hints_routes_feature_issue_to_new_topic_when_request_is_clear(self) -> None:
"""Feature requests with explicit desired behavior should stay actionable without fake bug repro gates."""
triage_hints = MODULE.build_triage_hints(
{
"title": "Support release note previews",
"labels": ["enhancement"],
"body": "The workflow should support previewing generated notes before completion.",
},
[],
)
self.assertEqual(triage_hints["issue_type_candidates"][0], "feature")
self.assertEqual(triage_hints["affected_active_topics"], [])
self.assertFalse(triage_hints["information_flags"]["needs_clarification"])
self.assertEqual(triage_hints["next_action"], "start-new-topic-with-boot")
if __name__ == "__main__":
unittest.main()

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
language: "zh-CN" language: "zh-CN"
early_access: false early_access: false

View File

@ -1,4 +1,7 @@
license_overrides: # Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
license_overrides:
NETStandard.Library: MIT NETStandard.Library: MIT
Microsoft.NETCore.Platforms: MIT Microsoft.NETCore.Platforms: MIT
System.Buffers: MIT System.Buffers: MIT

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
name: "Bug Report / 缺陷报告" name: "Bug Report / 缺陷报告"
description: "Report a reproducible defect in GFramework. / 报告可稳定复现的 GFramework 缺陷。" description: "Report a reproducible defect in GFramework. / 报告可稳定复现的 GFramework 缺陷。"
title: "[Bug]: " title: "[Bug]: "

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
name: "Feature Request / 功能建议" name: "Feature Request / 功能建议"
description: "Suggest a new capability or an API improvement. / 提出新能力或 API 改进建议。" description: "Suggest a new capability or an API improvement. / 提出新能力或 API 改进建议。"
title: "[Feature]: " title: "[Feature]: "

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
name: "Documentation / 文档改进" name: "Documentation / 文档改进"
description: "Report missing, outdated, or unclear documentation. / 报告缺失、过期或不清晰的文档。" description: "Report missing, outdated, or unclear documentation. / 报告缺失、过期或不清晰的文档。"
title: "[Docs]: " title: "[Docs]: "

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
name: "Question / 使用咨询" name: "Question / 使用咨询"
description: "Ask for guidance about usage, behavior, or adoption. / 询问用法、行为或接入方式。" description: "Ask for guidance about usage, behavior, or adoption. / 询问用法、行为或接入方式。"
title: "[Question]: " title: "[Question]: "

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: "Search Existing Issues / 搜索现有 Issues" - name: "Search Existing Issues / 搜索现有 Issues"

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
name: Validate PAT name: Validate PAT
description: Validate that the release PAT can access the repository and push tags. description: Validate that the release PAT can access the repository and push tags.

4
.github/cliff.toml vendored
View File

@ -54,6 +54,9 @@ https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{% endif -%} {% endif -%}
{% if commits | length > 0 -%}
## What's Changed
{% for group, commits in commits | group_by(attribute="group") -%} {% for group, commits in commits | group_by(attribute="group") -%}
### {{ group | striptags | trim }} ### {{ group | striptags | trim }}
{% for commit in commits -%} {% for commit in commits -%}
@ -61,6 +64,7 @@ https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{% endfor %} {% endfor %}
{% endfor -%} {% endfor -%}
{% endif -%}
{% if previous and previous.version and version -%} {% if previous and previous.version and version -%}
Full Changelog: [{{ previous.version }}...{{ version }}]({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}) Full Changelog: [{{ previous.version }}...{{ version }}]({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }})

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
version: 2 version: 2
updates: updates:
# ===== NuGet 依赖(所有项目)===== # ===== NuGet 依赖(所有项目)=====

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
name: Semantic Release Version and Tag name: Semantic Release Version and Tag
on: on:
@ -14,6 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
pull-requests: read
outputs: outputs:
published: ${{ steps.semantic_release.outputs.new_release_published }} published: ${{ steps.semantic_release.outputs.new_release_published }}
last_tag: ${{ steps.semantic_release.outputs.last_release_git_tag }} last_tag: ${{ steps.semantic_release.outputs.last_release_git_tag }}
@ -68,7 +72,7 @@ jobs:
env: env:
OUTPUT: PREVIEW_RELEASE_NOTES.md OUTPUT: PREVIEW_RELEASE_NOTES.md
GITHUB_REPO: ${{ github.repository }} GITHUB_REPO: ${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} GITHUB_TOKEN: ${{ github.token }}
- name: Write preview summary - name: Write preview summary
env: env:
@ -105,6 +109,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
pull-requests: read
environment: environment:
name: release-approval name: release-approval
steps: steps:
@ -154,7 +159,7 @@ jobs:
env: env:
OUTPUT: PUBLISHED_RELEASE_NOTES.md OUTPUT: PUBLISHED_RELEASE_NOTES.md
GITHUB_REPO: ${{ github.repository }} GITHUB_REPO: ${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} GITHUB_TOKEN: ${{ github.token }}
- name: Write release summary - name: Write release summary
env: env:

71
.github/workflows/benchmark.yml vendored Normal file
View File

@ -0,0 +1,71 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
name: Benchmark
on:
workflow_dispatch:
inputs:
benchmark_filter:
description: '可选的 BenchmarkDotNet 过滤器;留空时仅执行 benchmark 项目 Release build'
required: false
default: ''
type: string
permissions:
contents: read
jobs:
benchmark:
name: Benchmark Build Or Run
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup .NET 10
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x
- name: Cache NuGet packages
uses: actions/cache@v5
with:
path: |
~/.nuget/packages
~/.local/share/NuGet
key: ${{ runner.os }}-nuget-benchmarks-${{ hashFiles('GFramework.Cqrs.Benchmarks/*.csproj', 'GFramework.Cqrs/*.csproj', 'GFramework.Cqrs.Abstractions/*.csproj', 'GFramework.Core/*.csproj', 'GFramework.Core.Abstractions/*.csproj', '**/nuget.config') }}
- name: Restore benchmark project
run: dotnet restore GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj
- name: Build benchmark project
run: dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-restore
- name: Report build-only mode
if: ${{ inputs.benchmark_filter == '' }}
run: |
echo "No benchmark filter provided."
echo "Workflow completed after validating the benchmark project build."
- name: Run filtered benchmarks
if: ${{ inputs.benchmark_filter != '' }}
env:
BENCHMARK_FILTER: ${{ inputs.benchmark_filter }}
run: |
set -euo pipefail
dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- \
--filter "$BENCHMARK_FILTER"
- name: Upload BenchmarkDotNet artifacts
if: ${{ always() && inputs.benchmark_filter != '' }}
uses: actions/upload-artifact@v7
with:
name: benchmark-artifacts
path: |
BenchmarkDotNet.Artifacts/**
GFramework.Cqrs.Benchmarks/bin/Release/net10.0/BenchmarkDotNet.Artifacts/**
if-no-files-found: ignore

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
# CI/CD工作流配置构建和测试.NET项目 # CI/CD工作流配置构建和测试.NET项目
# 该工作流仅在创建或更新面向任意分支的 pull request 时触发 # 该工作流仅在创建或更新面向任意分支的 pull request 时触发
name: CI - Build & Test name: CI - Build & Test
@ -28,6 +31,13 @@ jobs:
- name: Validate C# naming - name: Validate C# naming
run: bash scripts/validate-csharp-naming.sh run: bash scripts/validate-csharp-naming.sh
# 校验仓库维护源码是否包含 Apache-2.0 文件头声明
- name: Validate license headers
run: python3 scripts/license-header.py --check
- name: Validate runtime-generator boundaries
run: python3 scripts/validate-runtime-generator-boundaries.py
# 缓存MegaLinter # 缓存MegaLinter
- name: Cache MegaLinter - name: Cache MegaLinter
uses: actions/cache@v5 uses: actions/cache@v5
@ -145,6 +155,19 @@ jobs:
- name: Build - name: Build
run: dotnet build GFramework.sln -c Release --no-restore 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
- name: Validate packed modules
run: bash scripts/validate-packed-modules.sh ./packages
# 运行单元测试输出TRX格式结果到TestResults目录 # 运行单元测试输出TRX格式结果到TestResults目录
# 顺序执行各测试项目,避免并发 dotnet test 进程导致“TRX 全绿但 step 仍返回失败”的假红状态 # 顺序执行各测试项目,避免并发 dotnet test 进程导致“TRX 全绿但 step 仍返回失败”的假红状态
- name: Test All Projects - name: Test All Projects

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
# GitHub Actions工作流配置CodeQL静态代码分析 # GitHub Actions工作流配置CodeQL静态代码分析
# 该工作流用于对C#项目进行安全漏洞和代码质量分析 # 该工作流用于对C#项目进行安全漏洞和代码质量分析
name: "CodeQL" name: "CodeQL"

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
name: License Compliance (Feluda) name: License Compliance (Feluda)
on: on:
@ -62,6 +65,7 @@ jobs:
# with: 配置上传的具体内容 # with: 配置上传的具体内容
# name: 工件名称,用于标识上传的文件集合 # name: 工件名称,用于标识上传的文件集合
# path: 指定需要上传的文件路径列表(支持多行格式) # path: 指定需要上传的文件路径列表(支持多行格式)
# third-party-licenses/**: 手工维护的参考源码许可证原文
- name: Upload compliance artifacts - name: Upload compliance artifacts
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
@ -69,6 +73,7 @@ jobs:
path: | path: |
NOTICE NOTICE
THIRD_PARTY_LICENSES.md THIRD_PARTY_LICENSES.md
third-party-licenses/**
sbom.spdx.json sbom.spdx.json
sbom.cyclonedx.json sbom.cyclonedx.json
sbom-spdx-validation.txt sbom-spdx-validation.txt
@ -79,15 +84,17 @@ jobs:
# 压缩包中包含以下文件: # 压缩包中包含以下文件:
# - NOTICE: 项目声明文件 # - NOTICE: 项目声明文件
# - THIRD_PARTY_LICENSES.md: 第三方许可证列表 # - THIRD_PARTY_LICENSES.md: 第三方许可证列表
# - third-party-licenses/: 手工维护的参考源码许可证原文
# - sbom.spdx.json: SPDX 格式的软件物料清单 # - sbom.spdx.json: SPDX 格式的软件物料清单
# - sbom.cyclonedx.json: CycloneDX 格式的软件物料清单 # - sbom.cyclonedx.json: CycloneDX 格式的软件物料清单
# - sbom-spdx-validation.txt: SPDX 格式验证结果 # - sbom-spdx-validation.txt: SPDX 格式验证结果
# - sbom-cyclonedx-validation.txt: CycloneDX 格式验证结果 # - sbom-cyclonedx-validation.txt: CycloneDX 格式验证结果
- name: Package compliance bundle - name: Package compliance bundle
run: | run: |
zip license-compliance.zip \ zip -r license-compliance.zip \
NOTICE \ NOTICE \
THIRD_PARTY_LICENSES.md \ THIRD_PARTY_LICENSES.md \
third-party-licenses \
sbom.spdx.json \ sbom.spdx.json \
sbom.cyclonedx.json \ sbom.cyclonedx.json \
sbom-spdx-validation.txt \ sbom-spdx-validation.txt \

View File

@ -0,0 +1,54 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
# 维护者手动触发的 Apache-2.0 文件头修复流程。
name: License Header Fix
on:
workflow_dispatch:
inputs:
base_branch:
description: Branch to fix and target with the generated pull request.
required: true
default: main
permissions:
contents: write
pull-requests: write
jobs:
fix-license-headers:
name: Create license header fix PR
runs-on: ubuntu-latest
steps:
- name: Checkout target branch
uses: actions/checkout@v6
with:
ref: ${{ inputs.base_branch }}
- name: Add missing license headers
run: python3 scripts/license-header.py --fix
- name: Create pull request
uses: peter-evans/create-pull-request@v8
with:
token: ${{ secrets.GITHUB_TOKEN }}
base: ${{ inputs.base_branch }}
branch: chore/license-headers-${{ github.run_id }}
delete-branch: true
commit-message: |
chore(license): 补齐 Apache-2.0 文件头
- 补充缺失源文件许可证声明
- 更新文件头治理校验结果
title: "chore(license): 补齐 Apache-2.0 文件头"
body: |
## Summary
- 补齐仓库维护源码和配置文件缺失的 Apache-2.0 文件头
- 使用 `scripts/license-header.py --fix` 生成本次修复
## Validation
- `python3 scripts/license-header.py --check`

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
# 工作流名称Publish Docs # 工作流名称Publish Docs
# 该工作流用于在推送标签或手动触发时构建并部署文档到 GitHub Pages # 该工作流用于在推送标签或手动触发时构建并部署文档到 GitHub Pages

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
name: Publish VS Code Extension name: Publish VS Code Extension
on: on:

View File

@ -1,3 +1,6 @@
# Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
# 发布工作流NuGet + GitHub Packages + GitHub Release # 发布工作流NuGet + GitHub Packages + GitHub Release
# #
# 功能:当推送标签时自动构建、打包,并将相同产物并发发布到 NuGet.org 与 GitHub Packages # 功能:当推送标签时自动构建、打包,并将相同产物并发发布到 NuGet.org 与 GitHub Packages
@ -79,41 +82,10 @@ jobs:
-p:IncludeSymbols=false -p:IncludeSymbols=false
- name: Validate packed modules - name: Validate packed modules
run: | run: bash scripts/validate-packed-modules.sh ./packages
set -euo pipefail
expected_packages=( - name: Validate runtime-generator package boundaries
"GeWuYou.GFramework" run: python3 scripts/validate-runtime-generator-boundaries.py --package-dir ./packages
"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
- name: Show packages - name: Show packages
run: ls -la ./packages || true run: ls -la ./packages || true
@ -240,6 +212,7 @@ jobs:
permissions: permissions:
contents: write contents: write
packages: read packages: read
pull-requests: read
steps: steps:
- name: Checkout repository (at tag) - name: Checkout repository (at tag)

1
.gitignore vendored
View File

@ -26,3 +26,4 @@ ai-libs/
.codex .codex
# tool # tool
.venv/ .venv/
BenchmarkDotNet.Artifacts/

View File

@ -1,4 +1,7 @@
# 配置文件用于设置代码质量检查工具的各项参数和规则 # Copyright (c) 2025-2026 GeWuYou
# SPDX-License-Identifier: Apache-2.0
# 配置文件用于设置代码质量检查工具的各项参数和规则
# 包含全局排除目录、启用/禁用的检查器、特定语言配置等设置 # 包含全局排除目录、启用/禁用的检查器、特定语言配置等设置
APPLY_FIXES: none APPLY_FIXES: none

View File

@ -33,6 +33,14 @@
"type": "refactor", "type": "refactor",
"release": "patch" "release": "patch"
}, },
{
"type": "deps",
"release": "patch"
},
{
"type": "security",
"release": "patch"
},
{ {
"type": "docs", "type": "docs",
"release": false "release": false
@ -70,6 +78,45 @@
"@semantic-release/release-notes-generator", "@semantic-release/release-notes-generator",
{ {
"preset": "conventionalcommits", "preset": "conventionalcommits",
"presetConfig": {
"types": [
{
"type": "feat",
"section": "Features",
"hidden": false
},
{
"type": "fix",
"section": "Bug Fixes",
"hidden": false
},
{
"type": "perf",
"section": "Performance Improvements",
"hidden": false
},
{
"type": "refactor",
"section": "Refactoring",
"hidden": false
},
{
"type": "deps",
"section": "Dependency Updates",
"hidden": false
},
{
"type": "security",
"section": "Security Fixes",
"hidden": false
},
{
"type": "revert",
"section": "Reverts",
"hidden": false
}
]
},
"parserOpts": { "parserOpts": {
"noteKeywords": [ "noteKeywords": [
"BREAKING CHANGE", "BREAKING CHANGE",

View File

@ -60,6 +60,10 @@ All AI agents and contributors must follow these rules when writing, reviewing,
`minor` segment. `minor` segment.
- Use `fix` for behavior corrections, `perf` for observable performance improvements, and `refactor` only for - Use `fix` for behavior corrections, `perf` for observable performance improvements, and `refactor` only for
non-feature code restructuring; these should raise the next released version's `patch` segment. non-feature code restructuring; these should raise the next released version's `patch` segment.
- Use `deps` for dependency version updates, dependency lockfile refreshes, and package maintenance that should raise
the next released version's `patch` segment.
- Use `security` for vulnerability fixes, dependency security mitigations, and security configuration corrections
that should raise the next released version's `patch` segment.
- Use `docs``test``chore``build``ci``style` for their literal categories; do not encode these changes as - Use `docs``test``chore``build``ci``style` for their literal categories; do not encode these changes as
`feat` just because they feel important. These categories MUST NOT trigger a release. `feat` just because they feel important. These categories MUST NOT trigger a release.
- Use `BREAKING CHANGE` in the commit footer or `!` after the type / scope header (for example `feat!:` or - Use `BREAKING CHANGE` in the commit footer or `!` after the type / scope header (for example `feat!:` or
@ -79,6 +83,23 @@ All AI agents and contributors must follow these rules when writing, reviewing,
- The branch naming rule for a new task branch is `<type>/<topic-or-scope>`, where `<type>` should match the intended - The branch naming rule for a new task branch is `<type>/<topic-or-scope>`, where `<type>` should match the intended
Conventional Commit category as closely as practical. Conventional Commit category as closely as practical.
## License Header Rules
- Repository-maintained source and configuration files that are supported by `scripts/license-header.py` MUST include an
Apache-2.0 file header before the task is considered complete.
- When creating or modifying supported files, contributors MUST preserve an existing compliant header or add the SPDX
header generated by `python3 scripts/license-header.py --fix`.
- Before committing changes that add or modify supported source/configuration files, contributors MUST run
`python3 scripts/license-header.py --check` and resolve any missing or misplaced headers.
- For files with shebang lines, keep the shebang as the first line and place the license header immediately after it.
- For XML/MSBuild files with an XML declaration, keep the XML declaration as the first node and place the license header
immediately after it.
- Do not add project license headers to excluded or third-party areas such as `.agents/**`, `ai-libs/**`,
`third-party-licenses/**`, generated snapshots, binary assets, lock files, and generated build output. Treat
`scripts/license-header.py` as the authoritative include/exclude policy for this check.
- If CI reports a license-header failure, either fix it locally with `python3 scripts/license-header.py --fix` or, for
maintainer-owned cleanup, use the manual `License Header Fix` GitHub Actions workflow to create a reviewed repair PR.
## Repository Boot Skill ## Repository Boot Skill
- The repository-maintained Codex boot skill lives at `.codex/skills/gframework-boot/`. - The repository-maintained Codex boot skill lives at `.codex/skills/gframework-boot/`.
@ -191,6 +212,9 @@ All generated or modified code MUST include clear and meaningful comments where
- Private fields: `_camelCase` - Private fields: `_camelCase`
- Keep abstractions projects free of implementation details and engine-specific dependencies. - Keep abstractions projects free of implementation details and engine-specific dependencies.
- Preserve existing module boundaries. Do not introduce new cross-module dependencies without clear architectural need. - Preserve existing module boundaries. Do not introduce new cross-module dependencies without clear architectural need.
- Framework runtime, abstractions, and meta-package projects MUST NOT reference `*.SourceGenerators*` projects or packages,
and MUST NOT use source-generator attributes such as `GenerateEnumExtensions` or `ContextAware`. Those capabilities are
reserved for consumer projects, generator projects, examples explicitly meant to demonstrate generator usage, and related tests.
### Formatting ### Formatting

View File

@ -1,11 +1,16 @@
<!--
Copyright (c) 2025-2026 GeWuYou
SPDX-License-Identifier: Apache-2.0
-->
<Project> <Project>
<!-- Keep repository-wide analyzer behavior consistent while allowing only selected projects to opt into polyfills. --> <!-- Keep repository-wide analyzer behavior consistent while allowing only selected projects to opt into polyfills. -->
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="3.0.58"> <PackageReference Include="Meziantou.Analyzer" Version="3.0.72">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Update="Meziantou.Polyfill" Version="1.0.120"> <PackageReference Update="Meziantou.Polyfill" Version="1.0.123">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference> </PackageReference>

View File

@ -1,3 +1,8 @@
<!--
Copyright (c) 2025-2026 GeWuYou
SPDX-License-Identifier: Apache-2.0
-->
<Project> <Project>
<!-- <!--

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using System.Collections.Concurrent; using System.Collections.Concurrent;
namespace GFramework.Core.Abstractions.Architectures; namespace GFramework.Core.Abstractions.Architectures;

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Enums; using GFramework.Core.Abstractions.Enums;
namespace GFramework.Core.Abstractions.Architectures; namespace GFramework.Core.Abstractions.Architectures;

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using System.Reflection; using System.Reflection;
using GFramework.Core.Abstractions.Lifecycle; using GFramework.Core.Abstractions.Lifecycle;
using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Model;
@ -81,6 +84,20 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia
void RegisterCqrsPipelineBehavior<TBehavior>() void RegisterCqrsPipelineBehavior<TBehavior>()
where TBehavior : class; where TBehavior : class;
/// <summary>
/// 注册 CQRS 流式请求管道行为。
/// 既支持实现 <c>IStreamPipelineBehavior&lt;,&gt;</c> 的开放泛型行为类型,
/// 也支持绑定到单一流式请求/响应对的封闭行为类型。
/// </summary>
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
/// <exception cref="InvalidOperationException">当前架构的底层容器已冻结,无法继续注册流式管道行为。</exception>
/// <exception cref="ObjectDisposedException">当前架构的底层容器已释放,无法继续注册流式管道行为。</exception>
/// <remarks>
/// 该入口应在架构初始化冻结容器之前调用;具体开放泛型或封闭行为类型的校验逻辑由底层容器负责。
/// </remarks>
void RegisterCqrsStreamPipelineBehavior<TBehavior>()
where TBehavior : class;
/// <summary> /// <summary>
/// 从指定程序集显式注册 CQRS 处理器。 /// 从指定程序集显式注册 CQRS 处理器。
/// 当处理器位于默认架构程序集之外的模块或扩展程序集中时,可在初始化阶段调用该入口接入对应程序集。 /// 当处理器位于默认架构程序集之外的模块或扩展程序集中时,可在初始化阶段调用该入口接入对应程序集。

View File

@ -1,4 +1,7 @@
using GFramework.Core.Abstractions.Properties; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Properties;
namespace GFramework.Core.Abstractions.Architectures; namespace GFramework.Core.Abstractions.Architectures;

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Command; using GFramework.Core.Abstractions.Command;
using GFramework.Core.Abstractions.Environment; using GFramework.Core.Abstractions.Environment;
using GFramework.Core.Abstractions.Events; using GFramework.Core.Abstractions.Events;

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Architectures; namespace GFramework.Core.Abstractions.Architectures;
/// <summary> /// <summary>

View File

@ -1,4 +1,7 @@
using GFramework.Core.Abstractions.Enums; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Enums;
namespace GFramework.Core.Abstractions.Architectures; namespace GFramework.Core.Abstractions.Architectures;

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Architectures; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Architectures;
/// <summary> /// <summary>
/// 架构模块接口,继承自架构生命周期接口。 /// 架构模块接口,继承自架构生命周期接口。

View File

@ -1,4 +1,7 @@
using GFramework.Core.Abstractions.Enums; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Enums;
namespace GFramework.Core.Abstractions.Architectures; namespace GFramework.Core.Abstractions.Architectures;

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Command; using GFramework.Core.Abstractions.Command;
using GFramework.Core.Abstractions.Events; using GFramework.Core.Abstractions.Events;
using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Ioc;

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Lifecycle; using GFramework.Core.Abstractions.Lifecycle;

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Ioc;
namespace GFramework.Core.Abstractions.Architectures; namespace GFramework.Core.Abstractions.Architectures;

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Bases; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Bases;
/// <summary> /// <summary>
/// 表示键值对的接口,定义了通用的键值对数据结构契约 /// 表示键值对的接口,定义了通用的键值对数据结构契约

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Bases; namespace GFramework.Core.Abstractions.Bases;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Bases; namespace GFramework.Core.Abstractions.Bases;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Rule; using GFramework.Core.Abstractions.Rule;
namespace GFramework.Core.Abstractions.Command; namespace GFramework.Core.Abstractions.Command;

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Rule; using GFramework.Core.Abstractions.Rule;
namespace GFramework.Core.Abstractions.Command; namespace GFramework.Core.Abstractions.Command;

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Command; namespace GFramework.Core.Abstractions.Command;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Events; using GFramework.Core.Abstractions.Events;
using GFramework.Core.Abstractions.Utility; using GFramework.Core.Abstractions.Utility;

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Controller; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Controller;
/// <summary> /// <summary>
/// 控制器标记接口,用于标识控制器组件 /// 控制器标记接口,用于标识控制器组件

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Coroutine; namespace GFramework.Core.Abstractions.Coroutine;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Coroutine; namespace GFramework.Core.Abstractions.Coroutine;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Coroutine; namespace GFramework.Core.Abstractions.Coroutine;
/// <summary> /// <summary>

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Coroutine; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Coroutine;
/// <summary> /// <summary>
/// 表示协程的执行状态枚举 /// 表示协程的执行状态枚举

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Coroutine; namespace GFramework.Core.Abstractions.Coroutine;
/// <summary> /// <summary>

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Coroutine; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Coroutine;
/// <summary> /// <summary>
/// 时间源接口,提供当前时间、时间增量以及更新功能 /// 时间源接口,提供当前时间、时间增量以及更新功能

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Coroutine; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Coroutine;
/// <summary> /// <summary>
/// 定义一个可等待指令的接口,用于协程系统中的异步操作控制 /// 定义一个可等待指令的接口,用于协程系统中的异步操作控制

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using System.ComponentModel; using System.ComponentModel;
namespace GFramework.Core.Abstractions.Cqrs; namespace GFramework.Core.Abstractions.Cqrs;

View File

@ -1,3 +1,8 @@
<!--
Copyright (c) 2025-2026 GeWuYou
SPDX-License-Identifier: Apache-2.0
-->
<Project> <Project>
<!-- import parent: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build --> <!-- import parent: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build -->
<PropertyGroup> <PropertyGroup>

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Enums; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Enums;
/// <summary> /// <summary>
/// 架构阶段枚举,定义了系统架构初始化和运行过程中的各个关键阶段 /// 架构阶段枚举,定义了系统架构初始化和运行过程中的各个关键阶段

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Environment; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Environment;
/// <summary> /// <summary>
/// 定义环境接口,提供应用程序运行环境的相关信息 /// 定义环境接口,提供应用程序运行环境的相关信息

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Events; namespace GFramework.Core.Abstractions.Events;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Events; namespace GFramework.Core.Abstractions.Events;
/// <summary> /// <summary>

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Events; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Events;
/// <summary> /// <summary>
/// 事件接口,定义了事件注册的基本功能 /// 事件接口,定义了事件注册的基本功能

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Events; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Events;
/// <summary> /// <summary>
/// 事件总线接口,提供事件的发送、注册和注销功能 /// 事件总线接口,提供事件的发送、注册和注销功能

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Events; namespace GFramework.Core.Abstractions.Events;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Events; namespace GFramework.Core.Abstractions.Events;
/// <summary> /// <summary>

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Events; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Events;
/// <summary> /// <summary>
/// 提供注销功能的接口 /// 提供注销功能的接口

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Events; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Events;
/// <summary> /// <summary>
/// 提供统一注销功能的接口,用于管理需要注销的对象列表 /// 提供统一注销功能的接口,用于管理需要注销的对象列表

View File

@ -1,4 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <!--
Copyright (c) 2025-2026 GeWuYou
SPDX-License-Identifier: Apache-2.0
-->
<Project Sdk="Microsoft.NET.Sdk">
<!-- <!--
配置项目构建属性 配置项目构建属性

View File

@ -1,4 +1,7 @@
// IsExternalInit.cs // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
// IsExternalInit.cs
// This type is required to support init-only setters and record types // This type is required to support init-only setters and record types
// when targeting netstandard2.0 or older frameworks. // when targeting netstandard2.0 or older frameworks.

View File

@ -1,13 +1,22 @@
using System.Reflection; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using System.Reflection;
using GFramework.Core.Abstractions.Rule; using GFramework.Core.Abstractions.Rule;
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;
namespace GFramework.Core.Abstractions.Ioc; namespace GFramework.Core.Abstractions.Ioc;
/// <summary> /// <summary>
/// 依赖注入容器接口,定义了服务注册、解析和管理的基本操作 /// 依赖注入容器接口,定义服务注册、解析与生命周期管理的统一入口。
/// </summary> /// </summary>
public interface IIocContainer : IContextAware /// <remarks>
/// 实现者必须在 <see cref="IDisposable.Dispose" /> 中释放容器拥有的根 <see cref="IServiceProvider" /> 及其
/// 关联同步资源,并保证释放操作幂等。
/// 容器一旦释放,后续任何注册、解析、查询或作用域创建调用都必须抛出
/// <see cref="ObjectDisposedException" />,避免消费者继续访问失效的运行时状态。
/// </remarks>
public interface IIocContainer : IContextAware, IDisposable
{ {
#region Register Methods #region Register Methods
@ -96,6 +105,20 @@ public interface IIocContainer : IContextAware
void RegisterCqrsPipelineBehavior<TBehavior>() void RegisterCqrsPipelineBehavior<TBehavior>()
where TBehavior : class; where TBehavior : class;
/// <summary>
/// 注册 CQRS 流式请求管道行为。
/// </summary>
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
/// <exception cref="InvalidOperationException">容器已冻结,无法继续注册流式管道行为。</exception>
/// <exception cref="ObjectDisposedException">容器已释放,无法继续注册流式管道行为。</exception>
/// <remarks>
/// 该入口既支持实现 <c>IStreamPipelineBehavior&lt;,&gt;</c> 的开放泛型行为类型,
/// 也支持绑定到单一流式请求/响应对的封闭行为类型。
/// 应在容器冻结前的注册阶段调用;具体可注册形态由实现容器负责校验。
/// </remarks>
void RegisterCqrsStreamPipelineBehavior<TBehavior>()
where TBehavior : class;
/// <summary> /// <summary>
/// 从指定程序集显式注册 CQRS 处理器。 /// 从指定程序集显式注册 CQRS 处理器。
/// 该入口适用于处理器不位于默认架构程序集中的场景,例如扩展包、模块程序集或拆分后的业务程序集。 /// 该入口适用于处理器不位于默认架构程序集中的场景,例如扩展包、模块程序集或拆分后的业务程序集。
@ -132,6 +155,10 @@ public interface IIocContainer : IContextAware
/// </summary> /// </summary>
/// <typeparam name="T">期望获取的实例类型</typeparam> /// <typeparam name="T">期望获取的实例类型</typeparam>
/// <returns>找到的第一个实例;如果未找到则返回 null</returns> /// <returns>找到的第一个实例;如果未找到则返回 null</returns>
/// <remarks>
/// 在 <see cref="Freeze" /> 之前,该查询只保证返回已经物化为实例绑定的服务。
/// 仅通过工厂或实现类型注册的服务在预冻结阶段可能不可见;若需要完整激活语义,请先冻结容器。
/// </remarks>
T? Get<T>() where T : class; T? Get<T>() where T : class;
/// <summary> /// <summary>
@ -140,6 +167,10 @@ public interface IIocContainer : IContextAware
/// </summary> /// </summary>
/// <param name="type">期望获取的实例类型</param> /// <param name="type">期望获取的实例类型</param>
/// <returns>找到的第一个实例;如果未找到则返回 null</returns> /// <returns>找到的第一个实例;如果未找到则返回 null</returns>
/// <remarks>
/// 在 <see cref="Freeze" /> 之前,该查询只保证返回已经物化为实例绑定的服务。
/// 仅通过工厂或实现类型注册的服务在预冻结阶段可能不可见;若需要完整激活语义,请先冻结容器。
/// </remarks>
object? Get(Type type); object? Get(Type type);
@ -165,6 +196,9 @@ public interface IIocContainer : IContextAware
/// </summary> /// </summary>
/// <typeparam name="T">期望获取的实例类型</typeparam> /// <typeparam name="T">期望获取的实例类型</typeparam>
/// <returns>所有符合条件的实例列表;如果没有则返回空数组</returns> /// <returns>所有符合条件的实例列表;如果没有则返回空数组</returns>
/// <remarks>
/// 在 <see cref="Freeze" /> 之前,该查询只会枚举当前已经可见的实例绑定,不会主动执行工厂或创建实现类型。
/// </remarks>
IReadOnlyList<T> GetAll<T>() where T : class; IReadOnlyList<T> GetAll<T>() where T : class;
/// <summary> /// <summary>
@ -172,6 +206,9 @@ public interface IIocContainer : IContextAware
/// </summary> /// </summary>
/// <param name="type">期望获取的实例类型</param> /// <param name="type">期望获取的实例类型</param>
/// <returns>所有符合条件的实例列表;如果没有则返回空数组</returns> /// <returns>所有符合条件的实例列表;如果没有则返回空数组</returns>
/// <remarks>
/// 在 <see cref="Freeze" /> 之前,该查询只会枚举当前已经可见的实例绑定,不会主动执行工厂或创建实现类型。
/// </remarks>
IReadOnlyList<object> GetAll(Type type); IReadOnlyList<object> GetAll(Type type);
@ -210,8 +247,26 @@ public interface IIocContainer : IContextAware
/// </summary> /// </summary>
/// <typeparam name="T">要检查的类型</typeparam> /// <typeparam name="T">要检查的类型</typeparam>
/// <returns>如果容器中包含指定类型的实例则返回true否则返回false</returns> /// <returns>如果容器中包含指定类型的实例则返回true否则返回false</returns>
/// <remarks>
/// 在 <see cref="Freeze" /> 之前,该方法更接近“是否存在对应注册”的检查,而不是完整的 DI 可解析性判断。
/// </remarks>
bool Contains<T>() where T : class; bool Contains<T>() where T : class;
/// <summary>
/// 检查容器中是否存在可赋值给指定服务类型的注册项,而不要求解析出实例。
/// </summary>
/// <param name="type">要检查的服务类型。</param>
/// <returns>若存在显式注册或开放泛型映射可满足该服务类型,则返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
/// <exception cref="ArgumentNullException">当 <paramref name="type" /> 为 <see langword="null" /> 时抛出。</exception>
/// <exception cref="ObjectDisposedException">当调用 <see cref="HasRegistration(Type)" /> 时容器已被释放时抛出。</exception>
/// <remarks>
/// 该入口面向“先判断是否值得解析实例”的热路径优化场景。
/// 与 <see cref="Contains{T}" /> 不同,它不会为了判断结果而激活服务实例,因此可避免把瞬态对象创建、
/// 多服务枚举或日志分配混入仅需存在性判断的调用链中。
/// 该方法按服务键与开放泛型映射判断可见性,不会把“仅以实现类型自身注册”的实例误判成其所有可赋值接口都已注册。
/// </remarks>
bool HasRegistration(Type type);
/// <summary> /// <summary>
/// 判断容器中是否包含某个具体的实例对象 /// 判断容器中是否包含某个具体的实例对象
/// </summary> /// </summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Lifecycle; namespace GFramework.Core.Abstractions.Lifecycle;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Lifecycle; namespace GFramework.Core.Abstractions.Lifecycle;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Lifecycle; namespace GFramework.Core.Abstractions.Lifecycle;
/// <summary> /// <summary>

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Lifecycle; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Lifecycle;
/// <summary> /// <summary>
/// 可销毁接口,为需要资源清理的组件提供标准销毁能力 /// 可销毁接口,为需要资源清理的组件提供标准销毁能力

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Lifecycle; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Lifecycle;
/// <summary> /// <summary>
/// 可初始化接口,为需要初始化的组件提供标准初始化能力 /// 可初始化接口,为需要初始化的组件提供标准初始化能力

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Lifecycle; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Lifecycle;
/// <summary> /// <summary>
/// 完整生命周期接口,组合了初始化和销毁能力 /// 完整生命周期接口,组合了初始化和销毁能力

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Localization; namespace GFramework.Core.Abstractions.Localization;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using System.Globalization; using System.Globalization;
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Localization; namespace GFramework.Core.Abstractions.Localization;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Localization; namespace GFramework.Core.Abstractions.Localization;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Localization; namespace GFramework.Core.Abstractions.Localization;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Localization; namespace GFramework.Core.Abstractions.Localization;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Localization; namespace GFramework.Core.Abstractions.Localization;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Localization; namespace GFramework.Core.Abstractions.Localization;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Logging; namespace GFramework.Core.Abstractions.Logging;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Logging; namespace GFramework.Core.Abstractions.Logging;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Logging; namespace GFramework.Core.Abstractions.Logging;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Logging; namespace GFramework.Core.Abstractions.Logging;
/// <summary> /// <summary>

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Logging; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Logging;
/// <summary> /// <summary>
/// 定义日志记录接口,提供日志记录和级别检查功能 /// 定义日志记录接口,提供日志记录和级别检查功能

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Logging; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Logging;
/// <summary> /// <summary>
/// 定义日志工厂接口,用于创建日志记录器实例 /// 定义日志工厂接口,用于创建日志记录器实例

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Logging; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Logging;
/// <summary> /// <summary>
/// 定义日志工厂提供者的接口,用于创建具有指定名称和最小日志级别的日志记录器 /// 定义日志工厂提供者的接口,用于创建具有指定名称和最小日志级别的日志记录器

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Logging; namespace GFramework.Core.Abstractions.Logging;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Logging; namespace GFramework.Core.Abstractions.Logging;
/// <summary> /// <summary>

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Logging; namespace GFramework.Core.Abstractions.Logging;
/// <summary> /// <summary>

View File

@ -1,4 +1,7 @@
namespace GFramework.Core.Abstractions.Logging; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Logging;
/// <summary> /// <summary>
/// 定义日志级别的枚举,用于标识不同严重程度的日志消息 /// 定义日志级别的枚举,用于标识不同严重程度的日志消息

View File

@ -1,3 +1,6 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
namespace GFramework.Core.Abstractions.Logging; namespace GFramework.Core.Abstractions.Logging;
/// <summary> /// <summary>

View File

@ -1,4 +1,7 @@
using GFramework.Core.Abstractions.Architectures; // Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Lifecycle; using GFramework.Core.Abstractions.Lifecycle;
using GFramework.Core.Abstractions.Rule; using GFramework.Core.Abstractions.Rule;

Some files were not shown because too many files have changed in this diff Show More