This is an automated email from the ASF dual-hosted git repository.
AlexStocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
The following commit(s) were added to refs/heads/develop by this push:
new 144d127e0 fix: update dubbogo CLI templates for Dubbo-Go 3.3 (#3587)
144d127e0 is described below
commit 144d127e099545880756f9d92efd15942f744a8a
Author: Xin.Zh <[email protected]>
AuthorDate: Sat Aug 1 14:16:23 2026 +0800
fix: update dubbogo CLI templates for Dubbo-Go 3.3 (#3587)
* fix: update dubbogo CLI templates for Dubbo-Go 3.3
Regenerate the embedded Triple stubs for the current client and server
APIs, update template dependencies, and compile generated projects in
regression tests.
Constraint: Limit the change to tools/dubbogo-cli templates, generated
fixtures, dependency metadata, tests, and matching README file names.
Confidence: High; generator-to-golden equality, direct generated-project
compilation, module tests, vet, tidy-diff, formatting, and staged diff checks
passed.
Scope-risk: Generated applications now require Go 1.24 and Dubbo-Go v3.3.1;
Linux was not locally exercised because WSL has no Go toolchain.
Red: TestGeneratedProjectsCompile failed on the removed logger package,
legacy NewGreeterClient signature, and legacy RegisterGreeterServer contract.
Green: CLI, newApp, and newDemo tests and vet passed; both generated
modules are tidy and compile against Dubbo-Go v3.3.1.
Tested: go test ./... and go vet ./... in tools/dubbogo-cli, newApp, and
newDemo; go mod tidy -diff for both generated modules; gofmt -l; git diff
--cached --check.
Not-tested: WSL/Linux because go is not installed in the available WSL
environment.
Co-authored-by: OmX <[email protected]>
Signed-off-by: Xin.Zh <[email protected]>
* fix: satisfy CLI template import formatting
Group the generated project logger import according to the repository
imports-formatter rules and keep generator sources synchronized with golden
files.
Constraint: Limit the change to the three affected generator templates and
their three golden outputs.
Confidence: High; the repository format steps produced no additional diff
after the import grouping change.
Scope-risk: Import layout only; generated application behavior and
dependencies are unchanged.
Red: The Linux CI Check Code Format step rewrote three golden files and
failed make check-fmt.
Green: The equivalent modernize, go fmt, imports-formatter, and CLI go fmt
chain leaves no unstaged diff.
Tested: go test ./... and go vet ./... in tools/dubbogo-cli; go vet ./...
and go mod tidy -diff in both generated modules; git diff --cached --check.
Not-tested: WSL/Linux locally because Go is not installed in WSL; GitHub
Actions will provide the Linux confirmation.
Co-authored-by: OmX <[email protected]>
Signed-off-by: Xin.Zh <[email protected]>
* docs: define PR 3587 reconciliation design
Signed-off-by: Xin.Zh <[email protected]>
* Document the approved PR 3587 implementation sequence
Record the exact merge conflict policy, TDD steps, Linux E2E coverage,
mutation proof, and race-safe push checks needed to reconcile the two CLI
template pull requests.
Constraint: Preserve the scaffold architecture already merged through PR
3585
Tested: plan placeholder scan and staged whitespace validation
Co-authored-by: OmX <[email protected]>
Signed-off-by: Xin.Zh <[email protected]>
* Pin generated CLI projects to the stable Dubbo-Go release
Replace the dated prerelease dependency in both generators and golden
projects while retaining the removed-logger regression guard.
Constraint: Keep the generated module language version at Go 1.25
Tested: targeted generator version logger and golden consistency tests
Related: Task 2/7
Co-authored-by: OmX <[email protected]>
Signed-off-by: Xin.Zh <[email protected]>
* Make fresh CLI scaffolds self-contained under make test
Generate protocol sources and tidy modules before compilation, then
exercise that public workflow against newly generated app and demo projects.
Constraint: Generated protobuf and Triple sources remain outside the
templates
Tested: DUBBOGO_CLI_E2E=1 generated-project compilation and golden
consistency tests
Related: Task 3/7
Co-authored-by: OmX <[email protected]>
Signed-off-by: Xin.Zh <[email protected]>
* Recognize wrapped EOF errors in generated stream handlers
Use errors.Is in both generator templates and golden outputs so wrapped
end-of-stream errors terminate normally instead of being returned as failures.
Constraint: Preserve the existing stream control flow outside EOF
classification
Tested: generated-output EOF assertions golden consistency and
fresh-project compilation
Related: Task 4/7
Co-authored-by: OmX <[email protected]>
Signed-off-by: Xin.Zh <[email protected]>
* Gate CLI scaffold compilation in Linux CI
Install the protocol generators used by fresh projects and run the explicit
generated-project E2E target before the existing repository checks.
Constraint: Ordinary CLI unit tests remain independent of a local protoc
toolchain
Tested: make generated-project target and environment-gated skip behavior
Related: Task 5/7
Co-authored-by: OmX <[email protected]>
Signed-off-by: Xin.Zh <[email protected]>
* Install protoc plugin from the locked module graph
Resolve protoc-gen-go through the root go.mod and go.sum with -mod=readonly
so the CI toolchain remains pinned to v1.34.2 without an inline version literal.
Constraint: Keep the generated-project toolchain reproducible and satisfy
SonarCloud githubactions:S8545 without suppressions
Tested: isolated GOBIN install plus make -C tools/dubbogo-cli
test-generated-projects
Related: PR #3587 SonarCloud follow-up
Co-authored-by: OmX <[email protected]>
Signed-off-by: Xin.Zh <[email protected]>
---------
Signed-off-by: Xin.Zh <[email protected]>
Co-authored-by: OmX <[email protected]>
---
.github/workflows/github-actions.yml | 12 +
.../plans/2026-08-01-unify-pr-3585-3587.md | 792 +++++++++++++++++++++
.../specs/2026-08-01-unify-pr-3585-3587-design.md | 221 ++++++
tools/dubbogo-cli/Makefile | 7 +-
tools/dubbogo-cli/cmd/gen_test.go | 96 +++
.../cmd/testGenCode/template/newApp/Makefile | 2 +-
.../cmd/testGenCode/template/newApp/go.mod | 2 +-
.../template/newApp/pkg/service/service.go | 3 +-
.../cmd/testGenCode/template/newDemo/Makefile | 2 +-
.../template/newDemo/go-server/cmd/server.go | 3 +-
.../cmd/testGenCode/template/newDemo/go.mod | 2 +-
tools/dubbogo-cli/generator/application/gomod.go | 2 +-
.../dubbogo-cli/generator/application/makefile.go | 2 +-
tools/dubbogo-cli/generator/application/pkg.go | 3 +-
tools/dubbogo-cli/generator/sample/gen_server.go | 3 +-
tools/dubbogo-cli/generator/sample/makefile.go | 2 +-
tools/dubbogo-cli/generator/sample/mod.go | 2 +-
17 files changed, 1143 insertions(+), 13 deletions(-)
diff --git a/.github/workflows/github-actions.yml
b/.github/workflows/github-actions.yml
index a8de57c2b..ca2c8a0ce 100644
--- a/.github/workflows/github-actions.yml
+++ b/.github/workflows/github-actions.yml
@@ -54,6 +54,18 @@ jobs:
restore-keys: |
${{ runner.os }}-go-
+ - name: Install generated-project toolchain
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y protobuf-compiler
+ go install -mod=readonly google.golang.org/protobuf/cmd/protoc-gen-go
+ cd tools/protoc-gen-go-triple
+ go install .
+ echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
+
+ - name: Test generated CLI projects
+ run: make -C tools/dubbogo-cli test-generated-projects
+
- name: Check Code Format
run: make check-fmt
diff --git a/docs/superpowers/plans/2026-08-01-unify-pr-3585-3587.md
b/docs/superpowers/plans/2026-08-01-unify-pr-3585-3587.md
new file mode 100644
index 000000000..1e6d8ba66
--- /dev/null
+++ b/docs/superpowers/plans/2026-08-01-unify-pr-3585-3587.md
@@ -0,0 +1,792 @@
+# PR #3587 统一 #3585/#3587 实现计划
+
+> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或
superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
+
+**目标:** 将 `develop@a62a44a266c63c00e9d22a2c018b4fdccae1dab1` 合入 PR
#3587,并在不恢复内嵌生成文件的前提下补齐稳定依赖、fresh scaffold 编译、EOF 和 CI 回归。
+
+**架构:** 合并冲突先全部收敛到 #3585 已进入 develop 的脚手架树,使合并 commit 的代码树相对 develop
只多设计/计划文档;随后用 TDD 分别加入稳定模块版本、真实 `make test` 生成项目验证和包装 EOF 处理。E2E
测试通过显式环境变量启用,普通单测不要求本地安装 protoc 工具链,GitHub Linux CI 则必须安装并执行完整链路。
+
+**技术栈:** Go 1.25、GNU Make、Protocol Buffers `protoc`、`protoc-gen-go`
v1.34.2、仓库内 `protoc-gen-go-triple`、GitHub Actions、PowerShell 与 WSL/Linux。
+
+---
+
+## 文件职责与变更结构
+
+-
`docs/superpowers/specs/2026-08-01-unify-pr-3585-3587-design.md`:已经批准的设计约束,不再修改其语义。
+- `docs/superpowers/plans/2026-08-01-unify-pr-3585-3587.md`:本实施计划。
+- `tools/dubbogo-cli/cmd/gen_test.go`:generator/golden 一致性、旧 logger、稳定版本、fresh
scaffold E2E 测试入口。
+- `tools/dubbogo-cli/generator/application/gomod.go`:newApp 的最小 `go.mod` 模板。
+- `tools/dubbogo-cli/generator/sample/mod.go`:newDemo 的最小 `go.mod` 模板。
+- `tools/dubbogo-cli/cmd/testGenCode/template/newApp/go.mod`:newApp golden 输出。
+- `tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go.mod`:newDemo golden
输出。
+- `tools/dubbogo-cli/generator/application/makefile.go`:newApp 的公开 Makefile 模板。
+- `tools/dubbogo-cli/generator/sample/makefile.go`:newDemo 的公开 Makefile 模板。
+- `tools/dubbogo-cli/cmd/testGenCode/template/newApp/Makefile`:newApp Makefile
golden 输出。
+- `tools/dubbogo-cli/cmd/testGenCode/template/newDemo/Makefile`:newDemo
Makefile golden 输出。
+- `tools/dubbogo-cli/generator/application/pkg.go`:newApp 服务实现模板。
+- `tools/dubbogo-cli/generator/sample/gen_server.go`:newDemo 服务端实现模板。
+-
`tools/dubbogo-cli/cmd/testGenCode/template/newApp/pkg/service/service.go`:newApp
服务实现 golden 输出。
+-
`tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go-server/cmd/server.go`:newDemo
服务端 golden 输出。
+- `tools/dubbogo-cli/Makefile`:本模块普通测试和显式 E2E target。
+- `.github/workflows/github-actions.yml`:Linux CI 安装协议生成工具链并运行 E2E。
+
+### 任务 1:合并 develop,并将冲突树收敛到 #3585 架构
+
+**文件:**
+- 保留 develop:`tools/dubbogo-cli/README.md`
+- 保留 develop:`tools/dubbogo-cli/README_CN.md`
+- 保留 develop:`tools/dubbogo-cli/cmd/gen_test.go`
+- 保留 develop:`tools/dubbogo-cli/cmd/testGenCode/template/newApp/cmd/app.go`
+- 保留 develop:`tools/dubbogo-cli/cmd/testGenCode/template/newApp/go.mod`
+- 保留
develop:`tools/dubbogo-cli/cmd/testGenCode/template/newApp/pkg/service/service.go`
+- 保留
develop:`tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go-client/cmd/client.go`
+- 保留
develop:`tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go-server/cmd/server.go`
+- 保留 develop:`tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go.mod`
+- 保留 develop:`tools/dubbogo-cli/generator/application/api.go`
+- 保留 develop:`tools/dubbogo-cli/generator/application/cmd.go`
+- 保留 develop:`tools/dubbogo-cli/generator/application/gomod.go`
+- 保留 develop:`tools/dubbogo-cli/generator/application/pkg.go`
+- 保留 develop:`tools/dubbogo-cli/generator/sample/gen_client.go`
+- 保留 develop:`tools/dubbogo-cli/generator/sample/gen_server.go`
+- 保留 develop:`tools/dubbogo-cli/generator/sample/mod.go`
+- 删除:`tools/dubbogo-cli/cmd/testGenCode/template/newApp/api/api.triple.go`
+-
删除:`tools/dubbogo-cli/cmd/testGenCode/template/newDemo/api/samples_api.triple.go`
+- 删除:`tools/dubbogo-cli/cmd/testGenCode/template/newApp/go.sum`
+- 删除:`tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go.sum`
+- 删除:`tools/dubbogo-cli/generator/internal/scaffold/gosum.go`
+- 删除:`tools/dubbogo-cli/generator/sample/api_tripe.go`
+
+- [ ] **步骤 1:重新固定远端 Head 和 develop**
+
+运行:
+
+```powershell
+gh pr view 3587 -R apache/dubbo-go --json
state,baseRefName,baseRefOid,headRefName,headRefOid
+git ls-remote origin refs/heads/develop
refs/heads/codex/fix-cli-template-logger
+```
+
+预期:PR 仍为 `OPEN`,PR Head 仍为 `23e50e5152c58d197317351dc835865ce26242a1`;远端
develop 为 `a62a44a266c63c00e9d22a2c018b4fdccae1dab1`。
+
+- [ ] **步骤 2:获取最新远端 refs**
+
+运行:
+
+```powershell
+git fetch --no-tags origin `
+ refs/heads/develop:refs/remotes/origin/develop `
+
refs/heads/codex/fix-cli-template-logger:refs/remotes/origin/codex/fix-cli-template-logger
+```
+
+预期:本地 `origin/develop` 与 `origin/codex/fix-cli-template-logger` 分别等于上一步固定的 OID。
+
+- [ ] **步骤 3:开始不自动提交的 merge**
+
+运行:
+
+```powershell
+git merge --no-ff --no-commit origin/develop
+```
+
+预期:命令因 `dubbogo-cli` 重叠实现返回冲突,且 `git diff --name-only --diff-filter=U` 包含
README、模板 go.mod、模板服务实现、generator 模板和 modify/delete 文件。
+
+- [ ] **步骤 4:把所有重叠实现恢复到 develop 版本**
+
+运行:
+
+```powershell
+git restore --source=origin/develop --staged --worktree -- `
+ tools/dubbogo-cli/README.md `
+ tools/dubbogo-cli/README_CN.md `
+ tools/dubbogo-cli/cmd/gen_test.go `
+ tools/dubbogo-cli/cmd/testGenCode/template/newApp/cmd/app.go `
+ tools/dubbogo-cli/cmd/testGenCode/template/newApp/go.mod `
+ tools/dubbogo-cli/cmd/testGenCode/template/newApp/pkg/service/service.go `
+ tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go-client/cmd/client.go `
+ tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go-server/cmd/server.go `
+ tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go.mod `
+ tools/dubbogo-cli/generator/application/api.go `
+ tools/dubbogo-cli/generator/application/cmd.go `
+ tools/dubbogo-cli/generator/application/gomod.go `
+ tools/dubbogo-cli/generator/application/pkg.go `
+ tools/dubbogo-cli/generator/sample/gen_client.go `
+ tools/dubbogo-cli/generator/sample/gen_server.go `
+ tools/dubbogo-cli/generator/sample/mod.go
+
+git rm -- `
+ tools/dubbogo-cli/cmd/testGenCode/template/newApp/api/api.triple.go `
+ tools/dubbogo-cli/cmd/testGenCode/template/newDemo/api/samples_api.triple.go
`
+ tools/dubbogo-cli/cmd/testGenCode/template/newApp/go.sum `
+ tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go.sum `
+ tools/dubbogo-cli/generator/internal/scaffold/gosum.go `
+ tools/dubbogo-cli/generator/sample/api_tripe.go
+```
+
+预期:`git diff --name-only --diff-filter=U` 无输出;相对 `origin/develop`
的暂存树只包含已经提交到当前分支的设计和计划文档。
+
+- [ ] **步骤 5:验证合并树没有恢复内嵌产物**
+
+运行:
+
+```powershell
+git diff --cached --name-status origin/develop
+rg -n
'api\.triple\.go|samples_api\.triple\.go|generator/internal/scaffold/gosum'
tools/dubbogo-cli
+git diff --check
+```
+
+预期:第一个命令只列 `docs/superpowers/specs/...` 和 `docs/superpowers/plans/...`;`rg`
不应在 generator/golden 文件中找到被删除产物,退出码可以为 1;`git diff --check` 退出 0。
+
+- [ ] **步骤 6:提交 merge commit**
+
+运行:
+
+```powershell
+git commit -s `
+ -m "Synchronize PR 3587 with the merged scaffold architecture" `
+ -m "Merge the latest develop history while resolving all overlapping CLI
scaffold files to the architecture already accepted through PR 3585." `
+ -m "Constraint: Do not restore embedded generated sources or template go.sum
files" `
+ -m "Tested: staged tree matches origin/develop except the approved design
and plan documents" `
+ -m "Co-authored-by: OmX <[email protected]>"
+git show -s --format='%H %P %s' HEAD
+```
+
+预期:commit 有两个父提交,其中一个祖先链包含 `a62a44a266c63c00e9d22a2c018b4fdccae1dab1`。
+
+### 任务 2:用失败测试固定稳定模块版本和旧 logger 防线
+
+**文件:**
+- 修改:`tools/dubbogo-cli/cmd/gen_test.go:20-95`
+- 修改:`tools/dubbogo-cli/generator/application/gomod.go:23-27`
+- 修改:`tools/dubbogo-cli/generator/sample/mod.go:23-27`
+- 修改:`tools/dubbogo-cli/cmd/testGenCode/template/newApp/go.mod:3-7`
+- 修改:`tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go.mod:3-7`
+
+- [ ] **步骤 1:添加共享 generated-project case 和版本失败测试**
+
+在 `tools/dubbogo-cli/cmd/gen_test.go` 增加:
+
+```go
+type generatedProjectCase struct {
+ name string
+ generate func(string) error
+}
+
+func generatedProjectCases() []generatedProjectCase {
+ return []generatedProjectCase{
+ {name: "newApp", generate: application.Generate},
+ {name: "newDemo", generate: sample.Generate},
+ }
+}
+
+func TestGeneratedModuleVersion(t *testing.T) {
+ const stableVersion = "dubbo.apache.org/dubbo-go/v3 v3.3.1"
+ const datedPrerelease = "v3.3.1-20260727"
+
+ for _, tc := range generatedProjectCases() {
+ t.Run(tc.name, func(t *testing.T) {
+ genPath := filepath.Join(t.TempDir(), tc.name)
+ require.NoError(t, tc.generate(genPath))
+
+ content, err := os.ReadFile(filepath.Join(genPath,
"go.mod"))
+ require.NoError(t, err)
+ require.Contains(t, string(content), stableVersion)
+ require.NotContains(t, string(content), datedPrerelease)
+ })
+ }
+}
+```
+
+同时保留 #3587 的旧 logger 扫描,内容固定为:
+
+```go
+func TestGeneratedCodeDoesNotImportRemovedLoggerPackage(t *testing.T) {
+ const removedLoggerImport =
`"dubbo.apache.org/dubbo-go/v3/common/logger"`
+
+ for _, dir := range []string{"../generator", "./testGenCode/template"} {
+ files, err := walkDir(dir)
+ require.NoError(t, err, "iterate generated code sources failed:
%s", dir)
+
+ for _, file := range files {
+ if filepath.Ext(file) != ".go" {
+ continue
+ }
+
+ content, err := os.ReadFile(file)
+ require.NoError(t, err, "read generated code source
failed: %s", file)
+ require.NotContains(t, string(content),
removedLoggerImport,
+ "generated code must not import the removed
logger package: %s", file)
+ }
+ }
+}
+```
+
+- [ ] **步骤 2:运行版本测试并验证红灯**
+
+运行:
+
+```bash
+cd tools/dubbogo-cli
+go test ./cmd -run
'TestGeneratedModuleVersion|TestGeneratedCodeDoesNotImportRemovedLoggerPackage'
-count=1 -v
+```
+
+预期:logger 测试通过,`TestGeneratedModuleVersion/newApp` 和 `/newDemo` 因实际内容仍为
`v3.3.1-20260727` 而失败。
+
+- [ ] **步骤 3:最小化修改四个 go.mod 模板**
+
+将以下四个文件中的:
+
+```text
+dubbo.apache.org/dubbo-go/v3 v3.3.1-20260727
+```
+
+统一替换为:
+
+```text
+dubbo.apache.org/dubbo-go/v3 v3.3.1
+```
+
+修改文件:
+
+```text
+tools/dubbogo-cli/generator/application/gomod.go
+tools/dubbogo-cli/generator/sample/mod.go
+tools/dubbogo-cli/cmd/testGenCode/template/newApp/go.mod
+tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go.mod
+```
+
+- [ ] **步骤 4:运行定向测试并验证绿灯**
+
+运行:
+
+```bash
+cd tools/dubbogo-cli
+go test ./cmd -run
'TestNewApp|TestNewDemo|TestGeneratedModuleVersion|TestGeneratedCodeDoesNotImportRemovedLoggerPackage'
-count=1 -v
+```
+
+预期:四组测试全部通过,且 `TestNewApp`/`TestNewDemo` 证明 generator 与 golden 同步。
+
+- [ ] **步骤 5:提交稳定版本和 logger 回归**
+
+运行:
+
+```powershell
+git add -- `
+ tools/dubbogo-cli/cmd/gen_test.go `
+ tools/dubbogo-cli/generator/application/gomod.go `
+ tools/dubbogo-cli/generator/sample/mod.go `
+ tools/dubbogo-cli/cmd/testGenCode/template/newApp/go.mod `
+ tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go.mod
+git commit -s `
+ -m "Pin generated CLI projects to the stable Dubbo-Go release" `
+ -m "Replace the dated prerelease dependency in both generators and golden
projects while retaining the removed-logger regression guard." `
+ -m "Constraint: Keep the generated module language version at Go 1.25" `
+ -m "Tested: targeted generator version logger and golden consistency tests" `
+ -m "Co-authored-by: OmX <[email protected]>"
+```
+
+### 任务 3:用 fresh scaffold E2E 修复 `make test`
+
+**文件:**
+- 修改:`tools/dubbogo-cli/cmd/gen_test.go`
+- 修改:`tools/dubbogo-cli/generator/application/makefile.go:79-80`
+- 修改:`tools/dubbogo-cli/generator/sample/makefile.go:42-43`
+- 修改:`tools/dubbogo-cli/cmd/testGenCode/template/newApp/Makefile:59-60`
+- 修改:`tools/dubbogo-cli/cmd/testGenCode/template/newDemo/Makefile:22-23`
+
+- [ ] **步骤 1:添加环境门禁和真实 `make test` E2E**
+
+在 `tools/dubbogo-cli/cmd/gen_test.go` 导入 `os/exec`,并增加:
+
+```go
+func TestGeneratedProjectsCompile(t *testing.T) {
+ if os.Getenv("DUBBOGO_CLI_E2E") != "1" {
+ t.Skip("set DUBBOGO_CLI_E2E=1 to run generated-project
compilation")
+ }
+
+ for _, tc := range generatedProjectCases() {
+ t.Run(tc.name, func(t *testing.T) {
+ genPath := filepath.Join(t.TempDir(), tc.name)
+ require.NoError(t, tc.generate(genPath))
+
+ cmd := exec.Command("make", "test")
+ cmd.Dir = genPath
+ cmd.Env = append(os.Environ(), "GOWORK=off")
+ output, err := cmd.CombinedOutput()
+ require.NoError(t, err, "generated project make test
failed:\n%s", output)
+ })
+ }
+}
+```
+
+- [ ] **步骤 2:准备 WSL E2E 工具链**
+
+运行:
+
+```bash
+export GOTOOLCHAIN=go1.25.0+auto
+export PATH="$(go env GOPATH)/bin:$PATH"
+protoc --version
+go install google.golang.org/protobuf/cmd/[email protected]
+(cd tools/protoc-gen-go-triple && go install .)
+protoc-gen-go --version
+protoc-gen-go-triple --version
+```
+
+预期:WSL 现有 `protoc` 输出 `libprotoc 3.21.12`;两个插件均能从 PATH 执行。
+
+- [ ] **步骤 3:运行 E2E 并验证红灯**
+
+运行:
+
+```bash
+cd tools/dubbogo-cli
+DUBBOGO_CLI_E2E=1 go test ./cmd -run '^TestGeneratedProjectsCompile$' -count=1
-v
+```
+
+预期:`newApp`、`newDemo` 至少一个因 fresh scaffold 的 `make test` 没有先执行
`proto-gen`/`tidy` 而失败;输出不得是 `protoc` 或插件不存在。
+
+- [ ] **步骤 4:最小化修复四个 Makefile 模板**
+
+把四个文件中的:
+
+```makefile
+test:
+ go test ./...
+```
+
+改为:
+
+```makefile
+test: proto-gen tidy
+ go test ./...
+```
+
+修改文件:
+
+```text
+tools/dubbogo-cli/generator/application/makefile.go
+tools/dubbogo-cli/generator/sample/makefile.go
+tools/dubbogo-cli/cmd/testGenCode/template/newApp/Makefile
+tools/dubbogo-cli/cmd/testGenCode/template/newDemo/Makefile
+```
+
+- [ ] **步骤 5:运行 E2E 并验证绿灯**
+
+运行:
+
+```bash
+cd tools/dubbogo-cli
+DUBBOGO_CLI_E2E=1 go test ./cmd -run '^TestGeneratedProjectsCompile$' -count=1
-v
+```
+
+预期:输出明确显示 `TestGeneratedProjectsCompile/newApp` 和 `/newDemo` 均通过。
+
+- [ ] **步骤 6:运行 generator/golden 一致性测试**
+
+运行:
+
+```bash
+cd tools/dubbogo-cli
+go test ./cmd -run 'TestNewApp|TestNewDemo' -count=1 -v
+```
+
+预期:两个测试均通过。
+
+- [ ] **步骤 7:提交 fresh scaffold 修复**
+
+运行:
+
+```powershell
+git add -- `
+ tools/dubbogo-cli/cmd/gen_test.go `
+ tools/dubbogo-cli/generator/application/makefile.go `
+ tools/dubbogo-cli/generator/sample/makefile.go `
+ tools/dubbogo-cli/cmd/testGenCode/template/newApp/Makefile `
+ tools/dubbogo-cli/cmd/testGenCode/template/newDemo/Makefile
+git commit -s `
+ -m "Make fresh CLI scaffolds self-contained under make test" `
+ -m "Generate protocol sources and tidy modules before compilation, then
exercise that public workflow against newly generated app and demo projects." `
+ -m "Constraint: Generated protobuf and Triple sources remain outside the
templates" `
+ -m "Tested: DUBBOGO_CLI_E2E=1 generated-project compilation and golden
consistency tests" `
+ -m "Co-authored-by: OmX <[email protected]>"
+```
+
+### 任务 4:用生成输出测试修复包装 EOF
+
+**文件:**
+- 修改:`tools/dubbogo-cli/cmd/gen_test.go`
+- 修改:`tools/dubbogo-cli/generator/application/pkg.go:39-67`
+- 修改:`tools/dubbogo-cli/generator/sample/gen_server.go:23-59`
+-
修改:`tools/dubbogo-cli/cmd/testGenCode/template/newApp/pkg/service/service.go:20-49`
+-
修改:`tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go-server/cmd/server.go:20-55`
+
+- [ ] **步骤 1:添加真实生成输出断言**
+
+在 `tools/dubbogo-cli/cmd/gen_test.go` 增加:
+
+```go
+func TestGeneratedStreamHandlersHandleWrappedEOF(t *testing.T) {
+ testCases := []struct {
+ generatedProjectCase
+ serverFile string
+ }{
+ {
+ generatedProjectCase: generatedProjectCase{name:
"newApp", generate: application.Generate},
+ serverFile: "pkg/service/service.go",
+ },
+ {
+ generatedProjectCase: generatedProjectCase{name:
"newDemo", generate: sample.Generate},
+ serverFile: "go-server/cmd/server.go",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ genPath := filepath.Join(t.TempDir(), tc.name)
+ require.NoError(t, tc.generate(genPath))
+
+ content, err := os.ReadFile(filepath.Join(genPath,
tc.serverFile))
+ require.NoError(t, err)
+ require.Contains(t, string(content), "errors.Is(err,
io.EOF)")
+ })
+ }
+}
+```
+
+- [ ] **步骤 2:运行 EOF 测试并验证红灯**
+
+运行:
+
+```bash
+cd tools/dubbogo-cli
+go test ./cmd -run '^TestGeneratedStreamHandlersHandleWrappedEOF$' -count=1 -v
+```
+
+预期:newApp 和 newDemo 都因生成内容仍为 `err == io.EOF` 而失败。
+
+- [ ] **步骤 3:最小化修改 generator 和 golden 文件**
+
+四个 Go 文件增加标准库 `errors` import,并将:
+
+```go
+if err == io.EOF {
+```
+
+改为:
+
+```go
+if errors.Is(err, io.EOF) {
+```
+
+修改文件:
+
+```text
+tools/dubbogo-cli/generator/application/pkg.go
+tools/dubbogo-cli/generator/sample/gen_server.go
+tools/dubbogo-cli/cmd/testGenCode/template/newApp/pkg/service/service.go
+tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go-server/cmd/server.go
+```
+
+- [ ] **步骤 4:格式化定向 Go 文件**
+
+运行:
+
+```bash
+gofmt -w \
+ tools/dubbogo-cli/cmd/gen_test.go \
+ tools/dubbogo-cli/generator/application/pkg.go \
+ tools/dubbogo-cli/generator/sample/gen_server.go \
+ tools/dubbogo-cli/cmd/testGenCode/template/newApp/pkg/service/service.go \
+ tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go-server/cmd/server.go
+```
+
+预期:只修改上述文件的 Go 格式和 import 排序。
+
+- [ ] **步骤 5:运行 EOF 和 generator/golden 测试并验证绿灯**
+
+运行:
+
+```bash
+cd tools/dubbogo-cli
+go test ./cmd -run
'TestGeneratedStreamHandlersHandleWrappedEOF|TestNewApp|TestNewDemo' -count=1 -v
+```
+
+预期:全部通过。
+
+- [ ] **步骤 6:提交 EOF 修复**
+
+运行:
+
+```powershell
+git add -- `
+ tools/dubbogo-cli/cmd/gen_test.go `
+ tools/dubbogo-cli/generator/application/pkg.go `
+ tools/dubbogo-cli/generator/sample/gen_server.go `
+ tools/dubbogo-cli/cmd/testGenCode/template/newApp/pkg/service/service.go `
+ tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go-server/cmd/server.go
+git commit -s `
+ -m "Recognize wrapped EOF errors in generated stream handlers" `
+ -m "Use errors.Is in both generator templates and golden outputs so wrapped
end-of-stream errors terminate normally instead of being logged as failures." `
+ -m "Constraint: Preserve the existing stream control flow outside EOF
classification" `
+ -m "Tested: generated-output EOF assertions and golden consistency tests" `
+ -m "Co-authored-by: OmX <[email protected]>"
+```
+
+### 任务 5:把 E2E 工具链接入 Makefile 和 Linux CI
+
+**文件:**
+- 修改:`tools/dubbogo-cli/Makefile:16-17`
+- 修改:`.github/workflows/github-actions.yml:38-60`
+
+- [ ] **步骤 1:增加固定 Make target**
+
+将 `tools/dubbogo-cli/Makefile` 改为:
+
+```makefile
+.PHONY: test test-generated-projects
+
+test:
+ go test ./...
+
+test-generated-projects:
+ DUBBOGO_CLI_E2E=1 go test ./cmd -run '^TestGeneratedProjectsCompile$$'
-count=1 -v
+```
+
+Make 中的正则结束符使用 `$$`,确保传给 shell 的参数为单个 `$`。
+
+- [ ] **步骤 2:在 Linux CI 增加协议工具链和 E2E step**
+
+在 `.github/workflows/github-actions.yml` 的 CI job 中,放在依赖缓存之后、`Check Code
Format` 之前:
+
+```yaml
+ - name: Install generated-project toolchain
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y protobuf-compiler
+ go install google.golang.org/protobuf/cmd/[email protected]
+ cd tools/protoc-gen-go-triple
+ go install .
+ echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
+
+ - name: Test generated CLI projects
+ run: make -C tools/dubbogo-cli test-generated-projects
+```
+
+- [ ] **步骤 3:运行 Make target**
+
+运行:
+
+```bash
+export PATH="$(go env GOPATH)/bin:$PATH"
+make -C tools/dubbogo-cli test-generated-projects
+```
+
+预期:输出显示 newApp/newDemo 子测试通过。
+
+- [ ] **步骤 4:检查 workflow 结构和 Diff**
+
+运行:
+
+```bash
+git diff --check
+git diff -- .github/workflows/github-actions.yml tools/dubbogo-cli/Makefile
+```
+
+预期:YAML 缩进与相邻 steps 一致,Make recipe 使用 tab,`git diff --check` 退出 0。
+
+- [ ] **步骤 5:提交 CI 接入**
+
+运行:
+
+```powershell
+git add -- tools/dubbogo-cli/Makefile .github/workflows/github-actions.yml
+git commit -s `
+ -m "Gate CLI scaffold compilation in Linux CI" `
+ -m "Install the protocol generators used by fresh projects and run the
explicit generated-project E2E target before the existing repository checks." `
+ -m "Constraint: Ordinary CLI unit tests remain independent of a local protoc
toolchain" `
+ -m "Tested: make -C tools/dubbogo-cli test-generated-projects and workflow
diff checks" `
+ -m "Co-authored-by: OmX <[email protected]>"
+```
+
+### 任务 6:执行完整 Linux 验证和变异式反证
+
+**文件:**
+- 验证:整个仓库
+- 探针:`D:\test\github\review\apache-dubbo-go-pr-3587\probes\e2e-mutation`
+
+- [ ] **步骤 1:记录验证前 Git 状态**
+
+运行:
+
+```powershell
+git status --porcelain=v2 --branch --untracked-files=all
+git log --oneline --decorate -8
+```
+
+预期:工作树干净,分支只领先远端本任务 commits。
+
+- [ ] **步骤 2:运行 CLI 定向和完整验证**
+
+在 WSL 中运行:
+
+```bash
+set -euo pipefail
+export GOTOOLCHAIN=go1.25.0+auto
+export PATH="$(go env GOPATH)/bin:$PATH"
+
+cd tools/dubbogo-cli
+go test ./cmd -run
'TestNewApp|TestNewDemo|TestGeneratedModuleVersion|TestGeneratedCodeDoesNotImportRemovedLoggerPackage|TestGeneratedProjectsCompile|TestGeneratedStreamHandlersHandleWrappedEOF'
-count=1 -v
+go test ./...
+go vet ./...
+go mod tidy -diff
+make test-generated-projects
+```
+
+预期:所有命令退出 0;普通 `go test ./...` 中 E2E 明确 Skip,显式 Make target 中 newApp/newDemo
明确执行并通过;`go mod tidy -diff` 无输出。
+
+- [ ] **步骤 3:运行根模块完整测试**
+
+在 WSL 仓库根目录运行:
+
+```bash
+GOTOOLCHAIN=go1.25.0+auto go test ./...
+```
+
+预期:退出 0,无失败包。
+
+- [ ] **步骤 4:运行 Linux 格式门禁**
+
+运行前记录:
+
+```powershell
+git status --porcelain=v2 --branch --untracked-files=all
+```
+
+在 WSL 仓库根目录运行:
+
+```bash
+make check-fmt
+```
+
+预期:退出 0,且命令后 `git status --short` 仍然干净。若只对本任务文件产生格式差异,使用同样的 Lore 正文、`Tested:`
trailer、OmX co-author trailer 和 `-s` 提交独立格式修正;若修改无关文件,停止并定位工具版本或基线问题。
+
+- [ ] **步骤 5:创建隔离变异探针**
+
+运行:
+
+```powershell
+$probe='D:\test\github\review\apache-dubbo-go-pr-3587\probes\e2e-mutation'
+if(Test-Path $probe){ throw "Probe target already exists: $probe" }
+git worktree add --detach $probe HEAD
+```
+
+在探针中只把两个 generator Makefile 和两个 golden Makefile 的:
+
+```makefile
+test: proto-gen tidy
+```
+
+变异回:
+
+```makefile
+test:
+```
+
+不得 commit。
+
+- [ ] **步骤 6:运行变异探针并验证测试失败**
+
+在 WSL 探针目录运行:
+
+```bash
+export PATH="$(go env GOPATH)/bin:$PATH"
+make -C tools/dubbogo-cli test-generated-projects
+```
+
+预期:命令非零退出,newApp/newDemo 因 fresh scaffold 未执行 `proto-gen`/`tidy` 而失败。该输出证明 E2E
能检测被修复问题。
+
+- [ ] **步骤 7:安全移除探针**
+
+先验证探针绝对路径位于:
+
+```text
+D:\test\github\review\apache-dubbo-go-pr-3587\probes
+```
+
+然后运行:
+
+```powershell
+git worktree remove --force
'D:\test\github\review\apache-dubbo-go-pr-3587\probes\e2e-mutation'
+```
+
+预期:只删除未提交的隔离探针;主工作树和分支不变。
+
+- [ ] **步骤 8:最终本地状态检查**
+
+运行:
+
+```powershell
+git diff --check
+git status --short --branch
+git log --oneline --decorate --graph -10
+```
+
+预期:主工作树干净,历史包含设计、计划、merge 和各项修复 commits。
+
+### 任务 7:防竞态 push,并复检 PR
+
+**文件:**
+- 外部状态:`apache/dubbo-go` PR #3587
+
+- [ ] **步骤 1:重新获取 GitHub Head 和远端分支**
+
+运行:
+
+```powershell
+gh pr view 3587 -R apache/dubbo-go --json
state,baseRefName,baseRefOid,headRefName,headRefOid,mergeable,mergeStateStatus,reviewDecision
+git ls-remote origin refs/heads/codex/fix-cli-template-logger
refs/heads/develop
+git rev-parse refs/remotes/origin/codex/fix-cli-template-logger
+```
+
+预期:GitHub/远端 PR Head 仍为本轮开始的
`23e50e5152c58d197317351dc835865ce26242a1`;develop 不早于
`a62a44a266c63c00e9d22a2c018b4fdccae1dab1`。若 PR Head 已变化,停止 push,fetch
后重新审查新增提交。
+
+- [ ] **步骤 2:普通 push,不使用 force**
+
+运行:
+
+```powershell
+git push origin codex/fix-cli-template-logger
+```
+
+预期:fast-forward push 成功。
+
+- [ ] **步骤 3:复检新 Head 和 Diff**
+
+运行:
+
+```powershell
+gh pr view 3587 -R apache/dubbo-go --json
state,baseRefName,baseRefOid,headRefName,headRefOid,mergeable,mergeStateStatus,reviewDecision,potentialMergeCommit,statusCheckRollup
+gh pr diff 3587 -R apache/dubbo-go --name-only
+```
+
+预期:Head 等于本地 `HEAD`,PR 相对最新 develop 只保留设计/计划文档、CLI 模板/测试、Makefile 和 CI
workflow 的统一改动;无内嵌生成文件或模板 go.sum。
+
+- [ ] **步骤 4:等待 GitHub checks**
+
+运行:
+
+```powershell
+gh pr checks 3587 -R apache/dubbo-go --watch --interval 10
+```
+
+预期:新 Head 的 CI、Integration Test、License、RISC-V、CodeQL 等已触发 checks
最终完成;任何失败都必须读取对应日志并区分 PR 回归、基线或环境问题。
+
+- [ ] **步骤 5:最终 Head/状态复检**
+
+运行:
+
+```powershell
+gh pr view 3587 -R apache/dubbo-go --json
state,baseRefName,baseRefOid,headRefName,headRefOid,mergeable,mergeStateStatus,reviewDecision,potentialMergeCommit,statusCheckRollup
+gh pr checks 3587 -R apache/dubbo-go --required --json
name,state,bucket,link,workflow,event
+```
+
+预期:Head 未漂移;若 required checks 仍未报告,明确记录该证据缺口,不据此声称完整可 Merge。
diff --git a/docs/superpowers/specs/2026-08-01-unify-pr-3585-3587-design.md
b/docs/superpowers/specs/2026-08-01-unify-pr-3585-3587-design.md
new file mode 100644
index 000000000..eb5fd5ab4
--- /dev/null
+++ b/docs/superpowers/specs/2026-08-01-unify-pr-3585-3587-design.md
@@ -0,0 +1,221 @@
+# PR #3587 合并 develop 并统一 #3585/#3587 的设计
+
+## 背景与固定基线
+
+- 仓库:`apache/dubbo-go`
+- PR:`#3587`
+- PR 分支:`codex/fix-cli-template-logger`
+- 本设计开始时的 PR Head:`23e50e5152c58d197317351dc835865ce26242a1`
+- 最新 `develop`:`a62a44a266c63c00e9d22a2c018b4fdccae1dab1`
+- `#3585` 已通过上述 commit 合入 `develop`
+- 隔离工作区:`D:\test\github\review\apache-dubbo-go-pr-3587\source`
+
+当前用户工作区 `D:\test\github\dubbo-go` 存在未提交文件,本任务不得在该目录
checkout、merge、格式化或测试。所有实现、测试、提交和 push 都在隔离工作区完成。
+
+## 目标
+
+将最新 `develop` 以普通 merge commit 合入 `codex/fix-cli-template-logger`,以 #3585
已合入的脚手架架构为主,保留 #3587 中仍然有效的回归保护和错误处理,最终让 #3587 只包含相对最新 `develop` 仍有价值的统一修复。
+
+完成后应满足:
+
+1. PR 分支包含 `develop@a62a44a266c63c00e9d22a2c018b4fdccae1dab1`。
+2. 不恢复 #3585 已删除的内嵌 `*.pb.go`、`*.triple.go`、模板 `go.sum` 或 generator 内嵌生成代码。
+3. 新生成的 `newApp` 和 `newDemo` 在完整工具链环境中可以从空生成目录通过 `make test`。
+4. 模板不引用已删除的 `dubbo.apache.org/dubbo-go/v3/common/logger`。
+5. 模板使用已发布的 `dubbo-go v3.3.1`,而不是日期预发布标签。
+6. 双向流模板使用可识别包装错误的 EOF 判断。
+7. Linux 下完成格式、单测、vet、生成项目编译和依赖整洁性验证。
+8. 不使用 rebase 或 force-push;最终只做普通 push 更新现有 PR。
+
+## 合并与冲突解决策略
+
+执行:
+
+```text
+git merge --no-ff origin/develop
+```
+
+冲突解决遵循以下所有权:
+
+### 以 develop/#3585 为准
+
+- `tools/dubbogo-cli/generator/application/api.go` 中删除内嵌生成代码的设计。
+- 删除 `tools/dubbogo-cli/generator/sample/api.go`。
+- 删除 `tools/dubbogo-cli/generator/sample/api_tripe.go`。
+- 删除 `tools/dubbogo-cli/generator/internal/scaffold/gosum.go`。
+- 删除模板内 `api.pb.go`、`samples_api.pb.go`、旧 Triple 文件和 `go.sum`。
+- `Makefile` 驱动 `protoc`、`protoc-gen-go` 和 `protoc-gen-go-triple` 的生成流程。
+- Go 1.25 基线。
+- 示例日志使用标准库 `log`,不为示例日志重新增加 `gost` 直接依赖。
+- 保留 `tri://localhost:20000`。当前 `client.processUserSpecifiedURLs` 明确将该格式列为支持的
direct URL。
+- #3585 对 generator 文件关闭、临时目录生成和全局模板路径隔离的修复。
+
+### 从 #3587 保留或重新实现
+
+- 生成结果不得包含旧 `common/logger` import 的回归检查。
+- 自动编译实际生成项目的回归验证,但改为适配 #3585 的“先生成协议代码”架构。
+- 生成模板的 Dubbo-Go 依赖统一为稳定版 `v3.3.1`。
+- 流式 handler 使用 `errors.Is(err, io.EOF)`。
+
+### 不保留
+
+- #3587 新增的内嵌 `api.triple.go` 和 `samples_api.triple.go`。
+- #3587 更新的模板 `go.sum` 和 generator `gosum.go`。
+- #3587 内嵌的新 Triple 生成代码字符串。
+- 仅为日志增加的 `github.com/dubbogo/gost` 直接依赖。
+- 将合法的 `tri://localhost:20000` 改为裸地址的变更。
+
+## 生产行为设计
+
+### 新生成项目的 `make test`
+
+#3585 合入后的 fresh scaffold 不包含生成的 PB/Triple 源码,但模板中的 `test` target 只运行 `go test
./...`。因此用户在刚执行 `newApp` 或 `newDemo` 后直接运行 `make test` 会在协议源码尚未生成时失败。
+
+两个模板统一改为:
+
+```makefile
+test: proto-gen tidy
+ go test ./...
+```
+
+对应修改:
+
+- `tools/dubbogo-cli/generator/application/makefile.go`
+- `tools/dubbogo-cli/generator/sample/makefile.go`
+- `tools/dubbogo-cli/cmd/testGenCode/template/newApp/Makefile`
+- `tools/dubbogo-cli/cmd/testGenCode/template/newDemo/Makefile`
+
+这使 `make test` 成为生成项目的完整可执行入口,同时保持 `proto-gen`、`tidy` 和 `test` 仍可独立调用。
+
+### 模板依赖版本
+
+`v3.3.1` 是当前已发布的稳定版本;`v3.3.1-20260727` 是指向旧 develop 基线的日期标签。面向新用户的模板统一使用:
+
+```text
+dubbo.apache.org/dubbo-go/v3 v3.3.1
+```
+
+Go 版本继续使用 #3585 设定的 `go 1.25`,与当前仓库 Makefile 的 `GOTOOLCHAIN=go1.25.0+auto` 一致。
+
+### EOF 处理
+
+application 和 demo server 模板中的双向流循环使用:
+
+```go
+if errors.Is(err, io.EOF) {
+ return nil
+}
+```
+
+这样能同时处理直接的 `io.EOF` 和被底层包装的 EOF。需要同步 generator 源和 golden template。
+
+## 测试设计
+
+### TDD 红灯
+
+在修改模板 Makefile 前,先增加一个 Linux E2E 测试:
+
+1. 使用真实 `application.Generate` 或 `sample.Generate` 写入 `t.TempDir()`。
+2. 在 fresh scaffold 中执行 `make test`。
+3. 捕获完整输出,失败时包含项目类型和命令输出。
+
+在当前 develop 行为上,该测试应因为 fresh scaffold 尚未生成 PB/Triple
源码而失败。必须保存该失败输出,确认失败原因不是工具缺失或网络问题。
+
+随后只修改模板 Makefile 的依赖关系,使同一测试通过。
+
+### 普通单元回归
+
+保留或重写以下检查:
+
+- `TestNewApp` 和 `TestNewDemo`:generator 输出与 golden template 完全一致。
+- 旧 logger import 扫描:检查 generator 和 golden template 中的 Go 文件。
+- 模板依赖版本检查:生成后的 `go.mod` 必须包含稳定 `v3.3.1`,且不得包含日期预发布版本。
+- EOF 模板检查通过真实生成文件与 golden 比较覆盖,不单独复制生产逻辑到测试 helper。
+
+### E2E 测试的本地门禁
+
+需要 `protoc` 和两个插件的测试通过环境变量显式启用:
+
+```text
+DUBBOGO_CLI_E2E=1
+```
+
+未设置时测试 `Skip`,普通 `go test ./...` 不强迫所有开发者预装协议工具链。CI 必须显式设置该变量并提供工具链,所以 CI
不会跳过该测试。
+
+`tools/dubbogo-cli/Makefile` 增加固定 target:
+
+```makefile
+test-generated-projects:
+ DUBBOGO_CLI_E2E=1 go test ./cmd -run TestGeneratedProjectsCompile
-count=1
+```
+
+### CI 工具链
+
+在 Linux CI 中增加独立步骤:
+
+1. 安装 `protobuf-compiler`。
+2. 安装与模板一致的 `protoc-gen-go` 版本。
+3. 从当前仓库的 `tools/protoc-gen-go-triple` 模块安装本地生成器。
+4. 确认 `protoc`、`protoc-gen-go`、`protoc-gen-go-triple` 都可从 `PATH` 发现。
+5. 执行 `make -C tools/dubbogo-cli test-generated-projects`。
+
+不得仅依据进程退出码;测试输出必须显示 `newApp` 和 `newDemo` 两个子测试均执行。
+
+## 验证矩阵
+
+### 合并后定向验证
+
+- `go test ./cmd -run
'TestNewApp|TestNewDemo|TestGeneratedCodeDoesNotImportRemovedLoggerPackage|TestGeneratedModuleVersion'
-count=1`
+- TDD E2E 红灯与绿灯命令。
+- `go vet ./...`,工作目录为 `tools/dubbogo-cli`。
+- `go mod tidy -diff`,工作目录为 `tools/dubbogo-cli`。
+
+### Linux 完整验证
+
+- 根模块 `go test ./...`。
+- `tools/dubbogo-cli` 模块 `go test ./...`。
+- `tools/dubbogo-cli` 模块 `go vet ./...`。
+- E2E 生成项目测试。
+- 对生成后的 `newApp` 和 `newDemo` 分别确认 `go mod tidy -diff` 无差异。
+- `git diff --check`。
+- 项目格式门禁的 Linux 等价命令;若运行会改写工作树,必须在运行前记录状态,并确认最终无额外 Diff。
+
+### 测试真实性
+
+E2E 测试必须执行真实 generator、真实 Makefile、真实 `protoc`、当前仓库构建的 Triple 插件和真实 `go
test`。不得用复制的 helper 或仅检查字符串来替代可编译性验证。
+
+验证测试有效性时,在隔离探针中临时移除 `test: proto-gen tidy` 的依赖,确认 E2E 测试重新失败;探针不得 commit 或
push。
+
+## 提交与推送
+
+计划提交序列:
+
+1. 本设计文档 commit。
+2. `develop` 合并 commit。
+3. 测试与统一修复 commit。
+4. 必要的验证或 CI 修正 commit。
+
+所有普通 commit 使用 `Signed-off-by`。不 squash、不 rebase、不 force-push。推送前重新比较:
+
+- GitHub PR 当前 `headRefOid`
+- `origin/codex/fix-cli-template-logger`
+- 本地 upstream
+
+三者必须仍等于本轮已知远端 Head,或只包含本任务已经推送的 commit。检测到第三方新增提交时停止 push 并重新对账。
+
+## 风险与回滚
+
+- CI 安装协议工具链会增加时长;通过独立 E2E step 隔离,不增加普通开发者单测前置条件。
+- 生成项目依赖稳定 `v3.3.1`,而 Triple 插件来自当前 develop;E2E 测试负责证明这组版本真实兼容。
+- 网络或依赖下载失败必须归为环境问题,不得通过跳过测试或降低断言绕过。
+- 合并冲突解决后若发现 #3585 的完整设计与稳定 `v3.3.1` 不兼容,停止实现并报告具体编译证据,不恢复内嵌生成文件作为临时绕过。
+- push 后若 GitHub Head 与本地不一致,不进行 force-push;保留本地 commit,重新获取并合并远端变化。
+
+## 非目标
+
+- 不重新审查或修改 #3585 的其他工具模块。
+- 不恢复已删除的 `variadicrpccheck`。
+- 不重构 `client.WithClientURL` 或 Triple URL 解析。
+- 不新增新的日志抽象。
+- 不修改根模块的 Go 版本。
+- 不处理与 #3587 无关的历史 Review 评论。
diff --git a/tools/dubbogo-cli/Makefile b/tools/dubbogo-cli/Makefile
index f9c8ea4b6..0c0bff0f7 100644
--- a/tools/dubbogo-cli/Makefile
+++ b/tools/dubbogo-cli/Makefile
@@ -13,5 +13,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+.PHONY: test test-generated-projects
+
test:
- go test ./...
\ No newline at end of file
+ go test ./...
+
+test-generated-projects:
+ DUBBOGO_CLI_E2E=1 go test ./cmd -run '^TestGeneratedProjectsCompile$$'
-count=1 -v
diff --git a/tools/dubbogo-cli/cmd/gen_test.go
b/tools/dubbogo-cli/cmd/gen_test.go
index 0456d8ba1..4d162077a 100644
--- a/tools/dubbogo-cli/cmd/gen_test.go
+++ b/tools/dubbogo-cli/cmd/gen_test.go
@@ -19,6 +19,7 @@ package cmd
import (
"os"
+ "os/exec"
"path/filepath"
"strings"
"testing"
@@ -33,6 +34,18 @@ import (
"dubbo.apache.org/dubbo-go/v3/tools/dubbogo-cli/generator/sample"
)
+type generatedProjectCase struct {
+ name string
+ generate func(string) error
+}
+
+func generatedProjectCases() []generatedProjectCase {
+ return []generatedProjectCase{
+ {name: "newApp", generate: application.Generate},
+ {name: "newDemo", generate: sample.Generate},
+ }
+}
+
func TestNewApp(t *testing.T) {
genPath := filepath.Join(t.TempDir(), "newApp")
require.NoError(t, application.Generate(genPath))
@@ -47,6 +60,89 @@ func TestNewDemo(t *testing.T) {
assertFileSame(t, genPath, "./testGenCode/template/newDemo")
}
+func TestGeneratedModuleVersion(t *testing.T) {
+ const stableVersion = "dubbo.apache.org/dubbo-go/v3 v3.3.1"
+ const datedPrerelease = "v3.3.1-20260727"
+
+ for _, tc := range generatedProjectCases() {
+ t.Run(tc.name, func(t *testing.T) {
+ genPath := filepath.Join(t.TempDir(), tc.name)
+ require.NoError(t, tc.generate(genPath))
+
+ content, err := os.ReadFile(filepath.Join(genPath,
"go.mod"))
+ require.NoError(t, err)
+ require.Contains(t, string(content), stableVersion)
+ require.NotContains(t, string(content), datedPrerelease)
+ })
+ }
+}
+
+func TestGeneratedCodeDoesNotImportRemovedLoggerPackage(t *testing.T) {
+ const removedLoggerImport =
`"dubbo.apache.org/dubbo-go/v3/common/logger"`
+
+ for _, dir := range []string{"../generator", "./testGenCode/template"} {
+ files, err := walkDir(dir)
+ require.NoError(t, err, "iterate generated code sources failed:
%s", dir)
+
+ for _, file := range files {
+ if filepath.Ext(file) != ".go" {
+ continue
+ }
+
+ content, err := os.ReadFile(file)
+ require.NoError(t, err, "read generated code source
failed: %s", file)
+ require.NotContains(t, string(content),
removedLoggerImport,
+ "generated code must not import the removed
logger package: %s", file)
+ }
+ }
+}
+
+func TestGeneratedProjectsCompile(t *testing.T) {
+ if os.Getenv("DUBBOGO_CLI_E2E") != "1" {
+ t.Skip("set DUBBOGO_CLI_E2E=1 to run generated-project
compilation")
+ }
+
+ for _, tc := range generatedProjectCases() {
+ t.Run(tc.name, func(t *testing.T) {
+ genPath := filepath.Join(t.TempDir(), tc.name)
+ require.NoError(t, tc.generate(genPath))
+
+ cmd := exec.Command("make", "test")
+ cmd.Dir = genPath
+ cmd.Env = append(os.Environ(), "GOWORK=off")
+ output, err := cmd.CombinedOutput()
+ require.NoError(t, err, "generated project make test
failed:\n%s", output)
+ })
+ }
+}
+
+func TestGeneratedStreamHandlersHandleWrappedEOF(t *testing.T) {
+ testCases := []struct {
+ generatedProjectCase
+ serverFile string
+ }{
+ {
+ generatedProjectCase: generatedProjectCase{name:
"newApp", generate: application.Generate},
+ serverFile: "pkg/service/service.go",
+ },
+ {
+ generatedProjectCase: generatedProjectCase{name:
"newDemo", generate: sample.Generate},
+ serverFile: "go-server/cmd/server.go",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ genPath := filepath.Join(t.TempDir(), tc.name)
+ require.NoError(t, tc.generate(genPath))
+
+ content, err := os.ReadFile(filepath.Join(genPath,
tc.serverFile))
+ require.NoError(t, err)
+ require.Contains(t, string(content), "errors.Is(err,
io.EOF)")
+ })
+ }
+}
+
func assertFileSame(t *testing.T, genPath, templatePath string) {
// 1. get all files in template directory
templateFiles, err := walkDir(templatePath)
diff --git a/tools/dubbogo-cli/cmd/testGenCode/template/newApp/Makefile
b/tools/dubbogo-cli/cmd/testGenCode/template/newApp/Makefile
index aa1d921ea..eced475ba 100644
--- a/tools/dubbogo-cli/cmd/testGenCode/template/newApp/Makefile
+++ b/tools/dubbogo-cli/cmd/testGenCode/template/newApp/Makefile
@@ -56,5 +56,5 @@ clean:
tidy:
go mod tidy
-test:
+test: proto-gen tidy
go test ./...
diff --git a/tools/dubbogo-cli/cmd/testGenCode/template/newApp/go.mod
b/tools/dubbogo-cli/cmd/testGenCode/template/newApp/go.mod
index 1f8917524..ad55eab77 100644
--- a/tools/dubbogo-cli/cmd/testGenCode/template/newApp/go.mod
+++ b/tools/dubbogo-cli/cmd/testGenCode/template/newApp/go.mod
@@ -3,6 +3,6 @@ module dubbo-go-app
go 1.25
require (
- dubbo.apache.org/dubbo-go/v3 v3.3.1-20260727
+ dubbo.apache.org/dubbo-go/v3 v3.3.1
google.golang.org/protobuf v1.34.2
)
diff --git
a/tools/dubbogo-cli/cmd/testGenCode/template/newApp/pkg/service/service.go
b/tools/dubbogo-cli/cmd/testGenCode/template/newApp/pkg/service/service.go
index 97a2c7670..ee5e935fc 100644
--- a/tools/dubbogo-cli/cmd/testGenCode/template/newApp/pkg/service/service.go
+++ b/tools/dubbogo-cli/cmd/testGenCode/template/newApp/pkg/service/service.go
@@ -19,6 +19,7 @@ package service
import (
"context"
+ "errors"
"io"
"log"
)
@@ -38,7 +39,7 @@ func (s *GreeterServerImpl) SayHello(ctx context.Context, in
*api.HelloRequest)
func (s *GreeterServerImpl) SayHelloStream(ctx context.Context, stream
api.Greeter_SayHelloStreamServer) error {
for {
in, err := stream.Recv()
- if err == io.EOF {
+ if errors.Is(err, io.EOF) {
return nil
}
if err != nil {
diff --git a/tools/dubbogo-cli/cmd/testGenCode/template/newDemo/Makefile
b/tools/dubbogo-cli/cmd/testGenCode/template/newDemo/Makefile
index 8bd58d398..4969182d9 100644
--- a/tools/dubbogo-cli/cmd/testGenCode/template/newDemo/Makefile
+++ b/tools/dubbogo-cli/cmd/testGenCode/template/newDemo/Makefile
@@ -19,5 +19,5 @@ proto-gen:
tidy:
go mod tidy
-test:
+test: proto-gen tidy
go test ./...
diff --git
a/tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go-server/cmd/server.go
b/tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go-server/cmd/server.go
index 25135a9a2..f06ab7246 100644
--- a/tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go-server/cmd/server.go
+++ b/tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go-server/cmd/server.go
@@ -19,6 +19,7 @@ package main
import (
"context"
+ "errors"
"io"
"log"
)
@@ -44,7 +45,7 @@ func (s *GreeterProvider) SayHello(ctx context.Context, in
*api.HelloRequest) (*
func (s *GreeterProvider) SayHelloStream(ctx context.Context, stream
api.Greeter_SayHelloStreamServer) error {
for {
in, err := stream.Recv()
- if err == io.EOF {
+ if errors.Is(err, io.EOF) {
return nil
}
if err != nil {
diff --git a/tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go.mod
b/tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go.mod
index d0d244046..5c329e06f 100644
--- a/tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go.mod
+++ b/tools/dubbogo-cli/cmd/testGenCode/template/newDemo/go.mod
@@ -3,6 +3,6 @@ module helloworld
go 1.25
require (
- dubbo.apache.org/dubbo-go/v3 v3.3.1-20260727
+ dubbo.apache.org/dubbo-go/v3 v3.3.1
google.golang.org/protobuf v1.34.2
)
diff --git a/tools/dubbogo-cli/generator/application/gomod.go
b/tools/dubbogo-cli/generator/application/gomod.go
index ea6bbc349..3938ff16b 100644
--- a/tools/dubbogo-cli/generator/application/gomod.go
+++ b/tools/dubbogo-cli/generator/application/gomod.go
@@ -23,7 +23,7 @@ const (
go 1.25
require (
- dubbo.apache.org/dubbo-go/v3 v3.3.1-20260727
+ dubbo.apache.org/dubbo-go/v3 v3.3.1
google.golang.org/protobuf v1.34.2
)
`
diff --git a/tools/dubbogo-cli/generator/application/makefile.go
b/tools/dubbogo-cli/generator/application/makefile.go
index ce574583b..272e69314 100644
--- a/tools/dubbogo-cli/generator/application/makefile.go
+++ b/tools/dubbogo-cli/generator/application/makefile.go
@@ -76,7 +76,7 @@ clean:
tidy:
go mod tidy
-test:
+test: proto-gen tidy
go test ./...
`
)
diff --git a/tools/dubbogo-cli/generator/application/pkg.go
b/tools/dubbogo-cli/generator/application/pkg.go
index bd04f84d6..420cab491 100644
--- a/tools/dubbogo-cli/generator/application/pkg.go
+++ b/tools/dubbogo-cli/generator/application/pkg.go
@@ -39,6 +39,7 @@ package service
import (
"context"
+ "errors"
"io"
"log"
)
@@ -58,7 +59,7 @@ func (s *GreeterServerImpl) SayHello(ctx context.Context, in
*api.HelloRequest)
func (s *GreeterServerImpl) SayHelloStream(ctx context.Context, stream
api.Greeter_SayHelloStreamServer) error {
for {
in, err := stream.Recv()
- if err == io.EOF {
+ if errors.Is(err, io.EOF) {
return nil
}
if err != nil {
diff --git a/tools/dubbogo-cli/generator/sample/gen_server.go
b/tools/dubbogo-cli/generator/sample/gen_server.go
index 3b8325279..5dfd1ba10 100644
--- a/tools/dubbogo-cli/generator/sample/gen_server.go
+++ b/tools/dubbogo-cli/generator/sample/gen_server.go
@@ -23,6 +23,7 @@ package main
import (
"context"
+ "errors"
"io"
"log"
)
@@ -48,7 +49,7 @@ func (s *GreeterProvider) SayHello(ctx context.Context, in
*api.HelloRequest) (*
func (s *GreeterProvider) SayHelloStream(ctx context.Context, stream
api.Greeter_SayHelloStreamServer) error {
for {
in, err := stream.Recv()
- if err == io.EOF {
+ if errors.Is(err, io.EOF) {
return nil
}
if err != nil {
diff --git a/tools/dubbogo-cli/generator/sample/makefile.go
b/tools/dubbogo-cli/generator/sample/makefile.go
index a250a2639..2db9075ff 100644
--- a/tools/dubbogo-cli/generator/sample/makefile.go
+++ b/tools/dubbogo-cli/generator/sample/makefile.go
@@ -39,7 +39,7 @@ proto-gen:
tidy:
go mod tidy
-test:
+test: proto-gen tidy
go test ./...
`
)
diff --git a/tools/dubbogo-cli/generator/sample/mod.go
b/tools/dubbogo-cli/generator/sample/mod.go
index 156bdf917..51d603ccf 100644
--- a/tools/dubbogo-cli/generator/sample/mod.go
+++ b/tools/dubbogo-cli/generator/sample/mod.go
@@ -23,7 +23,7 @@ const (
go 1.25
require (
- dubbo.apache.org/dubbo-go/v3 v3.3.1-20260727
+ dubbo.apache.org/dubbo-go/v3 v3.3.1
google.golang.org/protobuf v1.34.2
)
`