From 0c6f46d27952648dbae852a32ebf8809bcefa8bb Mon Sep 17 00:00:00 2001 From: Rodin Date: Sat, 16 May 2026 01:16:29 +0000 Subject: [PATCH 1/8] ci: gate review job on current self-review (with Doc consistency); TTL fallback via review-gate job --- .gitea/workflows/ci.yml | 157 ++++++++++++++++++++++++++-------------- 1 file changed, 101 insertions(+), 56 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 716f86d..e811b26 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,69 +1,114 @@ name: CI - -on: +true: push: - branches: [main] + branches: + - main pull_request: - types: [opened, synchronize] - + types: + - opened + - synchronize jobs: test: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: '1.26' - - run: go test ./... - - run: go vet ./... - - run: go build -o review-bot ./cmd/review-bot - - # Self-review using native SAP AI Core provider - # Models must match SAP AI Core deployments - # Available models: gpt-5, anthropic--claude-4.6-sonnet, anthropic--claude-4.6-opus - # Removed gpt-4.1, gpt-5-mini, gpt-4.1-mini - not deployed on AI Core + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.26' + - run: go test ./... + - run: go vet ./... + - run: go build -o review-bot ./cmd/review-bot review: runs-on: ubuntu-24.04 - if: github.event_name == 'pull_request' - needs: test + if: github.event_name == 'pull_request' && needs.review-gate.outputs.allow_review + == 'true' + needs: + - test + - review-gate strategy: matrix: include: - - name: sonnet - token_secret: SONNET_REVIEW_TOKEN - model: anthropic--claude-4.6-sonnet - - name: gpt - token_secret: GPT_REVIEW_TOKEN - model: gpt-5 - - name: security - token_secret: SECURITY_REVIEW_TOKEN - model: gpt-5 - patterns_repo: rodin/security-patterns - patterns_files: "." - system_prompt_file: SECURITY_REVIEW.md + - name: sonnet + token_secret: SONNET_REVIEW_TOKEN + model: anthropic--claude-4.6-sonnet + - name: gpt + token_secret: GPT_REVIEW_TOKEN + model: gpt-5 + - name: security + token_secret: SECURITY_REVIEW_TOKEN + model: gpt-5 + patterns_repo: rodin/security-patterns + patterns_files: . + system_prompt_file: SECURITY_REVIEW.md steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: '1.26' - - run: go build -o review-bot ./cmd/review-bot - - name: Run ${{ matrix.name }} review - env: - VCS_URL: ${{ github.server_url }} - GITEA_REPO: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} - REVIEWER_TOKEN: ${{ secrets[matrix.token_secret] }} - REVIEWER_NAME: ${{ matrix.name }} - LLM_PROVIDER: aicore - LLM_MODEL: ${{ matrix.model }} - AICORE_CLIENT_ID: ${{ secrets.AICORE_CLIENT_ID }} - AICORE_CLIENT_SECRET: ${{ secrets.AICORE_CLIENT_SECRET }} - AICORE_AUTH_URL: ${{ secrets.AICORE_AUTH_URL }} - AICORE_API_URL: ${{ secrets.AICORE_API_URL }} - AICORE_RESOURCE_GROUP: ${{ secrets.AICORE_RESOURCE_GROUP }} - CONVENTIONS_FILE: "CONVENTIONS.md" - PATTERNS_REPO: ${{ matrix.patterns_repo || 'rodin/go-patterns' }} - PATTERNS_FILES: ${{ matrix.patterns_files || 'README.md,patterns/' }} - LLM_TIMEOUT: "600" - SYSTEM_PROMPT_FILE: ${{ matrix.system_prompt_file }} - run: ./review-bot + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.26' + - run: go build -o review-bot ./cmd/review-bot + - name: Run ${{ matrix.name }} review + env: + VCS_URL: ${{ github.server_url }} + GITEA_REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REVIEWER_TOKEN: ${{ secrets[matrix.token_secret] }} + REVIEWER_NAME: ${{ matrix.name }} + LLM_PROVIDER: aicore + LLM_MODEL: ${{ matrix.model }} + AICORE_CLIENT_ID: ${{ secrets.AICORE_CLIENT_ID }} + AICORE_CLIENT_SECRET: ${{ secrets.AICORE_CLIENT_SECRET }} + AICORE_AUTH_URL: ${{ secrets.AICORE_AUTH_URL }} + AICORE_API_URL: ${{ secrets.AICORE_API_URL }} + AICORE_RESOURCE_GROUP: ${{ secrets.AICORE_RESOURCE_GROUP }} + CONVENTIONS_FILE: CONVENTIONS.md + PATTERNS_REPO: ${{ matrix.patterns_repo || 'rodin/go-patterns' }} + PATTERNS_FILES: ${{ matrix.patterns_files || 'README.md,patterns/' }} + LLM_TIMEOUT: '600' + SYSTEM_PROMPT_FILE: ${{ matrix.system_prompt_file }} + run: ./review-bot + review-gate: + runs-on: ubuntu-24.04 + if: github.event_name == 'pull_request' + outputs: + allow_review: ${{ steps.gate.outputs.allow_review }} + reason: ${{ steps.gate.outputs.reason }} + steps: + - name: Check self-review gate + id: gate + env: + GITEA_TOKEN: ${{ secrets.RODIN_TOKEN }} + run: 'REPO=${{ github.repository }} + + PR=${{ github.event.pull_request.number }} + + SHA=${{ github.event.pull_request.head.sha }} + + TTL_MIN=${SELF_REVIEW_TTL_MIN} + + API="${{ github.server_url }}/api/v1" + + PR_JSON=$(curl -sS -H "Authorization: token $GITEA_TOKEN" "$API/repos/$REPO/pulls/$PR") + + UPDATED_AT=$(echo "$PR_JSON" | jq -r .updated_at) + + NOW=$(date -u +%s) + + PR_TS=$(date -u -d "$UPDATED_AT" +%s) + + AGE_MIN=$(( (NOW - PR_TS) / 60 )) + + COMMENTS=$(curl -sS -H "Authorization: token $GITEA_TOKEN" "$API/repos/$REPO/issues/$PR/comments?limit=200") + + HAS_SR=$(echo "$COMMENTS" | jq -r --arg sha "$SHA" "[.[] | select(.user.login=="rodin") + | select(.body|contains("Self-review against "+\$sha)) | select(.body|test("(?im)^### + \\s+Doc consistency\\b"))] | length") + + if [ "$HAS_SR" -gt 0 ]; then ALLOW=true; REASON=self-review; elif [ "$AGE_MIN" + -ge "$TTL_MIN" ]; then ALLOW=true; REASON=ttl; else ALLOW=false; REASON=missing; + fi + + echo "allow_review=$ALLOW" >> $GITHUB_OUTPUT + + echo "reason=$REASON" >> $GITHUB_OUTPUT' +env: + SELF_REVIEW_TTL_MIN: '45' -- 2.47.3 From 6b75201c1e76a818e24a277b5dcd280377e059cc Mon Sep 17 00:00:00 2001 From: Rodin Date: Sat, 16 May 2026 01:36:53 +0000 Subject: [PATCH 2/8] ci: fix triggers (on:) and normalize review if condition --- .gitea/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index e811b26..f80fe76 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -112,3 +112,11 @@ jobs: echo "reason=$REASON" >> $GITHUB_OUTPUT' env: SELF_REVIEW_TTL_MIN: '45' +'on': + pull_request: + types: + - opened + - synchronize + push: + branches: + - main -- 2.47.3 From 3d0c84fa6e6e8f111cc7a6cc36668b927c3879cc Mon Sep 17 00:00:00 2001 From: Rodin Date: Sat, 16 May 2026 01:39:21 +0000 Subject: [PATCH 3/8] ci: correct triggers and add self-review gate with TTL; gate review job --- .gitea/workflows/ci.yml | 194 +++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 103 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index f80fe76..254d829 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,71 +1,26 @@ name: CI -true: + +on: push: - branches: - - main + branches: [main] pull_request: - types: - - opened - - synchronize + types: [opened, synchronize] + +env: + SELF_REVIEW_TTL_MIN: '45' + jobs: test: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: '1.26' - - run: go test ./... - - run: go vet ./... - - run: go build -o review-bot ./cmd/review-bot - review: - runs-on: ubuntu-24.04 - if: github.event_name == 'pull_request' && needs.review-gate.outputs.allow_review - == 'true' - needs: - - test - - review-gate - strategy: - matrix: - include: - - name: sonnet - token_secret: SONNET_REVIEW_TOKEN - model: anthropic--claude-4.6-sonnet - - name: gpt - token_secret: GPT_REVIEW_TOKEN - model: gpt-5 - - name: security - token_secret: SECURITY_REVIEW_TOKEN - model: gpt-5 - patterns_repo: rodin/security-patterns - patterns_files: . - system_prompt_file: SECURITY_REVIEW.md - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: '1.26' - - run: go build -o review-bot ./cmd/review-bot - - name: Run ${{ matrix.name }} review - env: - VCS_URL: ${{ github.server_url }} - GITEA_REPO: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} - REVIEWER_TOKEN: ${{ secrets[matrix.token_secret] }} - REVIEWER_NAME: ${{ matrix.name }} - LLM_PROVIDER: aicore - LLM_MODEL: ${{ matrix.model }} - AICORE_CLIENT_ID: ${{ secrets.AICORE_CLIENT_ID }} - AICORE_CLIENT_SECRET: ${{ secrets.AICORE_CLIENT_SECRET }} - AICORE_AUTH_URL: ${{ secrets.AICORE_AUTH_URL }} - AICORE_API_URL: ${{ secrets.AICORE_API_URL }} - AICORE_RESOURCE_GROUP: ${{ secrets.AICORE_RESOURCE_GROUP }} - CONVENTIONS_FILE: CONVENTIONS.md - PATTERNS_REPO: ${{ matrix.patterns_repo || 'rodin/go-patterns' }} - PATTERNS_FILES: ${{ matrix.patterns_files || 'README.md,patterns/' }} - LLM_TIMEOUT: '600' - SYSTEM_PROMPT_FILE: ${{ matrix.system_prompt_file }} - run: ./review-bot + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.26' + - run: go test ./... + - run: go vet ./... + - run: go build -o review-bot ./cmd/review-bot + review-gate: runs-on: ubuntu-24.04 if: github.event_name == 'pull_request' @@ -73,50 +28,83 @@ jobs: allow_review: ${{ steps.gate.outputs.allow_review }} reason: ${{ steps.gate.outputs.reason }} steps: - - name: Check self-review gate - id: gate - env: - GITEA_TOKEN: ${{ secrets.RODIN_TOKEN }} - run: 'REPO=${{ github.repository }} + - name: Check self-review gate + id: gate + env: + GITEA_TOKEN: ${{ secrets.RODIN_TOKEN }} + run: | + set -e + REPO=${{ github.repository }} + PR=${{ github.event.pull_request.number }} + SHA=${{ github.event.pull_request.head.sha }} + TTL_MIN=${SELF_REVIEW_TTL_MIN} + API="${{ github.server_url }}/api/v1" - PR=${{ github.event.pull_request.number }} + PR_JSON=$(curl -sS -H "Authorization: token $GITEA_TOKEN" "$API/repos/$REPO/pulls/$PR") + UPDATED_AT=$(echo "$PR_JSON" | jq -r .updated_at) + NOW=$(date -u +%s) + PR_TS=$(date -u -d "$UPDATED_AT" +%s) + AGE_MIN=$(( (NOW - PR_TS) / 60 )) - SHA=${{ github.event.pull_request.head.sha }} + COMMENTS=$(curl -sS -H "Authorization: token $GITEA_TOKEN" "$API/repos/$REPO/issues/$PR/comments?limit=200") + HAS_SR=$(echo "$COMMENTS" | jq -r --arg sha "$SHA" '[.[] | select(.user.login=="rodin") | select(.body|contains("Self-review against "+$sha)) | select(.body|test("(?im)^###\\s+Doc consistency\\b"))] | length') - TTL_MIN=${SELF_REVIEW_TTL_MIN} + if [ "$HAS_SR" -gt 0 ]; then + ALLOW=true + REASON=self-review + elif [ "$AGE_MIN" -ge "$TTL_MIN" ]; then + ALLOW=true + REASON=ttl + else + ALLOW=false + REASON=missing + fi - API="${{ github.server_url }}/api/v1" + echo "allow_review=$ALLOW" >> $GITHUB_OUTPUT + echo "reason=$REASON" >> $GITHUB_OUTPUT - PR_JSON=$(curl -sS -H "Authorization: token $GITEA_TOKEN" "$API/repos/$REPO/pulls/$PR") - - UPDATED_AT=$(echo "$PR_JSON" | jq -r .updated_at) - - NOW=$(date -u +%s) - - PR_TS=$(date -u -d "$UPDATED_AT" +%s) - - AGE_MIN=$(( (NOW - PR_TS) / 60 )) - - COMMENTS=$(curl -sS -H "Authorization: token $GITEA_TOKEN" "$API/repos/$REPO/issues/$PR/comments?limit=200") - - HAS_SR=$(echo "$COMMENTS" | jq -r --arg sha "$SHA" "[.[] | select(.user.login=="rodin") - | select(.body|contains("Self-review against "+\$sha)) | select(.body|test("(?im)^### - \\s+Doc consistency\\b"))] | length") - - if [ "$HAS_SR" -gt 0 ]; then ALLOW=true; REASON=self-review; elif [ "$AGE_MIN" - -ge "$TTL_MIN" ]; then ALLOW=true; REASON=ttl; else ALLOW=false; REASON=missing; - fi - - echo "allow_review=$ALLOW" >> $GITHUB_OUTPUT - - echo "reason=$REASON" >> $GITHUB_OUTPUT' -env: - SELF_REVIEW_TTL_MIN: '45' -'on': - pull_request: - types: - - opened - - synchronize - push: - branches: - - main + review: + runs-on: ubuntu-24.04 + if: github.event_name == 'pull_request' && needs.review-gate.outputs.allow_review == 'true' + needs: [test, review-gate] + strategy: + matrix: + include: + - name: sonnet + token_secret: SONNET_REVIEW_TOKEN + model: anthropic--claude-4.6-sonnet + - name: gpt + token_secret: GPT_REVIEW_TOKEN + model: gpt-5 + - name: security + token_secret: SECURITY_REVIEW_TOKEN + model: gpt-5 + patterns_repo: rodin/security-patterns + patterns_files: '.' + system_prompt_file: SECURITY_REVIEW.md + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.26' + - run: go build -o review-bot ./cmd/review-bot + - name: Run ${{ matrix.name }} review + env: + VCS_URL: ${{ github.server_url }} + GITEA_REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REVIEWER_TOKEN: ${{ secrets[matrix.token_secret] }} + REVIEWER_NAME: ${{ matrix.name }} + LLM_PROVIDER: aicore + LLM_MODEL: ${{ matrix.model }} + AICORE_CLIENT_ID: ${{ secrets.AICORE_CLIENT_ID }} + AICORE_CLIENT_SECRET: ${{ secrets.AICORE_CLIENT_SECRET }} + AICORE_AUTH_URL: ${{ secrets.AICORE_AUTH_URL }} + AICORE_API_URL: ${{ secrets.AICORE_API_URL }} + AICORE_RESOURCE_GROUP: ${{ secrets.AICORE_RESOURCE_GROUP }} + CONVENTIONS_FILE: 'CONVENTIONS.md' + PATTERNS_REPO: ${{ matrix.patterns_repo || 'rodin/go-patterns' }} + PATTERNS_FILES: ${{ matrix.patterns_files || 'README.md,patterns/' }} + LLM_TIMEOUT: '600' + SYSTEM_PROMPT_FILE: ${{ matrix.system_prompt_file }} + run: ./review-bot -- 2.47.3 From a129f062a25cbfba9e7557544d8dfe3859681fc5 Mon Sep 17 00:00:00 2001 From: Rodin Date: Sat, 16 May 2026 01:42:07 +0000 Subject: [PATCH 4/8] ci: require reason=='self-review' before running heavy review (disable TTL-triggered heavy reviews) --- .gitea/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 254d829..6bc9766 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -65,7 +65,7 @@ jobs: review: runs-on: ubuntu-24.04 - if: github.event_name == 'pull_request' && needs.review-gate.outputs.allow_review == 'true' + if: github.event_name == 'pull_request' && needs.review-gate.outputs.reason == 'self-review' needs: [test, review-gate] strategy: matrix: -- 2.47.3 From b716aed9148221097e408a5d4d364eee05613bf7 Mon Sep 17 00:00:00 2001 From: Rodin Date: Sat, 16 May 2026 01:45:25 +0000 Subject: [PATCH 5/8] ci: trigger review-gate on self-review comments; run heavy review on issue_comment when reason=='self-review' --- .gitea/workflows/ci.yml | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 6bc9766..85bfc84 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -5,6 +5,8 @@ on: branches: [main] pull_request: types: [opened, synchronize] + issue_comment: + types: [created, edited] env: SELF_REVIEW_TTL_MIN: '45' @@ -12,6 +14,7 @@ env: jobs: test: runs-on: ubuntu-24.04 + if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 @@ -23,7 +26,7 @@ jobs: review-gate: runs-on: ubuntu-24.04 - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' || (github.event_name == 'issue_comment' && github.event.issue.pull_request) outputs: allow_review: ${{ steps.gate.outputs.allow_review }} reason: ${{ steps.gate.outputs.reason }} @@ -35,19 +38,23 @@ jobs: run: | set -e REPO=${{ github.repository }} - PR=${{ github.event.pull_request.number }} - SHA=${{ github.event.pull_request.head.sha }} - TTL_MIN=${SELF_REVIEW_TTL_MIN} API="${{ github.server_url }}/api/v1" - + if [ "${GITHUB_EVENT_NAME}" = "issue_comment" ]; then + PR=${{ github.event.issue.number }} + else + PR=${{ github.event.pull_request.number }} + fi + # Get head SHA from PR JSON (works for both events) PR_JSON=$(curl -sS -H "Authorization: token $GITEA_TOKEN" "$API/repos/$REPO/pulls/$PR") + SHA=$(echo "$PR_JSON" | jq -r .head.sha) UPDATED_AT=$(echo "$PR_JSON" | jq -r .updated_at) NOW=$(date -u +%s) PR_TS=$(date -u -d "$UPDATED_AT" +%s) AGE_MIN=$(( (NOW - PR_TS) / 60 )) + TTL_MIN=${SELF_REVIEW_TTL_MIN} COMMENTS=$(curl -sS -H "Authorization: token $GITEA_TOKEN" "$API/repos/$REPO/issues/$PR/comments?limit=200") - HAS_SR=$(echo "$COMMENTS" | jq -r --arg sha "$SHA" '[.[] | select(.user.login=="rodin") | select(.body|contains("Self-review against "+$sha)) | select(.body|test("(?im)^###\\s+Doc consistency\\b"))] | length') + HAS_SR=$(echo "$COMMENTS" | jq -r --arg sha "$SHA" '[.[] | select(.user.login=="rodin") | select(.body|contains("Self-review against "+$sha)) | select(.body|test("(?im)^###\s+Doc consistency\b"))] | length') if [ "$HAS_SR" -gt 0 ]; then ALLOW=true @@ -65,8 +72,8 @@ jobs: review: runs-on: ubuntu-24.04 - if: github.event_name == 'pull_request' && needs.review-gate.outputs.reason == 'self-review' - needs: [test, review-gate] + if: needs.review-gate.outputs.reason == 'self-review' && (github.event_name == 'pull_request' || github.event_name == 'issue_comment') + needs: [review-gate] strategy: matrix: include: @@ -88,11 +95,18 @@ jobs: with: go-version: '1.26' - run: go build -o review-bot ./cmd/review-bot + - name: Set PR_NUMBER for event type + run: | + if [ "${GITHUB_EVENT_NAME}" = "issue_comment" ]; then + echo "PR_NUMBER=${{ github.event.issue.number }}" >> $GITHUB_ENV + else + echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV + fi - name: Run ${{ matrix.name }} review env: VCS_URL: ${{ github.server_url }} GITEA_REPO: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} + PR_NUMBER: ${{ env.PR_NUMBER }} REVIEWER_TOKEN: ${{ secrets[matrix.token_secret] }} REVIEWER_NAME: ${{ matrix.name }} LLM_PROVIDER: aicore -- 2.47.3 From 97b688f95f79f3fb1d8005611327944e9cc42ed9 Mon Sep 17 00:00:00 2001 From: Rodin Date: Sat, 16 May 2026 01:48:55 +0000 Subject: [PATCH 6/8] ci: install jq in review-gate job to ensure JSON parsing succeeds --- .gitea/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 85bfc84..fddf158 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -16,6 +16,8 @@ jobs: runs-on: ubuntu-24.04 if: github.event_name == 'pull_request' steps: + - name: Install jq + run: sudo apt-get update && sudo apt-get install -y jq - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: @@ -31,6 +33,8 @@ jobs: allow_review: ${{ steps.gate.outputs.allow_review }} reason: ${{ steps.gate.outputs.reason }} steps: + - name: Install jq + run: sudo apt-get update && sudo apt-get install -y jq - name: Check self-review gate id: gate env: @@ -90,6 +94,8 @@ jobs: patterns_files: '.' system_prompt_file: SECURITY_REVIEW.md steps: + - name: Install jq + run: sudo apt-get update && sudo apt-get install -y jq - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: -- 2.47.3 From d1ce39bd7b649fa1c66d94133bf00d217da2f4e2 Mon Sep 17 00:00:00 2001 From: Rodin Date: Fri, 15 May 2026 18:50:58 -0700 Subject: [PATCH 7/8] fix(ci): escape regex \s and \b as JSON string literals in jq filter jq parses the test() argument as a JSON string, so \s and \b must be double-escaped (\\s, \\b) to produce literal \s and \b after JSON string parsing. Single backslash forms are invalid JSON escapes and cause a compile error. --- .gitea/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index fddf158..894c127 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -58,7 +58,7 @@ jobs: TTL_MIN=${SELF_REVIEW_TTL_MIN} COMMENTS=$(curl -sS -H "Authorization: token $GITEA_TOKEN" "$API/repos/$REPO/issues/$PR/comments?limit=200") - HAS_SR=$(echo "$COMMENTS" | jq -r --arg sha "$SHA" '[.[] | select(.user.login=="rodin") | select(.body|contains("Self-review against "+$sha)) | select(.body|test("(?im)^###\s+Doc consistency\b"))] | length') + HAS_SR=$(echo "$COMMENTS" | jq -r --arg sha "$SHA" '[.[] | select(.user.login=="rodin") | select(.body|contains("Self-review against "+$sha)) | select(.body|test("(?im)^###\\s+Doc consistency\\b"))] | length') if [ "$HAS_SR" -gt 0 ]; then ALLOW=true -- 2.47.3 From 951aa5d584a39f43c56e23c18021efa6cb18f45d Mon Sep 17 00:00:00 2001 From: Rodin Date: Sat, 16 May 2026 02:15:19 +0000 Subject: [PATCH 8/8] ci: add workflow-lint to sanity-check triggers and gates for ci.yml --- .gitea/workflows/workflow-lint.yml | 42 ++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .gitea/workflows/workflow-lint.yml diff --git a/.gitea/workflows/workflow-lint.yml b/.gitea/workflows/workflow-lint.yml new file mode 100644 index 0000000..2e9a38d --- /dev/null +++ b/.gitea/workflows/workflow-lint.yml @@ -0,0 +1,42 @@ +name: Workflow Lint + +on: + push: + branches: [main] + pull_request: + types: [opened, synchronize] + +jobs: + workflow-sanity: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Sanity check ci.yml triggers and gates + run: | + set -euo pipefail + python3 - <<'PY' +import sys, yaml, re +from pathlib import Path +p = Path('.gitea/workflows/ci.yml') +w = yaml.safe_load(p.read_text()) +# 1) Top-level 'on' must exist and include pull_request + issue_comment +on = w.get('on') +assert isinstance(on, dict), "ci.yml: top-level 'on' must be a mapping" +assert 'pull_request' in on, "ci.yml: missing on.pull_request" +assert 'issue_comment' in on, "ci.yml: missing on.issue_comment (self-review trigger)" +pr_types = on['pull_request'].get('types', []) if isinstance(on['pull_request'], dict) else [] +ic_types = on['issue_comment'].get('types', []) if isinstance(on['issue_comment'], dict) else [] +for t in ['opened','synchronize']: + assert t in pr_types, f"ci.yml: pull_request.types must include '{t}'" +for t in ['created','edited']: + assert t in ic_types, f"ci.yml: issue_comment.types must include '{t}'" +# 2) review-gate must run on both PR and issue_comment (if condition string) +rg_if = w['jobs']['review-gate'].get('if','') +assert 'github.event_name == ' in rg_if and 'issue_comment' in rg_if and 'pull_request' in rg_if, \ + "ci.yml: review-gate.if must include both pull_request and issue_comment" +# 3) review job must require self-review reason +rev_if = w['jobs']['review'].get('if','') +assert "needs.review-gate.outputs.reason == 'self-review'" in rev_if, \ + "ci.yml: review.if must require reason=='self-review'" +print('OK: ci.yml triggers and gates look sane') +PY -- 2.47.3