From 5759e58bad7d550590cf0c500f189ddd5f73affe Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Tue, 29 Sep 2026 15:18:58 +0800 Subject: [PATCH] =?UTF-8?q?ci:=20=E5=BB=BA=E7=AB=8B=20CI=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E6=AD=A4=E5=89=8D=20main=20=E7=9A=84=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E4=B8=8E=20PR=20=E5=AE=8C=E5=85=A8=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 缺口 本仓此前**只有 release.yml**(只在 release/** 推送时跑)。也就是说 推 main、开 PR 一律无检查,而 SDK 正是外部插件开发者直接依赖的契约面 (sdk/plugin.go 接口一破,所有外部插件编译失败)。 ## 四个 job(每条命令都本地实测过) | job | 内容 | 实测耗时 | |---|---|---| | go | 根模块 build + vet + test | <1s | | hmapdev | 嵌套 module 测试 + 五平台交叉编译(矩阵) | ~2s + 编译 | | examples | 构建全部 20 个示例(linux/amd64、darwin/arm64) | 9s / 21s | | consistency | Lua SDK 三副本一致 + skills 可识别 + mkdocs --strict | ~2s | ## 三处「不能想当然」的地方(都有实测依据) 1. **示例不能用 `go build` 验**。它们是插件(只有 plugin.go、没有 func main), 必须由 hmapdev 注入 main 包装;直接 go build 得到 "function main is undeclared in the main package" —— 那不是缺陷,是方式不对。 故与发版走**同一个脚本**(package/build-examples.sh),避免 CI 与发版路径分叉。 2. **tools/hmapdev 是独立 module**,根模块的 `go test ./...` 不会进入它 —— 必须单独跑,否则它的测试永远不在 CI 里执行(Go 的模块边界)。 3. **不能用 `yaml.safe_load` 校验 mkdocs.yml**:它含 mkdocs-material 的 `!!python/name:` 标签(配置 emoji 的官方写法),safe_load 报 ConstructorError —— 是校验方式不对。改用 `mkdocs build --strict` (本地实测 2s、0 warning)。 ## 明确不进 CI - `hmapdev skill install` —— 它会写开发者本机的 ~/.claude、~/.codex 等目录 - 需要内核仓在场的检查(本仓独立可测) - 任何网络/真机依赖 CGO_ENABLED=0(SDK 纯 Go,与主仓相反)。 actionlint 全绿(修掉一处 shellcheck SC2012)。 --- .github/workflows/ci.yml | 219 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fed70ac --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,219 @@ +# HomeAgent SDK 仓 CI。 +# +# 与 release.yml 分工:本文件管「推送到 main / PR 时的检查」,release.yml 管 +# 「release/** 推送时的发版」。此前本仓**只有** release.yml —— main 的推送与 +# PR 完全没有检查,而 SDK 是被外部插件开发者直接依赖的契约面(sdk/plugin.go +# 的接口一旦破,所有外部插件编译失败),必须有门。 +# +# 设计原则与主仓一致:**CI 里每条命令都是本地已实测通过的**。 +# 不写「应该有用来试试」的步骤 —— 未验证的 CI 步骤会把假红灯变成常态。 +# +# 明确不进 CI 的: +# - `hmapdev skill install`(会写 ~/.claude、~/.codex 等**开发者本机目录**) +# - 需要内核仓在场的检查(本仓独立可测;sync-lua-sdk.sh 会在缺内核时 +# 明确 skip 而不是假装成功) +# - 任何网络/真机依赖 +name: CI + +on: + push: + branches: [main, 'release/**'] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +env: + # SDK 本身不需要 cgo(hmapdev 与 sdk 包都是纯 Go)—— 与主仓相反。 + CGO_ENABLED: 0 + GOFLAGS: -buildvcs=false + +jobs: + # ── 根模块:SDK 对外接口所在 ── + go: + name: Go build / vet / test + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version-file: go.mod + cache: true + - name: go build ./... + run: go build ./... + - name: go vet ./... + run: go vet ./... + - name: go test ./... + run: go test ./... -count=1 -timeout 10m + + # ── hmapdev 工具链(嵌套 module,根模块的 ./... 不会进入它)── + hmapdev: + name: hmapdev (${{ matrix.goos }}/${{ matrix.goarch }}) + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - goos: linux + goarch: amd64 + ext: '' + - goos: linux + goarch: arm64 + ext: '' + - goos: darwin + goarch: amd64 + ext: '' + - goos: darwin + goarch: arm64 + ext: '' + - goos: windows + goarch: amd64 + ext: '.exe' + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version-file: go.mod + cache: true + + # tools/hmapdev 是**独立 module**(有自己的 go.mod)—— + # 根模块的 `go test ./...` 不会进入它,必须单独跑,否则它的测试 + # 永远不在 CI 里执行。这是 Go 的模块边界,不是配置问题。 + - name: go test(嵌套 module) + working-directory: tools/hmapdev + run: go test ./... -count=1 -timeout 10m + + - name: 交叉编译 + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + run: | + set -euo pipefail + out="hmapdev_${{ matrix.goos }}_${{ matrix.goarch }}${{ matrix.ext }}" + go build -trimpath \ + -ldflags "-X gitcode.com/JianFeeeee/homeagent-sdk/meta.Version=0.0.0-ci" \ + -o "/tmp/$out" . + test -s "/tmp/$out" || { echo "ERROR: 未产出 $out"; exit 1; } + printf " ✓ %-28s %s 字节\n" "$out" "$(stat -c %s "/tmp/$out")" + file "/tmp/$out" + + # ── 示例插件:发版产物里最容易被使用者直接安装的东西 ── + # + # 为什么必须验:示例是使用者的模板(拷了就改),且**发版会连同 .hmap 一起发** + # —— 插件二进制与内核是协议绑定的(ProtocolVersion + 统一共享内存区魔数), + # 示例编不出来就意味着这次发版发不出配套产物。 + # + # ★ 示例**不能**用 `go build` 验:它们是插件(只有 plugin.go、没有 func main), + # 必须由 hmapdev 注入 main 包装。直接 go build 会得到 + # "function main is undeclared in the main package" —— 那不是缺陷, + # 是构建方式不对(本地实测确认)。 + examples: + name: Examples (${{ matrix.target }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + # windows 不在列:build-examples.sh 会明确拒绝(协议 2 的统一共享 + # 内存区未移植到 Windows),那是有意的设计而不是待修缺陷。 + target: ['linux/amd64', 'darwin/arm64'] + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version-file: go.mod + cache: true + + - name: 构建 hmapdev(宿主) + run: | + set -euo pipefail + cd tools/hmapdev + go build -o "$GITHUB_WORKSPACE/build/hmapdev_host" . + + # 用发版同一个脚本(package/build-examples.sh),避免 CI 与发版路径分叉。 + - name: 构建全部示例 + env: + HMAPDEV: ${{ github.workspace }}/build/hmapdev_host + TARGET: ${{ matrix.target }} + run: | + set -euo pipefail + bash package/build-examples.sh "$TARGET" /tmp/examples + echo + n=$(find /tmp/examples -name '*.hmap' | wc -l) + echo " .hmap 产物数: $n" + # 示例目录数应与产物数一致(少一个都要红) + want=$(find example -mindepth 2 -maxdepth 2 -name plugin.go | wc -l) + echo " 示例源码数: $want" + [ "$n" -ge "$want" ] || { echo "ERROR: 产物少于示例数"; exit 1; } + for f in /tmp/examples/*.hmap; do + printf " %9.1fKB %s\n" \ + "$(stat -c %s "$f" | awk '{print $1/1024}')" "$(basename "$f")" + done + + # ── 一致性与文档 ── + consistency: + name: Consistency & docs + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v7 + + # sdk.lua 有**四份**副本(本仓三份 + 内核仓一份),历史上漂移过, + # 出现「mock 有、内核没有」的静默失配。此处钉住本仓内的三份。 + - name: Lua SDK 副本一致性 + run: | + set -euo pipefail + canon=sdk/lua/sdk.lua + [ -f "$canon" ] || { echo "ERROR: 缺 $canon"; exit 1; } + want=$(sha256sum "$canon" | cut -d' ' -f1) + fail=0 + for f in tools/hmapdev/assets/sdk.lua example/luademo/sdk.lua; do + have=$(sha256sum "$f" | cut -d' ' -f1) + if [ "$have" = "$want" ]; then + echo " ✓ $f" + else + echo " ✗ $f 与 $canon 不一致" >&2 + echo " canon=$want" >&2 + echo " this =$have" >&2 + echo " 解法:bash scripts/sync-lua-sdk.sh" >&2 + fail=1 + fi + done + [ "$fail" = "0" ] || exit 1 + + - uses: actions/setup-go@v7 + with: + go-version-file: go.mod + cache: true + + # skills/ 会随 SDK store 分发给所有 agent(hmapdev skill install)。 + # skill list 会读 skills/ 目录并报告可用项 —— 目录结构坏掉会在这里红。 + - name: 技能目录可被 hmapdev 识别 + run: | + set -euo pipefail + cd tools/hmapdev && go build -o /tmp/hmapdev . && cd "$GITHUB_WORKSPACE" + /tmp/hmapdev skill list + /tmp/hmapdev skill path + + - uses: actions/setup-python@v7 + with: + python-version: '3.12' + + # 文档站会被部署(deploy-sdk-site.sh)——站点构建坏了此前无从发现。 + # --strict 把 warning 升级为 error:断链、缺失引用都会红。 + # + # ★ 不能用 yaml.safe_load 校验 mkdocs.yml:它含 mkdocs-material 的 + # `!!python/name:` 标签(配置 emoji 的官方写法),safe_load 处理不了, + # 会报 ConstructorError —— 那是校验方式不对,不是配置有问题。 + - name: 文档站构建(mkdocs --strict) + run: | + set -euo pipefail + python3 -m pip install --quiet mkdocs-material + mkdocs build --strict --site-dir /tmp/site