From 3fe9b55840e4bd156b17c8db9120c8ab70cc2fb9 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 28 Aug 2026 09:30:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(gitea):=20=E6=94=AF=E6=8F=B4=E5=9B=9E?= =?UTF-8?q?=E8=A6=86=20PR=20=E7=95=99=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- README.md | 6 ++++-- plugin.json | 2 +- skills/repo-sync/SKILL.md | 4 ++-- tools/gitea.sh | 34 +++++++++++++++++++++++++++++----- 6 files changed, 38 insertions(+), 12 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index fc5f906..2d86e3d 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-gitea", - "version": "0.1.6", + "version": "0.1.7", "description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步", "skills": "./skills", "author": { diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index f54c018..1711aba 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-gitea", - "version": "0.1.6", + "version": "0.1.7", "description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步", "skills": "./skills" } diff --git a/README.md b/README.md index 4c9fc30..4122a19 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,9 @@ gitea.sh pr-get / # 印出 PR 的標題、base # 前三行固定 title、base、body 三個標記,第 4 行起是描述原文 gitea.sh pr-edit / <body-file> # 更新 PR 的標題與描述;描述從檔案讀,裝得下多行 -gitea.sh pr-comments <owner>/<repo> <pr-index> # 印出所有留言(issue 留言、審查評語、行內留言),依時間排序 +gitea.sh pr-comments <owner>/<repo> <pr-index> # 印出所有留言(issue 留言、審查評語、行內留言),第三欄帶 #id,依時間排序 +gitea.sh comment-reply <owner>/<repo> <pr-index> <issue|review|inline> <comment-id> <body-file> + # 回覆本輪處理過的 PR 留言;inline 走 review comment reply,其餘補一則 PR 留言 gitea.sh pr-depend <owner>/<repo> <pr-index> <dep-owner>/<dep-repo> <dep-index> # 把 PR 掛上前置 PR 依賴;依賴未關閉前 Gitea 會阻擋合併 gitea.sh repo-set <owner>/<repo> <description> [website] # 設定 repo 描述與網頁 @@ -109,7 +111,7 @@ Gitea wiki 頁讀寫的統一入口:依頁面類型(QUESTION / PLAN / ANALYZ ### `repo-sync` -存取庫批次同步:列出 owner → 使用者選擇 → 逐 repo(sub agent)呼叫 `tools/repo-sync.sh` clone 或更新;回報 `dirty {分支}` 的存取庫交給 `jsc-git:pr`,base 直接用腳本帶出來的那個分支。 +存取庫批次同步:列出 owner → 使用者選擇 → 逐 repo(sub agent)呼叫 `tools/repo-sync.sh` clone 或更新;回報 `dirty {分支}` 的存取庫交給 `jsc-git:pr`,base 直接用腳本帶出來的那個分支;PR 依 `jsc-meta/references/pr-report.md` 集中成表。 ### `html-export` diff --git a/plugin.json b/plugin.json index 39cc9c0..1186d86 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-gitea", - "version": "0.1.6", + "version": "0.1.7", "description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步", "skills": "./skills/" } diff --git a/skills/repo-sync/SKILL.md b/skills/repo-sync/SKILL.md index 18f66a1..9c253a4 100644 --- a/skills/repo-sync/SKILL.md +++ b/skills/repo-sync/SKILL.md @@ -13,5 +13,5 @@ description: Batch-sync all readable repos of a chosen Gitea owner into the work 4. Run `tools/gitea.sh repos {owner}` to list every readable `{repo}` under that owner. Done when the command has printed the full `{owner}/{repo}` list for the chosen owner. 5. Sync each `{repo}` one by one. This step **MUST run as a sub agent** (one sub agent per repo): 1. Run `tools/repo-sync.sh {owner}/{repo}`. The script owns the clone-versus-pull decision and the base-branch precedence, so run no `git clone`, `git checkout` or `git pull` by hand, and derive no branch name yourself. Route on its single line of output: `cloned` or `updated` → this repo is done; `dirty {branch}` → go to substep 2; `failed {reason}` → record that reason and stop this repo. Done when exactly one of those four outcomes is recorded for this repo. - 2. `dirty {branch}` → call `jsc-git:pr` with `{branch}` from that same output line as the base, passed through verbatim. It commits, branches, pushes and opens the PR itself, so add none of those steps. Done when `jsc-git:pr` returns the PR URL. -6. Report the sync result for every repo: cloned, updated, PR URL, or the failure reason. Done when every `{repo}` from step 4 carries one of those four results. + 2. `dirty {branch}` → call `jsc-git:pr` with `{branch}` from that same output line as the base, passed through verbatim. It commits, branches, pushes and opens the PR itself, so add none of those steps. Done when `jsc-git:pr` returns the PR URL and reports it with the table format in `jsc-meta/references/pr-report.md`. +6. Report the sync result for every repo: cloned, updated, PR table row, or the failure reason. Done when every `{repo}` from step 4 carries one of those four results, and all PR rows share one table when more than one PR exists. diff --git a/tools/gitea.sh b/tools/gitea.sh index 9b4470a..929dcf8 100755 --- a/tools/gitea.sh +++ b/tools/gitea.sh @@ -17,7 +17,9 @@ # gitea.sh pr-status <owner>/<repo> <pr-index> # 印出「{state} {merged} {mergeable}」 # gitea.sh pr-get <owner>/<repo> <pr-index> # 印出 PR 的標題、base 分支與描述,供比對用 # gitea.sh pr-edit <owner>/<repo> <pr-index> <title> <body-file> # 更新 PR 的標題與描述 -# gitea.sh pr-comments <owner>/<repo> <pr-index> # 印出所有留言(issue 留言、審查評語、行內留言),依時間排序 +# gitea.sh pr-comments <owner>/<repo> <pr-index> # 印出所有留言(issue 留言、審查評語、行內留言),第三欄帶 #id +# gitea.sh comment-reply <owner>/<repo> <pr-index> <issue|review|inline> <comment-id> <body-file> +# 回覆本輪處理過的 PR 留言;inline 走 review comment reply,其餘補一則 PR 留言 # gitea.sh pr-depend <owner>/<repo> <pr-index> <dep-owner>/<dep-repo> <dep-index> # 把 PR 掛上前置 PR 依賴;依賴未關閉前 Gitea 會阻擋合併 # gitea.sh repo-set <owner>/<repo> <description> [website] # 設定 repo 描述與網頁 @@ -258,15 +260,16 @@ print(json.dumps({"title":sys.argv[1],"body":open(sys.argv[2],encoding="utf-8"). rm -f "$tmp" echo "OK $or#$idx" ;; pr-comments) - # 印出 PR 的所有留言,每行「{時間}<TAB>{作者}<TAB>{類型}<TAB>{內容單行化}」。 + # 印出 PR 的所有留言,每行「{時間}<TAB>{作者}<TAB>{類型}#{id}<TAB>{內容單行化}」。 # 三個來源都要讀:issue 留言、review 本體的評語、review 內逐行的程式碼留言。 # 只讀 issue 留言會漏掉真正的審查意見,那正是需要修正的部分。 + # 類型欄保留在第三欄,僅追加 id;呼叫端用第一欄做 since 比對時不受影響。 or="${1:?owner/repo required}"; idx="${2:?pr index required}" { req GET "/repos/$or/issues/$idx/comments?limit=100" | python3 -c ' import json,sys for c in json.load(sys.stdin): body=" ".join((c.get("body") or "").split()) - if body: print("%s\t%s\t留言\t%s" % (c.get("created_at",""), (c.get("user") or {}).get("login","?"), body)) + if body: print("%s\t%s\t留言#%s\t%s" % (c.get("created_at",""), (c.get("user") or {}).get("login","?"), c.get("id","?"), body)) ' reviews=$(req GET "/repos/$or/pulls/$idx/reviews?limit=100") printf '%s' "$reviews" | python3 -c ' @@ -276,7 +279,7 @@ for r in json.load(sys.stdin): st=r.get("state","") # 沒有評語的審查照樣要印:APPROVED 代表可以合併,REQUEST_CHANGES 代表被要求修改, # 兩者都是呼叫端要據以決策的事實,過濾掉會看不見 PR 真正的狀態。 - print("%s\t%s\t審查(%s)\t%s" % (r.get("submitted_at",""), (r.get("user") or {}).get("login","?"), st, body or "(無評語)")) + print("%s\t%s\t審查#%s(%s)\t%s" % (r.get("submitted_at",""), (r.get("user") or {}).get("login","?"), r.get("id","?"), st, body or "(無評語)")) ' for rid in $(printf '%s' "$reviews" | python3 -c ' import json,sys @@ -289,10 +292,31 @@ try: d=json.load(sys.stdin) except Exception: raise SystemExit for c in d: body=" ".join((c.get("body") or "").split()) - if body: print("%s\t%s\t行內(%s:%s)\t%s" % (c.get("created_at",""), (c.get("user") or {}).get("login","?"), c.get("path",""), c.get("original_position") or c.get("position") or "", body)) + if body: print("%s\t%s\t行內#%s(%s:%s)\t%s" % (c.get("created_at",""), (c.get("user") or {}).get("login","?"), c.get("id","?"), c.get("path",""), c.get("original_position") or c.get("position") or "", body)) ' || true done } | sort ;; + comment-reply) + or="${1:?owner/repo required}"; idx="${2:?pr index required}" + kind="${3:?kind required}"; cid="${4:?comment id required}"; body_file="${5:?body file required}" + [ -f "$body_file" ] || { echo "找不到回覆檔: $body_file" >&2; exit 2; } + case "$kind" in issue|review|inline) ;; *) echo "kind must be issue, review, or inline" >&2; exit 2 ;; esac + tmp=$(mktemp) + python3 -c ' +import json,sys +print(json.dumps({"body":open(sys.argv[1],encoding="utf-8").read()})) +' "$body_file" > "$tmp" + case "$kind" in + inline) + path="/repos/$or/pulls/$idx/comments/$cid/replies" ;; + issue|review) + path="/repos/$or/issues/$idx/comments" ;; + esac + if ! out=$(req POST "$path" "$tmp"); then + rm -f "$tmp"; printf '%s\n' "$out" >&2; exit 4 + fi + rm -f "$tmp" + printf '%s' "$out" | json_field html_url ;; pr-depend) or="${1:?owner/repo required}"; idx="${2:?pr index required}" dep_or="${3:?dep owner/repo required}"; dep_idx="${4:?dep index required}" -- 2.53.0