ソースを参照

ci: 落地 OPS-CI-GATE 本地编译闸门(githooks/pre-push) + 远端CI草案

- 新增 githooks/pre-push:push 到受保护分支(cfclub/main/master/release/*)
  且涉及 cfc-backend 改动时,自动跑 mvn -DskipTests compile,红构建阻止 push
- 新增 docs/2026-10-29-CI编译闸门-远端CI草案.md:方案 B 远端 CI 配置草案(Gitea/GitLab/webhook)
- AGENTS.md 补充启用说明(git config core.hooksPath githooks)
- 约束:编译闸门仅在 iwintrue(远端CI)与 local(本机)执行
- 验证:bash -n 通过;非受保护分支跳过 exit0;受保护分支触发 mvn compile 绿灯 exit0
Agnes Agent 2 週間 前
親
コミット
2d2cdfcb8c
3 ファイル変更、190 行追加、1 行削除
  1. 15 1
      AGENTS.md
  2. 101 0
      docs/2026-10-29-CI编译闸门-远端CI草案.md
  3. 74 0
      githooks/pre-push

+ 15 - 1
AGENTS.md

@@ -414,4 +414,18 @@ cd d:\Java_code\CFC\cfc-langgraph
 
 $env:OPENCODE_ADMIN_PASSWORD = "IwinTrue@123"
 opencode web --port 4090 --hostname 127.0.0.1
-```
+```
+
+## CI 编译闸门(OPS-CI-GATE)
+
+为防止红构建(如 `ee71f7716` 的 `StatsController` lambda 捕获非 effectively-final 错误)未经编译即进入 `cfclub` 主线,落地双保险:
+
+- **方案 A(本地 hook,已落地)**:仓库级 `githooks/pre-push`。每位开发者启用一次:
+  ```bash
+  git config core.hooksPath githooks
+  ```
+  push 到受保护分支(`cfclub`/`main`/`master`/`release/*`)且涉及 `cfc-backend` 改动时,自动跑 `mvn -q -DskipTests compile`,编译失败则阻止 push。紧急 bypass:`XAF_CI_GATE=0 git push`。
+- **方案 B(远端 CI,草案)**:见 `docs/2026-10-29-CI编译闸门-远端CI草案.md`,在 `git.iwintrue.com` 接 CI runner,仅 iwintrue 环境编译。
+- **方案 C(分支保护)**:`cfclub` 设为受保护分支,仅允许经绿灯(A/B)的合并进入。
+
+约束:**编译闸门仅在 `iwintrue`(远端 CI)与 `local`(开发者本机)执行**,不在其他环境运行。

+ 101 - 0
docs/2026-10-29-CI编译闸门-远端CI草案.md

@@ -0,0 +1,101 @@
+# 远端 CI 编译闸门草案(OPS-CI-GATE 方案 B)
+
+> 起草:2026-10-29|配套文档:`docs/2026-10-29-CI编译闸门待办.md`(方案总述)
+> 状态:**草案** — 需主理人确认 `git.iwintrue.com` 实际 CI 平台(Gitea Actions / GitLab CI / 自定义 webhook)后落地
+> 约束:编译闸门**仅在 `iwintrue`(远端 CI 环境)与 `local`(开发者本机)执行**
+
+## 一、目标
+
+在代码进入 `cfclub`(及主要长期分支)前,由远端 CI 强制跑 `mvn compile`(可选 + 单测),**红灯禁止合并 / 禁止 fast-forward**。与本地 `githooks/pre-push`(方案 A)形成双保险:本机漏装的,远端兜底拦。
+
+## 二、触发条件
+
+- 触发事件:`push` 到 `cfclub` / `main` / `master` / `release/*`,以及指向这些分支的 PR/MR。
+- 仅当提交涉及 `cfc-backend/` 时编译后端;前端(`cfc-frontend` / `cfc-web`)另行加构建步骤。
+- 跳过机制:提交信息含 `[skip ci]` 时跳过(已有 auto-bump 提交在用,需保留此豁免以免自 bump 触发死循环)。
+
+## 三、环境
+
+- JDK:Temurin 21(与本地开发一致,`JAVA_HOME` 指向 temurin21)。
+- 构建:Maven 3.9.x(`mvn -q -DskipTests compile`;单测步可单独开)。
+- 仅运行于 iwintrue runner(不调度到其他环境),符合"仅 iwintrue/local 编译"约束。
+
+## 四、配置示例
+
+### 4.1 若为 Gitea Actions(自托管 Gitea 常见)
+
+文件路径:`.gitea/workflows/ci-compile.yml`
+
+```yaml
+name: ci-compile
+on:
+  push:
+    branches: [cfclub, main, master]
+    paths:
+      - 'cfc-backend/**'
+      - '.gitea/workflows/**'
+  pull_request:
+    branches: [cfclub, main, master]
+    paths:
+      - 'cfc-backend/**'
+jobs:
+  compile:
+    if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
+    runs-on: [iwintrue]
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up JDK 21
+        uses: actions/setup-java@v4
+        with:
+          distribution: temurin
+          java-version: '21'
+          cache: maven
+      - name: Compile cfc-backend
+        working-directory: cfc-backend
+        run: mvn -B -q -DskipTests compile
+```
+
+### 4.2 若为 GitLab CI
+
+文件路径:`.gitlab-ci.yml`(仅保留 compile 阶段,runner tag 限定 `iwintrue`)
+
+```yaml
+stages:
+  - build
+
+cfc-backend-compile:
+  stage: build
+  tags:
+    - iwintrue
+  rules:
+    - if: '$CI_COMMIT_BRANCH =~ /^(cfclub|main|master)$/ && $CI_COMMIT_MESSAGE !~ /\[skip ci\]/'
+      changes:
+        - cfc-backend/**/*
+  image: eclipse-temurin:21-jdk
+  script:
+    - cd cfc-backend
+    - mvn -B -q -DskipTests compile
+```
+
+### 4.3 若为自定义 webhook(通用)
+
+- 在 `git.iwintrue.com` 的 push webhook 中指向 iwintrue 上的 CI 接收服务;
+- 服务 `git clone` 目标分支、`cd cfc-backend && mvn -DskipTests compile`;
+- 非零退出 → 调用 Git 平台 API 标记 commit status 为 `failure`,并阻止受保护分支的 merge(配合方案 C 分支保护)。
+
+## 五、与方案 C 的衔接
+
+- `cfclub` 设为受保护分支:仅允许经绿灯(A 本机 hook 或 B 远端 CI)的合并进入;禁止直接 push 红构建。
+- 远端 CI 红灯 → 标记失败 → 受保护分支拒绝 fast-forward / merge。
+
+## 六、验收
+
+1. 人为制造一处 `cfc-backend` 编译错误 → push 到 `cfclub` → 远端 CI 红灯、merge 被拒。
+2. 修复后重推 → 绿灯、可合入。
+3. 含 `[skip ci]` 的提交不被 CI 拦截(避免 auto-bump 死循环)。
+
+## 七、待主理人确认
+
+- [ ] `git.iwintrue.com` 实际 CI 平台(决定用 4.1 / 4.2 / 4.3)。
+- [ ] iwintrue runner 是否已装 Temurin 21 + Maven 3.9.x。
+- [ ] 是否同步对前端(`cfc-frontend` / `cfc-web`)加构建闸门(当前仅后端)。

+ 74 - 0
githooks/pre-push

@@ -0,0 +1,74 @@
+#!/usr/bin/env bash
+#
+# githooks/pre-push — 本地编译闸门(OPS-CI-GATE 方案 A)
+#
+# 作用:git push 前,对"推送到受保护长期分支 且 涉及 cfc-backend 改动"的 ref
+#       执行 `mvn -q -DskipTests compile`;编译失败(红构建)则禁止 push。
+# 启用:仓库内执行 `git config core.hooksPath githooks`(每位开发者配置一次)。
+# 约束:编译闸门仅在 local(开发者本机)与 iwintrue(远端 CI)执行 —— 本 hook 即 local 侧。
+# 临时关闭(紧急 bypass):XAF_CI_GATE=0 git push
+#
+# 读取 stdin:每行 "<local-ref> <local-sha> <remote-ref> <remote-sha>"
+#
+set -euo pipefail
+
+# 允许通过环境变量临时关闭
+if [ "${XAF_CI_GATE:-1}" = "0" ]; then
+  echo "[pre-push 编译闸门] XAF_CI_GATE=0,已跳过编译检查。" >&2
+  exit 0
+fi
+
+BACKEND_DIR="cfc-backend"
+ZERO="0000000000000000000000000000000000000000"
+
+# 判断本次推送的提交范围是否触及 backend 源码
+backend_touched() {
+  local local_sha="$1" remote_sha="$2"
+  if [ "$remote_sha" = "$ZERO" ]; then
+    # 新建分支:与本地 HEAD 的 merge-base 比较
+    git diff --name-only "$(git merge-base HEAD "$local_sha")" "$local_sha" 2>/dev/null
+  else
+    git diff --name-only "$remote_sha" "$local_sha" 2>/dev/null
+  fi | grep -q "^${BACKEND_DIR}/"
+}
+
+need_compile=0
+while read -r local_ref local_sha remote_ref remote_sha; do
+  # 仅对受保护长期分支设闸门;其余分支(个人 feature 等)不拦截,交给远端 CI
+  case "$remote_ref" in
+    refs/heads/cfclub|refs/heads/main|refs/heads/master|refs/heads/release/*) ;;
+    *) continue ;;
+  esac
+  if backend_touched "$local_sha" "$remote_sha"; then
+    need_compile=1
+  fi
+done
+
+if [ "$need_compile" -ne 1 ]; then
+  exit 0
+fi
+
+echo "[pre-push 编译闸门] 检测到 cfc-backend 改动,开始编译校验(mvn -q -DskipTests compile)..." >&2
+
+if ! command -v mvn >/dev/null 2>&1; then
+  echo "[pre-push 编译闸门] ❌ 未找到 mvn,无法校验编译。请安装 Maven 并加入 PATH;或确认用 XAF_CI_GATE=0 临时跳过(不推荐,红构建将不被拦截)。" >&2
+  exit 1
+fi
+
+if [ ! -d "$BACKEND_DIR" ]; then
+  echo "[pre-push 编译闸门] ⚠️ 未找到 $BACKEND_DIR 目录,跳过(可能非后端仓库)。" >&2
+  exit 0
+fi
+
+set +e
+( cd "$BACKEND_DIR" && mvn -q -DskipTests compile )
+rc=$?
+set -e
+
+if [ "$rc" -ne 0 ]; then
+  echo "[pre-push 编译闸门] ❌ 编译失败(exit=$rc),已阻止 push。请修复后再推送;确认需跳过可用 XAF_CI_GATE=0。" >&2
+  exit 1
+fi
+
+echo "[pre-push 编译闸门] ✅ 编译通过,允许 push。" >&2
+exit 0