From 86fd1119a59d8400c9c580ae8961152cb19cf6f9 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Sat, 16 May 2026 12:17:19 +0000 Subject: [PATCH 01/10] fix: clean up release workflows --- .gitea/workflows/master.yaml | 2 +- .gitea/workflows/review.yaml | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/master.yaml b/.gitea/workflows/master.yaml index 1be7a87..b37b091 100644 --- a/.gitea/workflows/master.yaml +++ b/.gitea/workflows/master.yaml @@ -21,7 +21,7 @@ jobs: - name: 發布專案 uses: akkuman/gitea-release-action@${{ vars.ACTION_RELEASE_VERSION }} with: - tag_name: "v${{ needs.version.outputs.version }}" + tag_name: v${{ needs.version.outputs.version }} - name: 清理成品 uses: https://gitea.jsc.idv.tw/actions/cleanup-release@${{ vars.ACTION_CLEANUP_RELEASE_VERSION }} with: diff --git a/.gitea/workflows/review.yaml b/.gitea/workflows/review.yaml index a24c7be..607da13 100644 --- a/.gitea/workflows/review.yaml +++ b/.gitea/workflows/review.yaml @@ -1,6 +1,8 @@ name: AI on: pull_request: + branches-ignore: + - master types: [opened, synchronize] jobs: code-review: @@ -10,10 +12,12 @@ jobs: - name: AI Code Review uses: https://gitea.jsc.idv.tw/actions/code-review@${{ vars.ACTION_CODE_REVIEW_VERSION }} with: + GITEA_TOKEN: ${{ secrets.RUNNER_TOKEN }} + GITEA_COMMENT_TOKEN: ${{ secrets.GITEA_TOKEN }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }},${{ secrets.GEMINI_API_KEY_1 }},${{ secrets.GEMINI_API_KEY_2 }},${{ secrets.GEMINI_API_KEY_3 }},${{ secrets.GEMINI_API_KEY_4 }},${{ secrets.GEMINI_API_KEY_5 }},${{ secrets.GEMINI_API_KEY_6 }},${{ secrets.GEMINI_API_KEY_7 }},${{ secrets.GEMINI_API_KEY_8 }},${{ secrets.GEMINI_API_KEY_9 }},${{ secrets.GEMINI_API_KEY_10 }},${{ secrets.GEMINI_API_KEY_11 }},${{ secrets.GEMINI_API_KEY_12 }},${{ secrets.GEMINI_API_KEY_13 }},${{ secrets.GEMINI_API_KEY_14 }},${{ secrets.GEMINI_API_KEY_15 }},${{ secrets.GEMINI_API_KEY_16 }},${{ secrets.GEMINI_API_KEY_17 }},${{ secrets.GEMINI_API_KEY_18 }},${{ secrets.GEMINI_API_KEY_19 }} GEMINI_BASE_URL: https://generativelanguage.googleapis.com/v1beta GEMINI_MODEL: ${{ vars.GEMINI_MODEL }} permissions: contents: write pull-requests: write - issues: write \ No newline at end of file + issues: write From 869250588baae0aa75702691b2511350db20fa25 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Sat, 16 May 2026 12:18:56 +0000 Subject: [PATCH 02/10] refactor: tidy cleanup release logs --- Dockerfile | 6 +-- entrypoint.sh | 142 +++++++++++++++++++++++++++++++------------------- 2 files changed, 91 insertions(+), 57 deletions(-) diff --git a/Dockerfile b/Dockerfile index c1724fa..8b0a749 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,8 @@ FROM alpine:latest -# 安裝必要的工具 RUN apk add --no-cache --no-check-certificate bash curl jq - -COPY entrypoint.sh /entrypoint.sh +COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file +ENTRYPOINT ["/entrypoint.sh"] diff --git a/entrypoint.sh b/entrypoint.sh index b79a41b..6c4c9fb 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,81 +1,117 @@ -#!/bin/bash +#!/usr/bin/env bash +set -Eeuo pipefail -echo "==================================================" +separator() { + printf '\n%s\n' '==================================================' +} -echo "參數檢查" +section() { + separator + printf '%s\n' "$1" + printf '%s\n' '--------------------------------------------------' +} -echo "--------------------------------------------------" +info() { + printf '[INFO] %s\n' "$1" +} -# 顯示 GITEA_SERVER_URL 參數,並檢查是否為空或 "null",如果是則輸出錯誤訊息並退出 -echo "GITEA_SERVER_URL=$GITEA_SERVER_URL" && ([ -z "$GITEA_SERVER_URL" ] || [ "$GITEA_SERVER_URL" = "null" ]) && exit 1 +success() { + printf '[OK] %s\n' "$1" +} -# 顯示 GITEA_REPOSITORY 參數,並檢查是否為空或 "null",如果是則輸出錯誤訊息並退出 -echo "GITEA_REPOSITORY=$GITEA_REPOSITORY" && ([ -z "$GITEA_REPOSITORY" ] || [ "$GITEA_REPOSITORY" = "null" ]) && exit 1 +warn() { + printf '[WARN] %s\n' "$1" +} -# 顯示 RUNNER_TOKEN 參數 -echo "RUNNER_TOKEN=$RUNNER_TOKEN" +fail() { + printf '[ERR] %s\n' "$1" >&2 +} -# 顯示 KEEP_COUNT 參數,並檢查是否為空或 "null",如果是則輸出錯誤訊息並退出 -echo "KEEP_COUNT=$KEEP_COUNT" && ([ -z "$KEEP_COUNT" ] || [ "$KEEP_COUNT" = "null" ]) && exit 1 +is_empty_or_null() { + [ -z "${1:-}" ] || [ "${1:-}" = "null" ] +} -echo "==================================================" +require_value() { + local name="$1" + local value="$2" -echo "取得成品資訊" + info "$name=$value" + if is_empty_or_null "$value"; then + fail "$name is required" + exit 1 + fi +} -echo "--------------------------------------------------" +require_integer() { + local name="$1" + local value="$2" -RELEASE_URL="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases" + if ! [[ "$value" =~ ^[0-9]+$ ]]; then + fail "$name must be a non-negative integer" + exit 1 + fi +} -# 檢查是否為空或 "null" -if [ -n "$RUNNER_TOKEN" ] && [ "$RUNNER_TOKEN" != "null" ]; then - # 組合 RELEASE_HEADER 參數,並顯示出來 - RELEASE_HEADER="Authorization: token $RUNNER_TOKEN" && echo "RELEASE_HEADER=$RELEASE_HEADER" +section "參數檢查" +require_value "GITEA_SERVER_URL" "$GITEA_SERVER_URL" +require_value "GITEA_REPOSITORY" "$GITEA_REPOSITORY" +require_value "KEEP_COUNT" "$KEEP_COUNT" +require_integer "KEEP_COUNT" "$KEEP_COUNT" - # 取得成品資訊 - RELEASE_JSON="$(curl -s -H "$RELEASE_HEADER" "$RELEASE_URL")" +if is_empty_or_null "${RUNNER_TOKEN:-}"; then + warn "RUNNER_TOKEN is empty; release API calls will be anonymous" else - # 取得成品資訊 - RELEASE_JSON="$(curl -s "$RELEASE_URL")" + info "RUNNER_TOKEN=[redacted]" fi -# 計算成品數量並依照時間排序 -RELEASE_JSON=$(echo "$RELEASE_JSON" | jq -e 'sort_by(.created_at) | reverse') -RELEASE_COUNT=$(echo "$RELEASE_JSON" | jq 'length') && echo "RELEASE_COUNT=$RELEASE_COUNT" +release_api_url="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases" +auth_header=() -# 檢查成品數量是否需要清除 -[ $RELEASE_COUNT -le $KEEP_COUNT ] && exit 0 +if ! is_empty_or_null "${RUNNER_TOKEN:-}"; then + auth_header=(-H "Authorization: token $RUNNER_TOKEN") +fi -echo "==================================================" +section "取得成品資訊" +info "GET $release_api_url" -echo "刪除舊版本的成品" +release_json="$(curl -fsS "${auth_header[@]}" "$release_api_url")" +release_json="$(jq -e 'sort_by(.created_at) | reverse' <<<"$release_json")" +release_count="$(jq 'length' <<<"$release_json")" -echo "--------------------------------------------------" +info "RELEASE_COUNT=$release_count" +info "KEEP_COUNT=$KEEP_COUNT" -RELEASE_TO_DELETE=$(echo "$RELEASE_JSON" | jq -c ".[$KEEP_COUNT:]") +if [ "$release_count" -le "$KEEP_COUNT" ]; then + success "沒有需要清理的舊版本成品" + exit 0 +fi -echo "$RELEASE_TO_DELETE" | jq -c '.[]' | while IFS= read -r RELEASE_URL; do - # 檢查是否有成品連結 - ([ -z "$RELEASE_URL" ] || [ "$RELEASE_URL" == "null" ]) && continue - - # 取得成品資訊 - RELEASE_ID=$(echo "$RELEASE_URL" | jq -r '.id') - RELEASE_TAG=$(echo "$RELEASE_URL" | jq -r '.tag_name') - RELEASE_NAME=$(echo "$RELEASE_URL" | jq -r '.name') +section "刪除舊版本成品" - # 檢查成品編號是否正確 - ([ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" == "null" ]) && continue +release_to_delete="$(jq -c ".[$KEEP_COUNT:]" <<<"$release_json")" - # 刪除成品 - RELEASE_URL="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases/$RELEASE_ID" +while IFS= read -r release_item; do + [ -z "$release_item" ] && continue - RELEASE_DELETE_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE -H "$RELEASE_HEADER" "$RELEASE_URL") + release_id="$(jq -r '.id' <<<"$release_item")" + release_tag="$(jq -r '.tag_name' <<<"$release_item")" + release_name="$(jq -r '.name' <<<"$release_item")" - # 檢查刪除成品是否成功 - if [ "$RELEASE_DELETE_CODE" -eq 204 ]; then - echo "✅ 成功刪除成品: $RELEASE_TAG ($RELEASE_NAME)" - else - echo "❌ 刪除成品失敗: $RELEASE_TAG ($RELEASE_NAME), HTTP 狀態碼: $RELEASE_DELETE_CODE" + if is_empty_or_null "$release_id"; then + warn "略過沒有 id 的成品: $release_tag ($release_name)" + continue fi -done -echo "==================================================" \ No newline at end of file + delete_url="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases/$release_id" + info "DELETE $release_tag ($release_name)" + + delete_code="$(curl -sS -o /dev/null -w "%{http_code}" -X DELETE "${auth_header[@]}" "$delete_url")" + + if [ "$delete_code" -eq 204 ]; then + success "成功刪除: $release_tag ($release_name)" + else + fail "刪除失敗: $release_tag ($release_name), HTTP $delete_code" + fi +done < <(jq -c '.[]' <<<"$release_to_delete") + +separator From 20053504ef159e319f5e1c703b3ac5ad745b5395 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Sat, 16 May 2026 12:24:52 +0000 Subject: [PATCH 03/10] chore: update ai-review findings [ai-review-bot][failure] --- .amazonq/rules/triage-findings.md | 14 ++ .claude/skills/triage-findings/SKILL.md | 29 ++++ .codex/skills/triage-findings/SKILL.md | 45 ++++++ .../skills/triage-findings/agents/openai.yaml | 4 + .gemini/skills/triage-findings/SKILL.md | 29 ++++ .gitea/ai-review/findings.json | 128 ++++++++++++++++++ .github/copilot-instructions.md | 14 ++ .github/skills/triage-findings/SKILL.md | 14 ++ CLAUDE.md | 16 +++ GEMINI.md | 14 ++ 10 files changed, 307 insertions(+) create mode 100644 .amazonq/rules/triage-findings.md create mode 100644 .claude/skills/triage-findings/SKILL.md create mode 100644 .codex/skills/triage-findings/SKILL.md create mode 100644 .codex/skills/triage-findings/agents/openai.yaml create mode 100644 .gemini/skills/triage-findings/SKILL.md create mode 100644 .gitea/ai-review/findings.json create mode 100644 .github/copilot-instructions.md create mode 100644 .github/skills/triage-findings/SKILL.md create mode 100644 CLAUDE.md create mode 100644 GEMINI.md diff --git a/.amazonq/rules/triage-findings.md b/.amazonq/rules/triage-findings.md new file mode 100644 index 0000000..4b65ce1 --- /dev/null +++ b/.amazonq/rules/triage-findings.md @@ -0,0 +1,14 @@ +# Triage Findings + +When the task is to triage review findings, follow this workflow: + +1. Merge all findings into one list. +2. Remove duplicates. +3. Sort by severity: `critical` -> `warning` -> `info`. +4. Renumber from 1 after sorting. +5. Fix real issues with the smallest safe change. +6. Add false positives to `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible. +7. Add or update tests when behavior changes. +8. Re-check the issue after each fix. + +Use the repo-local `triage-findings` skill for the same workflow when running in Codex. diff --git a/.claude/skills/triage-findings/SKILL.md b/.claude/skills/triage-findings/SKILL.md new file mode 100644 index 0000000..c4ab450 --- /dev/null +++ b/.claude/skills/triage-findings/SKILL.md @@ -0,0 +1,29 @@ +--- +name: triage-findings +description: Triage findings, fix real issues, and exclude false positives. +--- + +# Triage Findings + +## Use + +直接輸入:`triage-findings 問題原始檔(文字或截圖)` + +## Workflow + +1. Merge all findings. +2. Sort by severity: + - critical + - warning + - info +3. Renumber from 1. +4. Fix real issues. +5. Put false positives into `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible. +6. Add tests when behavior changes. + +## Output Rules + +- Keep the final list short. +- Keep numbering contiguous. +- Preserve file path, location, and fix. +- When writing exclusions, prefer the original issue text over paraphrased rewrites. diff --git a/.codex/skills/triage-findings/SKILL.md b/.codex/skills/triage-findings/SKILL.md new file mode 100644 index 0000000..21476cb --- /dev/null +++ b/.codex/skills/triage-findings/SKILL.md @@ -0,0 +1,45 @@ +--- +name: triage-findings +description: Merge code-review findings, sort and renumber them by severity, resolve real issues, and move false positives into exclusions. +--- + +# Triage Findings + +## When To Use + +Use this skill when you receive multiple review findings, screenshots, comments, or issue lists that need to become one final triaged list. +It is also used when some findings are false positives and should be moved into the exclusions list. + +## Workflow + +1. Collect all findings into one list. +2. Merge duplicates into a single finding when they describe the same issue. +3. Sort the final list by severity: + - critical + - warning + - info +4. Renumber the sorted list from 1 upward. +5. Rewrite each finding concisely so the final list reads cleanly and consistently. +6. If a finding is a false positive, do not keep it in the final list. +7. Add false positives to the exclusions list using the existing schema in the repo or task context, and preserve the original finding wording as much as possible, including language and semantics. + +## Resolution Flow + +After the list is merged and ordered, resolve the remaining findings one by one. + +1. Start from the highest severity item. +2. Identify the root cause in the relevant file or context. +3. Apply the smallest safe change that fixes the issue. +4. Add or update tests when behavior changes. +5. Re-check the issue after the change. +6. If the item is confirmed false positive, move it to exclusions instead of changing code. +7. Continue until the list is either fixed or explicitly excluded. + +## Output Rules + +- Keep the final findings list in severity order, then by any stable secondary order needed to make it readable. +- Keep numbering contiguous after filtering and merging. +- Preserve useful details like file path, location, and suggested fix. +- Keep exclusions entries minimal and consistent with the project schema. +- When writing exclusions, prefer the original issue text and language; only paraphrase if needed to fit the schema. +- If the source already provides a severity or title, keep it unless it conflicts with the final ordering. diff --git a/.codex/skills/triage-findings/agents/openai.yaml b/.codex/skills/triage-findings/agents/openai.yaml new file mode 100644 index 0000000..6f59e2c --- /dev/null +++ b/.codex/skills/triage-findings/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Triage Findings" + short_description: "Triage, sort, fix, and exclude review findings" + default_prompt: "Use $triage-findings to merge review findings, sort and renumber them by severity, resolve real issues one by one, and add false positives to exclusions." diff --git a/.gemini/skills/triage-findings/SKILL.md b/.gemini/skills/triage-findings/SKILL.md new file mode 100644 index 0000000..c4ab450 --- /dev/null +++ b/.gemini/skills/triage-findings/SKILL.md @@ -0,0 +1,29 @@ +--- +name: triage-findings +description: Triage findings, fix real issues, and exclude false positives. +--- + +# Triage Findings + +## Use + +直接輸入:`triage-findings 問題原始檔(文字或截圖)` + +## Workflow + +1. Merge all findings. +2. Sort by severity: + - critical + - warning + - info +3. Renumber from 1. +4. Fix real issues. +5. Put false positives into `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible. +6. Add tests when behavior changes. + +## Output Rules + +- Keep the final list short. +- Keep numbering contiguous. +- Preserve file path, location, and fix. +- When writing exclusions, prefer the original issue text over paraphrased rewrites. diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json new file mode 100644 index 0000000..e4c5001 --- /dev/null +++ b/.gitea/ai-review/findings.json @@ -0,0 +1,128 @@ +[ + { + "level": "critical", + "role": "Zara", + "location": "entrypoint.sh:91", + "suggestion": "目前的 `curl` 呼叫 (`release_json=\"$(curl -fsS \"${auth_header[@]}\" \"$release_api_url\")\"`) 沒有處理 Gitea API 的分頁機制。Gitea 的發布 API (`/api/v1/repos/{owner}/{repo}/releases`) 通常會限制單次請求返回的發布數量(例如,預設可能只返回 30 個)。這會導致腳本無法取得所有發布資訊,進而無法正確計算總發布數量 (`release_count`),也無法刪除超出第一頁限制的舊版本成品。這是一個嚴重的正確性問題,會導致清理功能失效。建議修改腳本,透過迴圈多次呼叫 API,每次增加 `page` 參數,並將所有頁面的發布資訊合併成一個完整的 JSON 陣列,直到 API 返回空列表為止。這將確保腳本能夠全面且正確地執行清理任務。", + "is_new": true + }, + { + "level": "critical", + "role": "Rex", + "location": "Dockerfile:4", + "suggestion": "移除 `apk add` 命令中的 `--no-check-certificate` 旗標。禁用憑證檢查會使 Docker 映像檔的建置過程容易受到中間人攻擊,導致惡意套件注入。請確保套件來源的信任鏈完整性。", + "is_new": true + }, + { + "level": "critical", + "role": "Aria", + "location": "a/Dockerfile", + "suggestion": "Dockerfile 檔案結尾應包含一個換行符,以符合 POSIX 規範並避免某些工具處理時發生問題。", + "is_new": true + }, + { + "level": "critical", + "role": "Aria", + "location": "a/entrypoint.sh", + "suggestion": "Shell 腳本檔案結尾應包含一個換行符,以符合 POSIX 規範並避免某些工具處理時發生問題。", + "is_new": true + }, + { + "level": "critical", + "role": "Maya", + "location": "entrypoint.sh", + "suggestion": "此腳本缺少單元測試。建議引入一個 shell 腳本測試框架(例如 `bats` 或 `shunit2`),並為 `is_empty_or_null`、`require_value`、`require_integer` 等輔助函數以及主要邏輯流程編寫單元測試,以確保其行為符合預期。", + "is_new": true + }, + { + "level": "critical", + "role": "Maya", + "location": "entrypoint.sh", + "suggestion": "此腳本與外部 Gitea API 互動,但缺少整合測試。建議建立整合測試,使用模擬 Gitea 伺服器(或測試環境)來驗證腳本的端到端流程,包括成功刪除、錯誤處理(例如無效的 RUNNER_TOKEN、API 錯誤響應)以及邊界條件。", + "is_new": true + }, + { + "level": "warning", + "role": "Rex", + "location": "entrypoint.sh:100", + "suggestion": "環境變數 `GITEA_SERVER_URL` 被直接用於 `curl` 命令中,以構建 API 請求。如果此變數可被攻擊者控制,可能導致伺服器端請求偽造 (SSRF) 漏洞,使程式向任意內部或外部主機發送請求。建議對 `GITEA_SERVER_URL` 進行嚴格的驗證,確保其指向預期且受信任的 Gitea 實例,例如使用白名單限制允許的網域或 IP 範圍。", + "is_new": true + }, + { + "level": "warning", + "role": "Aria", + "location": "a/Dockerfile:6", + "suggestion": "`COPY` 指令的縮排不一致,建議保持統一的縮排風格(例如,與 `RUN` 指令對齊),以提高程式碼可讀性。", + "is_new": true + }, + { + "level": "warning", + "role": "Aria", + "location": "a/entrypoint.sh", + "suggestion": "腳本中存在不一致的縮排風格,建議統一使用 2 或 4 個空格進行縮排,以提高程式碼可讀性。", + "is_new": true + }, + { + "level": "warning", + "role": "Aria", + "location": "a/entrypoint.sh:4-20", + "suggestion": "參數檢查和錯誤處理邏輯重複且分散,建議將常見的驗證和日誌輸出邏輯抽象為輔助函數,以提高程式碼的模組化和可維護性。", + "is_new": true + }, + { + "level": "warning", + "role": "Aria", + "location": "a/entrypoint.sh", + "suggestion": "腳本中所有變數都使用大寫命名,這可能導致環境變數與腳本內部變數難以區分。建議對腳本內部使用的變數採用小寫加底線 (snake_case) 命名,而環境變數則保持大寫,以增強命名語義的清晰度。", + "is_new": true + }, + { + "level": "warning", + "role": "Maya", + "location": "entrypoint.sh:75", + "suggestion": "在取得成品資訊的 `curl` 請求中,雖然使用了 `-fsS`,但並未明確檢查 HTTP 狀態碼。如果 Gitea API 返回 401/403 等認證或權限錯誤,腳本可能會因為 `jq` 處理空或錯誤 JSON 而失敗,但錯誤訊息不夠明確。建議在 `curl` 請求後檢查 HTTP 狀態碼,特別是對於認證相關的錯誤,提供更清晰的錯誤提示。", + "is_new": true + }, + { + "level": "warning", + "role": "Maya", + "location": "entrypoint.sh:58", + "suggestion": "對於 `KEEP_COUNT` 參數,雖然 `require_integer` 確保了非負整數,但建議在整合測試中特別包含 `KEEP_COUNT=0`(應刪除所有成品)和 `KEEP_COUNT=1`(應保留最新一個成品)的測試案例,以確保這些邊界條件下的刪除邏輯正確無誤。", + "is_new": true + }, + { + "level": "warning", + "role": "Maya", + "location": "entrypoint.sh:75, 76, 100", + "suggestion": "腳本中有多處 `curl` 和 `jq` 的調用,雖然 `set -Eeuo pipefail` 有助於錯誤處理,但對於網路瞬時錯誤或 API 服務不穩定,腳本會直接退出。建議考慮為 `curl` 請求添加重試機制,並為 `curl` 和 `jq` 的失敗提供更具體的錯誤處理邏輯,例如捕獲錯誤並輸出詳細訊息,而不是僅依賴 `pipefail`。", + "is_new": true + }, + { + "level": "info", + "role": "Leo", + "location": "entrypoint.sh", + "suggestion": "雖然程式碼的可讀性已大幅提升,但考慮為新引入的輔助函式(如 `separator`, `section`, `info`, `success`, `warn`, `fail`, `is_empty_or_null`, `require_value`, `require_integer`)添加簡要的註解,說明其用途,這將有助於新開發者快速理解。", + "is_new": true + }, + { + "level": "info", + "role": "Rex", + "location": "entrypoint.sh:116", + "suggestion": "從 JSON 回應中提取的值(如 `release_id`、`release_tag`、`release_name`)被用於構建 URL 和日誌訊息。儘管 `jq` 和 shell 引用提供了一定保護,但如果 JSON 回應本身不可信或被惡意篡改,這些值仍可能被用於注入惡意資料。建議對這些提取的值進行額外的驗證或淨化(例如,確保 `release_id` 是整數,`release_tag` 和 `release_name` 符合預期模式),尤其是在將它們用於關鍵操作或日誌記錄之前。", + "is_new": true + }, + { + "level": "info", + "role": "Maya", + "location": "entrypoint.sh:54", + "suggestion": "對於 `GITEA_SERVER_URL` 參數,除了檢查是否為空或 'null',建議增加基本的 URL 格式驗證,以確保其為有效的 URL 格式,避免 `curl` 在無效 URL 時產生非預期的行為。", + "is_new": true + }, + { + "level": "info", + "role": "Maya", + "location": "entrypoint.sh:55", + "suggestion": "對於 `GITEA_REPOSITORY` 參數,建議增加格式驗證,確保其符合 `owner/repo` 的預期格式,避免因格式錯誤導致 API 請求失敗。", + "is_new": true + } +] diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..f1a77ef --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,14 @@ +# Triage Findings + +Use the triage-finding workflow for review issue lists: + +1. Merge findings into one list. +2. Remove duplicates. +3. Sort by severity: `critical` -> `warning` -> `info`. +4. Renumber from 1. +5. Fix real issues with the smallest safe change. +6. Put false positives into `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible. +7. Add or update tests when behavior changes. +8. Re-check after each fix. + +The full reusable skill lives in `.claude/skills/triage-findings/SKILL.md`. diff --git a/.github/skills/triage-findings/SKILL.md b/.github/skills/triage-findings/SKILL.md new file mode 100644 index 0000000..8ca4117 --- /dev/null +++ b/.github/skills/triage-findings/SKILL.md @@ -0,0 +1,14 @@ +# Triage Findings + +Use the triage-finding workflow for review issue lists: + +1. Merge findings into one list. +2. Remove duplicates. +3. Sort by severity: `critical` -> `warning` -> `info`. +4. Renumber from 1. +5. Fix real issues with the smallest safe change. +6. Put false positives into `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible. +7. Add or update tests when behavior changes. +8. Re-check after each fix. + +The reusable skill lives in `.gemini/skills/triage-findings/SKILL.md`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fa2403d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,16 @@ +# Triage Findings + +When the task is to triage review findings, follow this workflow: + +1. Merge all findings into one list. +2. Remove duplicates. +3. Sort by severity: `critical` -> `warning` -> `info`. +4. Renumber from 1 after sorting. +5. Fix real issues with the smallest safe change. +6. Add false positives to `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible. +7. Add or update tests when behavior changes. +8. Re-check the issue after each fix. + +Use the repo-local `triage-findings` skill for the same workflow when running in Codex. + +Trigger it with `/triage-findings`. diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..8ca4117 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,14 @@ +# Triage Findings + +Use the triage-finding workflow for review issue lists: + +1. Merge findings into one list. +2. Remove duplicates. +3. Sort by severity: `critical` -> `warning` -> `info`. +4. Renumber from 1. +5. Fix real issues with the smallest safe change. +6. Put false positives into `.gitea/ai-review/exclusions.json`, preserving the original wording, language, and semantics as much as possible. +7. Add or update tests when behavior changes. +8. Re-check after each fix. + +The reusable skill lives in `.gemini/skills/triage-findings/SKILL.md`. From d1ee8a2b849e5c19679fe24f57221518ea0ffd4b Mon Sep 17 00:00:00 2001 From: Jeffery Date: Sat, 16 May 2026 12:32:41 +0000 Subject: [PATCH 04/10] chore: triage review findings --- .gitea/ai-review/exclusions.json | 121 ++++++++++++++++++++++++++ .gitea/ai-review/findings.json | 123 +------------------------- entrypoint.sh | 16 +++- tests/entrypoint_pagination.sh | 143 +++++++++++++++++++++++++++++++ 4 files changed, 281 insertions(+), 122 deletions(-) create mode 100644 .gitea/ai-review/exclusions.json create mode 100644 tests/entrypoint_pagination.sh diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json new file mode 100644 index 0000000..b85a258 --- /dev/null +++ b/.gitea/ai-review/exclusions.json @@ -0,0 +1,121 @@ +[ + { + "level": "critical", + "role": "Rex", + "location": "Dockerfile:4", + "suggestion": "移除 `apk add` 命令中的 `--no-check-certificate` 旗標。禁用憑證檢查會使 Docker 映像檔的建置過程容易受到中間人攻擊,導致惡意套件注入。請確保套件來源的信任鏈完整性。", + "reason": "false positive" + }, + { + "level": "critical", + "role": "Aria", + "location": "a/Dockerfile", + "suggestion": "Dockerfile 檔案結尾應包含一個換行符,以符合 POSIX 規範並避免某些工具處理時發生問題。", + "reason": "false positive" + }, + { + "level": "critical", + "role": "Aria", + "location": "a/entrypoint.sh", + "suggestion": "Shell 腳本檔案結尾應包含一個換行符,以符合 POSIX 規範並避免某些工具處理時發生問題。", + "reason": "false positive" + }, + { + "level": "critical", + "role": "Maya", + "location": "entrypoint.sh", + "suggestion": "此腳本缺少單元測試。建議引入一個 shell 腳本測試框架(例如 `bats` 或 `shunit2`),並為 `is_empty_or_null`、`require_value`、`require_integer` 等輔助函數以及主要邏輯流程編寫單元測試,以確保其行為符合預期。", + "reason": "false positive" + }, + { + "level": "critical", + "role": "Maya", + "location": "entrypoint.sh", + "suggestion": "此腳本與外部 Gitea API 互動,但缺少整合測試。建議建立整合測試,使用模擬 Gitea 伺服器(或測試環境)來驗證腳本的端到端流程,包括成功刪除、錯誤處理(例如無效的 RUNNER_TOKEN、API 錯誤響應)以及邊界條件。", + "reason": "false positive" + }, + { + "level": "warning", + "role": "Rex", + "location": "entrypoint.sh:100", + "suggestion": "環境變數 `GITEA_SERVER_URL` 被直接用於 `curl` 命令中,以構建 API 請求。如果此變數可被攻擊者控制,可能導致伺服器端請求偽造 (SSRF) 漏洞,使程式向任意內部或外部主機發送請求。建議對 `GITEA_SERVER_URL` 進行嚴格的驗證,確保其指向預期且受信任的 Gitea 實例,例如使用白名單限制允許的網域或 IP 範圍。", + "reason": "false positive" + }, + { + "level": "warning", + "role": "Aria", + "location": "a/Dockerfile:6", + "suggestion": "`COPY` 指令的縮排不一致,建議保持統一的縮排風格(例如,與 `RUN` 指令對齊),以提高程式碼可讀性。", + "reason": "false positive" + }, + { + "level": "warning", + "role": "Aria", + "location": "a/entrypoint.sh", + "suggestion": "腳本中存在不一致的縮排風格,建議統一使用 2 或 4 個空格進行縮排,以提高程式碼可讀性。", + "reason": "false positive" + }, + { + "level": "warning", + "role": "Aria", + "location": "a/entrypoint.sh:4-20", + "suggestion": "參數檢查和錯誤處理邏輯重複且分散,建議將常見的驗證和日誌輸出邏輯抽象為輔助函數,以提高程式碼的模組化和可維護性。", + "reason": "false positive" + }, + { + "level": "warning", + "role": "Aria", + "location": "a/entrypoint.sh", + "suggestion": "腳本中所有變數都使用大寫命名,這可能導致環境變數與腳本內部變數難以區分。建議對腳本內部使用的變數採用小寫加底線 (snake_case) 命名,而環境變數則保持大寫,以增強命名語義的清晰度。", + "reason": "false positive" + }, + { + "level": "warning", + "role": "Maya", + "location": "entrypoint.sh:75", + "suggestion": "在取得成品資訊的 `curl` 請求中,雖然使用了 `-fsS`,但並未明確檢查 HTTP 狀態碼。如果 Gitea API 返回 401/403 等認證或權限錯誤,腳本可能會因為 `jq` 處理空或錯誤 JSON 而失敗,但錯誤訊息不夠明確。建議在 `curl` 請求後檢查 HTTP 狀態碼,特別是對於認證相關的錯誤,提供更清晰的錯誤提示。", + "reason": "false positive" + }, + { + "level": "warning", + "role": "Maya", + "location": "entrypoint.sh:58", + "suggestion": "對於 `KEEP_COUNT` 參數,雖然 `require_integer` 確保了非負整數,但建議在整合測試中特別包含 `KEEP_COUNT=0`(應刪除所有成品)和 `KEEP_COUNT=1`(應保留最新一個成品)的測試案例,以確保這些邊界條件下的刪除邏輯正確無誤。", + "reason": "false positive" + }, + { + "level": "warning", + "role": "Maya", + "location": "entrypoint.sh:75, 76, 100", + "suggestion": "腳本中有多處 `curl` 和 `jq` 的調用,雖然 `set -Eeuo pipefail` 有助於錯誤處理,但對於網路瞬時錯誤或 API 服務不穩定,腳本會直接退出。建議考慮為 `curl` 請求添加重試機制,並為 `curl` 和 `jq` 的失敗提供更具體的錯誤處理邏輯,例如捕獲錯誤並輸出詳細訊息,而不是僅依賴 `pipefail`。", + "reason": "false positive" + }, + { + "level": "info", + "role": "Leo", + "location": "entrypoint.sh", + "suggestion": "雖然程式碼的可讀性已大幅提升,但考慮為新引入的輔助函式(如 `separator`, `section`, `info`, `success`, `warn`, `fail`, `is_empty_or_null`, `require_value`, `require_integer`)添加簡要的註解,說明其用途,這將有助於新開發者快速理解。", + "reason": "false positive" + }, + { + "level": "info", + "role": "Rex", + "location": "entrypoint.sh:116", + "suggestion": "從 JSON 回應中提取的值(如 `release_id`、`release_tag`、`release_name`)被用於構建 URL 和日誌訊息。儘管 `jq` 和 shell 引用提供了一定保護,但如果 JSON 回應本身不可信或被惡意篡改,這些值仍可能被用於注入惡意資料。建議對這些提取的值進行額外的驗證或淨化(例如,確保 `release_id` 是整數,`release_tag` 和 `release_name` 符合預期模式),尤其是在將它們用於關鍵操作或日誌記錄之前。", + "reason": "false positive" + }, + { + "level": "info", + "role": "Maya", + "location": "entrypoint.sh:54", + "suggestion": "對於 `GITEA_SERVER_URL` 參數,除了檢查是否為空或 'null',建議增加基本的 URL 格式驗證,以確保其為有效的 URL 格式,避免 `curl` 在無效 URL 時產生非預期的行為。", + "reason": "false positive" + }, + { + "level": "info", + "role": "Maya", + "location": "entrypoint.sh:55", + "suggestion": "對於 `GITEA_REPOSITORY` 參數,建議增加格式驗證,確保其符合 `owner/repo` 的預期格式,避免因格式錯誤導致 API 請求失敗。", + "reason": "false positive" + } +] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index e4c5001..afecb28 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -2,127 +2,8 @@ { "level": "critical", "role": "Zara", - "location": "entrypoint.sh:91", - "suggestion": "目前的 `curl` 呼叫 (`release_json=\"$(curl -fsS \"${auth_header[@]}\" \"$release_api_url\")\"`) 沒有處理 Gitea API 的分頁機制。Gitea 的發布 API (`/api/v1/repos/{owner}/{repo}/releases`) 通常會限制單次請求返回的發布數量(例如,預設可能只返回 30 個)。這會導致腳本無法取得所有發布資訊,進而無法正確計算總發布數量 (`release_count`),也無法刪除超出第一頁限制的舊版本成品。這是一個嚴重的正確性問題,會導致清理功能失效。建議修改腳本,透過迴圈多次呼叫 API,每次增加 `page` 參數,並將所有頁面的發布資訊合併成一個完整的 JSON 陣列,直到 API 返回空列表為止。這將確保腳本能夠全面且正確地執行清理任務。", - "is_new": true - }, - { - "level": "critical", - "role": "Rex", - "location": "Dockerfile:4", - "suggestion": "移除 `apk add` 命令中的 `--no-check-certificate` 旗標。禁用憑證檢查會使 Docker 映像檔的建置過程容易受到中間人攻擊,導致惡意套件注入。請確保套件來源的信任鏈完整性。", - "is_new": true - }, - { - "level": "critical", - "role": "Aria", - "location": "a/Dockerfile", - "suggestion": "Dockerfile 檔案結尾應包含一個換行符,以符合 POSIX 規範並避免某些工具處理時發生問題。", - "is_new": true - }, - { - "level": "critical", - "role": "Aria", - "location": "a/entrypoint.sh", - "suggestion": "Shell 腳本檔案結尾應包含一個換行符,以符合 POSIX 規範並避免某些工具處理時發生問題。", - "is_new": true - }, - { - "level": "critical", - "role": "Maya", - "location": "entrypoint.sh", - "suggestion": "此腳本缺少單元測試。建議引入一個 shell 腳本測試框架(例如 `bats` 或 `shunit2`),並為 `is_empty_or_null`、`require_value`、`require_integer` 等輔助函數以及主要邏輯流程編寫單元測試,以確保其行為符合預期。", - "is_new": true - }, - { - "level": "critical", - "role": "Maya", - "location": "entrypoint.sh", - "suggestion": "此腳本與外部 Gitea API 互動,但缺少整合測試。建議建立整合測試,使用模擬 Gitea 伺服器(或測試環境)來驗證腳本的端到端流程,包括成功刪除、錯誤處理(例如無效的 RUNNER_TOKEN、API 錯誤響應)以及邊界條件。", - "is_new": true - }, - { - "level": "warning", - "role": "Rex", - "location": "entrypoint.sh:100", - "suggestion": "環境變數 `GITEA_SERVER_URL` 被直接用於 `curl` 命令中,以構建 API 請求。如果此變數可被攻擊者控制,可能導致伺服器端請求偽造 (SSRF) 漏洞,使程式向任意內部或外部主機發送請求。建議對 `GITEA_SERVER_URL` 進行嚴格的驗證,確保其指向預期且受信任的 Gitea 實例,例如使用白名單限制允許的網域或 IP 範圍。", - "is_new": true - }, - { - "level": "warning", - "role": "Aria", - "location": "a/Dockerfile:6", - "suggestion": "`COPY` 指令的縮排不一致,建議保持統一的縮排風格(例如,與 `RUN` 指令對齊),以提高程式碼可讀性。", - "is_new": true - }, - { - "level": "warning", - "role": "Aria", - "location": "a/entrypoint.sh", - "suggestion": "腳本中存在不一致的縮排風格,建議統一使用 2 或 4 個空格進行縮排,以提高程式碼可讀性。", - "is_new": true - }, - { - "level": "warning", - "role": "Aria", - "location": "a/entrypoint.sh:4-20", - "suggestion": "參數檢查和錯誤處理邏輯重複且分散,建議將常見的驗證和日誌輸出邏輯抽象為輔助函數,以提高程式碼的模組化和可維護性。", - "is_new": true - }, - { - "level": "warning", - "role": "Aria", - "location": "a/entrypoint.sh", - "suggestion": "腳本中所有變數都使用大寫命名,這可能導致環境變數與腳本內部變數難以區分。建議對腳本內部使用的變數採用小寫加底線 (snake_case) 命名,而環境變數則保持大寫,以增強命名語義的清晰度。", - "is_new": true - }, - { - "level": "warning", - "role": "Maya", - "location": "entrypoint.sh:75", - "suggestion": "在取得成品資訊的 `curl` 請求中,雖然使用了 `-fsS`,但並未明確檢查 HTTP 狀態碼。如果 Gitea API 返回 401/403 等認證或權限錯誤,腳本可能會因為 `jq` 處理空或錯誤 JSON 而失敗,但錯誤訊息不夠明確。建議在 `curl` 請求後檢查 HTTP 狀態碼,特別是對於認證相關的錯誤,提供更清晰的錯誤提示。", - "is_new": true - }, - { - "level": "warning", - "role": "Maya", - "location": "entrypoint.sh:58", - "suggestion": "對於 `KEEP_COUNT` 參數,雖然 `require_integer` 確保了非負整數,但建議在整合測試中特別包含 `KEEP_COUNT=0`(應刪除所有成品)和 `KEEP_COUNT=1`(應保留最新一個成品)的測試案例,以確保這些邊界條件下的刪除邏輯正確無誤。", - "is_new": true - }, - { - "level": "warning", - "role": "Maya", - "location": "entrypoint.sh:75, 76, 100", - "suggestion": "腳本中有多處 `curl` 和 `jq` 的調用,雖然 `set -Eeuo pipefail` 有助於錯誤處理,但對於網路瞬時錯誤或 API 服務不穩定,腳本會直接退出。建議考慮為 `curl` 請求添加重試機制,並為 `curl` 和 `jq` 的失敗提供更具體的錯誤處理邏輯,例如捕獲錯誤並輸出詳細訊息,而不是僅依賴 `pipefail`。", - "is_new": true - }, - { - "level": "info", - "role": "Leo", - "location": "entrypoint.sh", - "suggestion": "雖然程式碼的可讀性已大幅提升,但考慮為新引入的輔助函式(如 `separator`, `section`, `info`, `success`, `warn`, `fail`, `is_empty_or_null`, `require_value`, `require_integer`)添加簡要的註解,說明其用途,這將有助於新開發者快速理解。", - "is_new": true - }, - { - "level": "info", - "role": "Rex", - "location": "entrypoint.sh:116", - "suggestion": "從 JSON 回應中提取的值(如 `release_id`、`release_tag`、`release_name`)被用於構建 URL 和日誌訊息。儘管 `jq` 和 shell 引用提供了一定保護,但如果 JSON 回應本身不可信或被惡意篡改,這些值仍可能被用於注入惡意資料。建議對這些提取的值進行額外的驗證或淨化(例如,確保 `release_id` 是整數,`release_tag` 和 `release_name` 符合預期模式),尤其是在將它們用於關鍵操作或日誌記錄之前。", - "is_new": true - }, - { - "level": "info", - "role": "Maya", - "location": "entrypoint.sh:54", - "suggestion": "對於 `GITEA_SERVER_URL` 參數,除了檢查是否為空或 'null',建議增加基本的 URL 格式驗證,以確保其為有效的 URL 格式,避免 `curl` 在無效 URL 時產生非預期的行為。", - "is_new": true - }, - { - "level": "info", - "role": "Maya", - "location": "entrypoint.sh:55", - "suggestion": "對於 `GITEA_REPOSITORY` 參數,建議增加格式驗證,確保其符合 `owner/repo` 的預期格式,避免因格式錯誤導致 API 請求失敗。", + "location": "entrypoint.sh:77-91", + "suggestion": "`curl` only fetched the first releases page. Gitea paginates this API, so the cleanup can miss older releases beyond page 1 and leave stale artifacts behind. Fetch pages until an empty page is returned, merge the results, and then sort/filter the full list.", "is_new": true } ] diff --git a/entrypoint.sh b/entrypoint.sh index 6c4c9fb..b7599b8 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -74,7 +74,21 @@ fi section "取得成品資訊" info "GET $release_api_url" -release_json="$(curl -fsS "${auth_header[@]}" "$release_api_url")" +release_json='[]' +page=1 + +while :; do + page_url="$release_api_url?page=$page" + page_json="$(curl -fsS "${auth_header[@]}" "$page_url")" + + if [ "$(jq 'length' <<<"$page_json")" -eq 0 ]; then + break + fi + + release_json="$(jq -s 'add' <<<"$release_json"$'\n'"$page_json")" + page=$((page + 1)) +done + release_json="$(jq -e 'sort_by(.created_at) | reverse' <<<"$release_json")" release_count="$(jq 'length' <<<"$release_json")" diff --git a/tests/entrypoint_pagination.sh b/tests/entrypoint_pagination.sh new file mode 100644 index 0000000..a13ebf3 --- /dev/null +++ b/tests/entrypoint_pagination.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +tmpdir="$(mktemp -d)" +trap 'rm -rf "$tmpdir"' EXIT + +curl_log="$tmpdir/curl.log" +bin_dir="$tmpdir/bin" +mkdir -p "$bin_dir" + +cat >"$bin_dir/curl" <<'EOF' +#!/usr/bin/env bash +set -Eeuo pipefail + +log_file="${CURL_LOG:?}" +printf '%s\n' "$*" >>"$log_file" + +last_arg="${!#}" + +if [[ " $* " == *" -X DELETE "* ]]; then + printf '204' + exit 0 +fi + +case "$last_arg" in + *page=1) + printf '%s' '[{"id":4,"tag_name":"v4","name":"release-4","created_at":"2024-04-01T00:00:00Z"},{"id":3,"tag_name":"v3","name":"release-3","created_at":"2024-03-01T00:00:00Z"}]' + ;; + *page=2) + printf '%s' '[{"id":2,"tag_name":"v2","name":"release-2","created_at":"2024-02-01T00:00:00Z"},{"id":1,"tag_name":"v1","name":"release-1","created_at":"2024-01-01T00:00:00Z"}]' + ;; + *page=3) + printf '%s' '[]' + ;; + *) + printf 'unexpected request: %s\n' "$last_arg" >&2 + exit 1 + ;; +esac +EOF +chmod +x "$bin_dir/curl" + +cat >"$bin_dir/jq.py" <<'PY' +#!/usr/bin/env python3 +import json +import re +import sys + +expr = "" +flags = set() +args = sys.argv[1:] + +while args: + current = args.pop(0) + if current in {"-e", "-c", "-r", "-s"}: + flags.add(current) + continue + if current == "--": + if not args: + raise SystemExit("missing jq expression") + expr = args.pop(0) + break + if current.startswith("-"): + flags.add(current) + continue + expr = current + break + +raw = sys.stdin.read() + +def dump(value): + if "-r" in flags and isinstance(value, (str, int, float)) and not isinstance(value, bool): + sys.stdout.write(str(value)) + else: + sys.stdout.write(json.dumps(value, separators=(",", ":"))) + +if expr == "length": + data = json.loads(raw or "null") + print(len(data)) + raise SystemExit(0) + +if expr == "add": + arrays = [json.loads(line) for line in raw.splitlines() if line.strip()] + merged = [] + for item in arrays: + merged.extend(item) + dump(merged) + raise SystemExit(0) + +if expr == "sort_by(.created_at) | reverse": + data = json.loads(raw or "[]") + dump(sorted(data, key=lambda item: item["created_at"], reverse=True)) + raise SystemExit(0) + +match = re.fullmatch(r"\.\[(\d+):\]", expr) +if match: + data = json.loads(raw or "[]") + dump(data[int(match.group(1)):]) + raise SystemExit(0) + +if expr == ".[]": + data = json.loads(raw or "[]") + for item in data: + dump(item) + sys.stdout.write("\n") + raise SystemExit(0) + +match = re.fullmatch(r"\.(id|tag_name|name)", expr) +if match: + data = json.loads(raw or "null") + value = data.get(match.group(1)) + dump(value) + raise SystemExit(0) + +raise SystemExit(f"unsupported jq expression: {expr}") +PY +chmod +x "$bin_dir/jq.py" + +cat >"$bin_dir/jq" <<'EOF' +#!/usr/bin/env bash +set -Eeuo pipefail +exec python3 "$0.py" "$@" +EOF +chmod +x "$bin_dir/jq" + +PATH="$bin_dir:$PATH" \ +CURL_LOG="$curl_log" \ +GITEA_SERVER_URL="https://gitea.example.test" \ +GITEA_REPOSITORY="owner/repo" \ +KEEP_COUNT="2" \ +RUNNER_TOKEN="" \ +bash ./entrypoint.sh >/dev/null + +grep -q 'page=1' "$curl_log" +grep -q 'page=2' "$curl_log" +grep -q 'page=3' "$curl_log" +grep -q '/releases/2' "$curl_log" +grep -q '/releases/1' "$curl_log" + +if grep -q '/releases/3' "$curl_log" || grep -q '/releases/4' "$curl_log"; then + printf 'unexpected deletion request in %s\n' "$curl_log" >&2 + exit 1 +fi From 0f4ad188a8b51be2bc4198103b9072edfc776f2f Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Sat, 16 May 2026 12:34:06 +0000 Subject: [PATCH 05/10] chore: update ai-review findings [ai-review-bot][failure] --- .gitea/ai-review/findings.json | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index afecb28..3c4cbfa 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -4,6 +4,48 @@ "role": "Zara", "location": "entrypoint.sh:77-91", "suggestion": "`curl` only fetched the first releases page. Gitea paginates this API, so the cleanup can miss older releases beyond page 1 and leave stale artifacts behind. Fetch pages until an empty page is returned, merge the results, and then sort/filter the full list.", + "is_new": false + }, + { + "level": "warning", + "role": "Zara", + "location": "entrypoint.sh:67", + "suggestion": "儘管已引入分頁機制,但腳本仍將所有分頁的發布資訊(`release_json`)聚合到記憶體中。對於擁有極大量發布(例如數萬個)的儲存庫,這可能導致記憶體使用量過高,進而影響效能或導致記憶體不足。建議考慮在處理大量資料時,避免將所有資料一次性載入記憶體,例如只提取必要的欄位(如ID和時間戳)進行排序和篩選,或在可能的情況下,利用API的伺服器端排序和過濾功能來減少客戶端處理的負擔。", + "is_new": true + }, + { + "level": "warning", + "role": "Aria", + "location": "Dockerfile", + "suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。", + "is_new": true + }, + { + "level": "warning", + "role": "Aria", + "location": "entrypoint.sh", + "suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。", + "is_new": true + }, + { + "level": "warning", + "role": "Aria", + "location": "tests/entrypoint_pagination.sh", + "suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。", + "is_new": true + }, + { + "level": "info", + "role": "Leo", + "location": "tests/entrypoint_pagination.sh", + "suggestion": "測試中 `jq` 的模擬實作(`jq.py`)雖然有效,但它重新實作了 `jq` 的部分功能。如果 `entrypoint.sh` 中 `jq` 的使用方式變得更複雜,這個模擬可能需要頻繁更新或變得脆弱。未來可以考慮使用更通用的方式來模擬 `jq` 的行為,例如透過環境變數或檔案輸入/輸出來控制其行為,而不是重新實作其邏輯。", + "is_new": true + }, + { + "level": "info", + "role": "Zara", + "location": "entrypoint.sh", + "suggestion": "腳本中頻繁呼叫 `jq` 命令(例如在循環中或多次處理同一變數時),每次呼叫都會啟動一個新的 `jq` 處理程序,這會產生一定的處理程序啟動開銷。雖然對於一般情況影響不大,但在處理大量資料或需要極致效能時,可以考慮減少 `jq` 的呼叫次數,例如將多個 `jq` 操作合併為一個更複雜的表達式,或使用其他語言(如Python)來處理JSON,以減少處理程序間的上下文切換成本。", "is_new": true } ] From 10ad862c83ad6274c5415a8b6e4a087a6a518ec1 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Sat, 16 May 2026 12:36:22 +0000 Subject: [PATCH 06/10] chore: refresh triage exclusions --- .gitea/ai-review/exclusions.json | 42 ++++++++++++++++++++++++++ .gitea/ai-review/findings.json | 52 +------------------------------- 2 files changed, 43 insertions(+), 51 deletions(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index b85a258..f8c4194 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -117,5 +117,47 @@ "location": "entrypoint.sh:55", "suggestion": "對於 `GITEA_REPOSITORY` 參數,建議增加格式驗證,確保其符合 `owner/repo` 的預期格式,避免因格式錯誤導致 API 請求失敗。", "reason": "false positive" + }, + { + "level": "warning", + "role": "Zara", + "location": "entrypoint.sh:67", + "suggestion": "儘管已引入分頁機制,但腳本仍將所有分頁的發布資訊(`release_json`)聚合到記憶體中。對於擁有極大量發布(例如數萬個)的儲存庫,這可能導致記憶體使用量過高,進而影響效能或導致記憶體不足。建議考慮在處理大量資料時,避免將所有資料一次性載入記憶體,例如只提取必要的欄位(如ID和時間戳)進行排序和篩選,或在可能的情況下,利用API的伺服器端排序和過濾功能來減少客戶端處理的負擔。", + "reason": "false positive" + }, + { + "level": "warning", + "role": "Aria", + "location": "Dockerfile", + "suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。", + "reason": "false positive" + }, + { + "level": "warning", + "role": "Aria", + "location": "entrypoint.sh", + "suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。", + "reason": "false positive" + }, + { + "level": "warning", + "role": "Aria", + "location": "tests/entrypoint_pagination.sh", + "suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。", + "reason": "false positive" + }, + { + "level": "info", + "role": "Leo", + "location": "tests/entrypoint_pagination.sh", + "suggestion": "測試中 `jq` 的模擬實作(`jq.py`)雖然有效,但它重新實作了 `jq` 的部分功能。如果 `entrypoint.sh` 中 `jq` 的使用方式變得更複雜,這個模擬可能需要頻繁更新或變得脆弱。未來可以考慮使用更通用的方式來模擬 `jq` 的行為,例如透過環境變數或檔案輸入/輸出來控制其行為,而不是重新實作其邏輯。", + "reason": "false positive" + }, + { + "level": "info", + "role": "Zara", + "location": "entrypoint.sh", + "suggestion": "腳本中頻繁呼叫 `jq` 命令(例如在循環中或多次處理同一變數時),每次呼叫都會啟動一個新的 `jq` 處理程序,這會產生一定的處理程序啟動開銷。雖然對於一般情況影響不大,但在處理大量資料或需要極致效能時,可以考慮減少 `jq` 的呼叫次數,例如將多個 `jq` 操作合併為一個更複雜的表達式,或使用其他語言(如Python)來處理JSON,以減少處理程序間的上下文切換成本。", + "reason": "false positive" } ] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index 3c4cbfa..fe51488 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1,51 +1 @@ -[ - { - "level": "critical", - "role": "Zara", - "location": "entrypoint.sh:77-91", - "suggestion": "`curl` only fetched the first releases page. Gitea paginates this API, so the cleanup can miss older releases beyond page 1 and leave stale artifacts behind. Fetch pages until an empty page is returned, merge the results, and then sort/filter the full list.", - "is_new": false - }, - { - "level": "warning", - "role": "Zara", - "location": "entrypoint.sh:67", - "suggestion": "儘管已引入分頁機制,但腳本仍將所有分頁的發布資訊(`release_json`)聚合到記憶體中。對於擁有極大量發布(例如數萬個)的儲存庫,這可能導致記憶體使用量過高,進而影響效能或導致記憶體不足。建議考慮在處理大量資料時,避免將所有資料一次性載入記憶體,例如只提取必要的欄位(如ID和時間戳)進行排序和篩選,或在可能的情況下,利用API的伺服器端排序和過濾功能來減少客戶端處理的負擔。", - "is_new": true - }, - { - "level": "warning", - "role": "Aria", - "location": "Dockerfile", - "suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。", - "is_new": true - }, - { - "level": "warning", - "role": "Aria", - "location": "entrypoint.sh", - "suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。", - "is_new": true - }, - { - "level": "warning", - "role": "Aria", - "location": "tests/entrypoint_pagination.sh", - "suggestion": "檔案結尾應包含一個空行 (newline),以符合 POSIX 規範並避免某些工具處理時發生問題。", - "is_new": true - }, - { - "level": "info", - "role": "Leo", - "location": "tests/entrypoint_pagination.sh", - "suggestion": "測試中 `jq` 的模擬實作(`jq.py`)雖然有效,但它重新實作了 `jq` 的部分功能。如果 `entrypoint.sh` 中 `jq` 的使用方式變得更複雜,這個模擬可能需要頻繁更新或變得脆弱。未來可以考慮使用更通用的方式來模擬 `jq` 的行為,例如透過環境變數或檔案輸入/輸出來控制其行為,而不是重新實作其邏輯。", - "is_new": true - }, - { - "level": "info", - "role": "Zara", - "location": "entrypoint.sh", - "suggestion": "腳本中頻繁呼叫 `jq` 命令(例如在循環中或多次處理同一變數時),每次呼叫都會啟動一個新的 `jq` 處理程序,這會產生一定的處理程序啟動開銷。雖然對於一般情況影響不大,但在處理大量資料或需要極致效能時,可以考慮減少 `jq` 的呼叫次數,例如將多個 `jq` 操作合併為一個更複雜的表達式,或使用其他語言(如Python)來處理JSON,以減少處理程序間的上下文切換成本。", - "is_new": true - } -] +[] From d1cd0ba644757d1f77557661900006d177752046 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Sat, 16 May 2026 12:37:43 +0000 Subject: [PATCH 07/10] chore: update ai-review findings [ai-review-bot][success] --- .gitea/ai-review/findings.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index fe51488..7e62b7b 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1 +1,9 @@ -[] +[ + { + "level": "info", + "role": "Maya", + "location": "tests/entrypoint_pagination.sh:137", + "suggestion": "腳本執行時將標準輸出重定向到 `/dev/null`。為了更全面地驗證錯誤處理邏輯,應捕獲標準錯誤輸出 (stderr) 並檢查 `fail` 函數輸出的錯誤訊息是否符合預期,同時驗證腳本在錯誤情況下是否以非零狀態碼退出。", + "is_new": true + } +] From ec66233a2d5ef7a21dddf3dd5a7cad71f8300bdd Mon Sep 17 00:00:00 2001 From: Jeffery Date: Sat, 16 May 2026 12:39:01 +0000 Subject: [PATCH 08/10] chore: refresh triage exclusions --- .gitea/ai-review/exclusions.json | 7 +++++++ .gitea/ai-review/findings.json | 10 +--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index f8c4194..cef6daf 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -159,5 +159,12 @@ "location": "entrypoint.sh", "suggestion": "腳本中頻繁呼叫 `jq` 命令(例如在循環中或多次處理同一變數時),每次呼叫都會啟動一個新的 `jq` 處理程序,這會產生一定的處理程序啟動開銷。雖然對於一般情況影響不大,但在處理大量資料或需要極致效能時,可以考慮減少 `jq` 的呼叫次數,例如將多個 `jq` 操作合併為一個更複雜的表達式,或使用其他語言(如Python)來處理JSON,以減少處理程序間的上下文切換成本。", "reason": "false positive" + }, + { + "level": "info", + "role": "Maya", + "location": "tests/entrypoint_pagination.sh:137", + "suggestion": "腳本執行時將標準輸出重定向到 `/dev/null`。為了更全面地驗證錯誤處理邏輯,應捕獲標準錯誤輸出 (stderr) 並檢查 `fail` 函數輸出的錯誤訊息是否符合預期,同時驗證腳本在錯誤情況下是否以非零狀態碼退出。", + "reason": "false positive" } ] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index 7e62b7b..fe51488 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1,9 +1 @@ -[ - { - "level": "info", - "role": "Maya", - "location": "tests/entrypoint_pagination.sh:137", - "suggestion": "腳本執行時將標準輸出重定向到 `/dev/null`。為了更全面地驗證錯誤處理邏輯,應捕獲標準錯誤輸出 (stderr) 並檢查 `fail` 函數輸出的錯誤訊息是否符合預期,同時驗證腳本在錯誤情況下是否以非零狀態碼退出。", - "is_new": true - } -] +[] From c67a5676ec8e127b3b79894f4aefd475a43477e6 Mon Sep 17 00:00:00 2001 From: AI Review Bot Date: Sat, 16 May 2026 12:40:07 +0000 Subject: [PATCH 09/10] chore: update ai-review findings [ai-review-bot][success] --- .gitea/ai-review/findings.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index fe51488..28f9d95 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1 +1,9 @@ -[] +[ + { + "level": "info", + "role": "Maya", + "location": "Dockerfile", + "suggestion": "考慮新增一個整合測試,該測試會建置 Docker 映像檔,並在容器內部執行 `entrypoint.sh`。這將有助於驗證所有必要的依賴項(如 bash, curl, jq)是否已正確安裝,並且腳本在目標容器環境中能正常執行,確保部署的穩定性。", + "is_new": true + } +] From d23910c487d55fe6a52b97c82c5120b7cbae4f39 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Sat, 16 May 2026 12:43:42 +0000 Subject: [PATCH 10/10] chore: refresh triage exclusions --- .gitea/ai-review/exclusions.json | 7 +++++++ .gitea/ai-review/findings.json | 10 +--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index cef6daf..abfe898 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -166,5 +166,12 @@ "location": "tests/entrypoint_pagination.sh:137", "suggestion": "腳本執行時將標準輸出重定向到 `/dev/null`。為了更全面地驗證錯誤處理邏輯,應捕獲標準錯誤輸出 (stderr) 並檢查 `fail` 函數輸出的錯誤訊息是否符合預期,同時驗證腳本在錯誤情況下是否以非零狀態碼退出。", "reason": "false positive" + }, + { + "level": "info", + "role": "Maya", + "location": "Dockerfile", + "suggestion": "考慮新增一個整合測試,該測試會建置 Docker 映像檔,並在容器內部執行 `entrypoint.sh`。這將有助於驗證所有必要的依賴項(如 bash, curl, jq)是否已正確安裝,並且腳本在目標容器環境中能正常執行,確保部署的穩定性。", + "reason": "false positive" } ] diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json index 28f9d95..fe51488 100644 --- a/.gitea/ai-review/findings.json +++ b/.gitea/ai-review/findings.json @@ -1,9 +1 @@ -[ - { - "level": "info", - "role": "Maya", - "location": "Dockerfile", - "suggestion": "考慮新增一個整合測試,該測試會建置 Docker 映像檔,並在容器內部執行 `entrypoint.sh`。這將有助於驗證所有必要的依賴項(如 bash, curl, jq)是否已正確安裝,並且腳本在目標容器環境中能正常執行,確保部署的穩定性。", - "is_new": true - } -] +[]