From 1ea9baa633e3c5ba5dfed73270646e3a072dfe88 Mon Sep 17 00:00:00 2001 From: randypanding Date: Wed, 19 Aug 2026 04:24:22 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20adr-required=20=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=B8=85=E5=8D=95=E6=94=B9=20jq=20=E7=BB=93=E6=9E=84=E5=8C=96?= =?UTF-8?q?=E5=A4=84=E7=90=86=E2=80=94=E2=80=94=E6=8D=A2=E8=A1=8C=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=90=8D=E7=BB=95=E8=BF=87=E6=88=AA=E6=96=AD=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=EF=BC=88qodo=20review=EF=BC=8CADR-0016=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Git 允许文件名含换行——行流计数(grep -c)可被单文件多行虚增 GOT, 使 3000 截断 fail-closed 失效。改为逐页 jq 按 JSON 对象计数(length) 与 C1 匹配(test 正则),文件名中的换行不影响任何一层判定。 与 agent-registry #22(首发发现)三仓同步。 --- .github/workflows/gate.yml | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml index a8a708a..076d2cd 100644 --- a/.github/workflows/gate.yml +++ b/.github/workflows/gate.yml @@ -69,24 +69,37 @@ jobs: PR_TITLE: ${{ github.event.pull_request.title }} PR_BODY: ${{ github.event.pull_request.body }} run: | - # 文件清单三防线(qodo review #2 #3,CI-Workflows #8 同款加固): - # --paginate:>100 文件的 PR 首页截断会把 C1 变更漏检成非 C1; - # rename 的 previous_filename 一并纳入——governance/standards/CODEOWNERS - # 等 C1 资产改名移出受管路径同样是 C1 变更(files API 只在 - # previous_filename 暴露原路径); - # files API 3000 文件硬上限:返回数 < changed_files = 清单不完整, - # fail-closed(清单不全时 C1 判定不可信,宁误杀不放过)。 + # 文件清单三防线(qodo review #2 #3 + 追加:换行文件名绕过): + # 分页拉全量;rename 的 previous_filename 一并纳入 C1 判定(C1 资产改名 + # 移出受管路径同样是 C1 变更);files API 3000 文件硬上限 fail-closed。 + # 计数与 C1 匹配全部在 jq 内按 JSON 对象处理——Git 允许文件名含换行, + # 行流(grep -c / echo|grep)会被单文件多行虚增计数绕过截断检测 + # (qodo review:Newline filenames bypass truncation,AR#22 同款)。 PR_API="repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" - FILES_API="$PR_API/files?per_page=100" CHANGED=$(gh api "$PR_API" --jq '.changed_files') - FILES_CUR=$(gh api --paginate "$FILES_API" --jq '.[].filename') - GOT=$(grep -c . <<<"$FILES_CUR" || true) + GOT=0 + C1_HIT=0 + PAGE=1 + while :; do + PAGEJSON=$(gh api "$PR_API/files?per_page=100&page=$PAGE") + if ! jq -e 'type == "array"' <<<"$PAGEJSON" >/dev/null 2>&1; then + echo "::error::files API 第 $PAGE 页拉取失败——C1 判定完整性无法保证,fail-closed" + exit 1 + fi + N=$(jq 'length' <<<"$PAGEJSON") + GOT=$((GOT+N)) + if jq -e '[.[] | .filename, (.previous_filename // empty)] + | any(test("^(governance/|standards/|scripts/|\\.github/|CODEOWNERS|profile/)"))' <<<"$PAGEJSON" >/dev/null 2>&1; then + C1_HIT=1 + fi + [[ $N -lt 100 ]] && break + PAGE=$((PAGE+1)) + done if [ "$GOT" -lt "$CHANGED" ]; then echo "::error::PR 声明变更 $CHANGED 个文件但 files API 只返回 $GOT 个(3000 上限截断)——C1 判定完整性无法保证,fail-closed" exit 1 fi - FILES=$( { echo "$FILES_CUR"; gh api --paginate "$FILES_API" --jq '[.[].previous_filename // empty][]'; } ) - if ! echo "$FILES" | grep -qE '^(governance/|standards/|scripts/|\.github/|CODEOWNERS|profile/)'; then + if [ "$C1_HIT" -ne 1 ]; then echo "非 C1 路径变更,跳过 adr-required" exit 0 fi