Merge pull request 'feat(gitea): 新增 PR 盯場輪詢腳本與 pr-get、pr-edit 兩個子指令' (#23) from feat/sdlc-flow-rules/main into develop
This commit was merged in pull request #23.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-gitea",
|
"name": "jsc-gitea",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步",
|
"description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步",
|
||||||
"skills": "./skills",
|
"skills": "./skills",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-gitea",
|
"name": "jsc-gitea",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步",
|
"description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步",
|
||||||
"skills": "./skills"
|
"skills": "./skills"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
2. 技能位於 `skills/{name}/SKILL.md`;處理任務前先比對需求與各技能的 `description`,相符就載入並依其步驟執行。
|
2. 技能位於 `skills/{name}/SKILL.md`;處理任務前先比對需求與各技能的 `description`,相符就載入並依其步驟執行。
|
||||||
3. 技能準則的唯一來源:`plugins/meta` 存取庫的 `references/guidelines.md`。
|
3. 技能準則的唯一來源:`plugins/meta` 存取庫的 `references/guidelines.md`。
|
||||||
4. 所有 hook 只放在 `jsc-hooks`;gitea 操作一律經由 `jsc-gitea` 的 `tools/gitea.sh`;問使用者一律依 `jsc-ask:ask` 的決策樹規則。
|
4. 所有 hook 只放在 `jsc-hooks`;gitea 操作一律經由 `jsc-gitea` 的 `tools/gitea.sh`;問使用者一律依 `jsc-ask:ask` 的決策樹規則。
|
||||||
5. 主 agent 不需要處理細節的流程,一律建立 sub agent 處理。
|
5. 盯 PR 等合併用 `tools/pr-watch.sh`,不要自己寫輪詢迴圈。它回 10 就代表有新留言,把留言接回決策樹處理完,再重新盯一次。
|
||||||
|
6. 主 agent 不需要處理細節的流程,一律建立 sub agent 處理。
|
||||||
|
|
||||||
## 呼叫慣例
|
## 呼叫慣例
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ gitea.sh wiki-put <owner>/<repo> <page> <file> # 自動判斷新建或更新
|
|||||||
gitea.sh wiki-url <owner>/<repo> <page> # 印出 wiki 頁絕對網址(取自 API 的 html_url);跨存取庫連結用
|
gitea.sh wiki-url <owner>/<repo> <page> # 印出 wiki 頁絕對網址(取自 API 的 html_url);跨存取庫連結用
|
||||||
gitea.sh pr-create <owner>/<repo> <head> <base> <title> <body-file>
|
gitea.sh pr-create <owner>/<repo> <head> <base> <title> <body-file>
|
||||||
gitea.sh pr-status <owner>/<repo> <pr-index> # 印出 {state} {merged} {mergeable}
|
gitea.sh pr-status <owner>/<repo> <pr-index> # 印出 {state} {merged} {mergeable}
|
||||||
|
gitea.sh pr-get <owner>/<repo> <pr-index> # 印出 PR 的標題、base 分支與描述,供呼叫端比對有沒有差
|
||||||
|
# 前三行固定 title、base、body 三個標記,第 4 行起是描述原文
|
||||||
|
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 留言、審查評語、行內留言),依時間排序
|
||||||
gitea.sh pr-depend <owner>/<repo> <pr-index> <dep-owner>/<dep-repo> <dep-index>
|
gitea.sh pr-depend <owner>/<repo> <pr-index> <dep-owner>/<dep-repo> <dep-index>
|
||||||
# 把 PR 掛上前置 PR 依賴;依賴未關閉前 Gitea 會阻擋合併
|
# 把 PR 掛上前置 PR 依賴;依賴未關閉前 Gitea 會阻擋合併
|
||||||
@@ -45,8 +49,14 @@ hash-id <text> # 與 gitea.sh hash-id 相同
|
|||||||
repo-sync.sh <owner>/<repo> [target-dir] # 同步單一存取庫;印出 cloned、updated、dirty {分支} 或 failed {原因}
|
repo-sync.sh <owner>/<repo> [target-dir] # 同步單一存取庫;印出 cloned、updated、dirty {分支} 或 failed {原因}
|
||||||
# 基準分支的優先序只在這支腳本裡;dirty 會把解析好的分支帶出來當 PR 的 base
|
# 基準分支的優先序只在這支腳本裡;dirty 會把解析好的分支帶出來當 PR 的 base
|
||||||
check-wiki-rules.sh # 驗證 wiki repo 解析與 hash fallback 規則
|
check-wiki-rules.sh # 驗證 wiki repo 解析與 hash fallback 規則
|
||||||
|
pr-watch.sh <owner>/<repo> <pr-index> [state-file]
|
||||||
|
# 盯著一支 PR,直到它合併或關閉,不自動逾時退場
|
||||||
|
# 退出碼 0 = 已合併或關閉、10 = 有新留言要接手、2 = 參數錯誤、3 = 查不到該 PR
|
||||||
|
# 已回報過的留言記在狀態檔,預設 $JSC_HOME/pr-watch/{owner}-{repo}-{index}.seen
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`pr-watch.sh` 的退出碼 10 是設計重點:腳本只負責偵測,決策交回呼叫端。收到 10 就把印出來的留言丟進決策樹,處理完再重新盯一次。
|
||||||
|
|
||||||
議題與 HTML 產出:
|
議題與 HTML 產出:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -123,6 +133,8 @@ Gitea wiki 頁讀寫的統一入口:依頁面類型(QUESTION / PLAN / ANALYZ
|
|||||||
| `GITEA_TOKEN` | Gitea API token;缺少或遇 401/403 時自動退回 tea CLI 登入 token | 詢問使用者 |
|
| `GITEA_TOKEN` | Gitea API token;缺少或遇 401/403 時自動退回 tea CLI 登入 token | 詢問使用者 |
|
||||||
| `JSC_WIKI_REPO_{TYPE}` | 各類型 wiki 頁的 `{owner}/{repo}`;TYPE = QUESTION / PLAN / ANALYZE / DELIVER / MAINTAIN / REPO / LOG / LEARN / ERROR | 退回 `JSC_WIKI_REPO`,不做跨類型代用 |
|
| `JSC_WIKI_REPO_{TYPE}` | 各類型 wiki 頁的 `{owner}/{repo}`;TYPE = QUESTION / PLAN / ANALYZE / DELIVER / MAINTAIN / REPO / LOG / LEARN / ERROR | 退回 `JSC_WIKI_REPO`,不做跨類型代用 |
|
||||||
| `JSC_WIKI_REPO` | 共用預設的 wiki `{owner}/{repo}` | 詢問使用者 |
|
| `JSC_WIKI_REPO` | 共用預設的 wiki `{owner}/{repo}` | 詢問使用者 |
|
||||||
|
| `JSC_HOME` | `pr-watch.sh` 狀態檔的根目錄 | 預設 `~/.jsc` |
|
||||||
|
| `JSC_PR_WATCH_INTERVAL` | `pr-watch.sh` 的輪詢間隔秒數(正整數) | 預設 60 |
|
||||||
|
|
||||||
## Hash 規則
|
## Hash 規則
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-gitea",
|
"name": "jsc-gitea",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步",
|
"description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步",
|
||||||
"skills": "./skills/"
|
"skills": "./skills/"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
# gitea.sh wiki-url <owner>/<repo> <page> # 印出 wiki 頁絕對網址(取自 API 的 html_url);跨存取庫連結用
|
# gitea.sh wiki-url <owner>/<repo> <page> # 印出 wiki 頁絕對網址(取自 API 的 html_url);跨存取庫連結用
|
||||||
# gitea.sh pr-create <owner>/<repo> <head> <base> <title> <body-file> # 建立 PR,印出 PR URL
|
# gitea.sh pr-create <owner>/<repo> <head> <base> <title> <body-file> # 建立 PR,印出 PR URL
|
||||||
# gitea.sh pr-status <owner>/<repo> <pr-index> # 印出「{state} {merged} {mergeable}」
|
# 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 留言、審查評語、行內留言),依時間排序
|
||||||
# gitea.sh pr-depend <owner>/<repo> <pr-index> <dep-owner>/<dep-repo> <dep-index>
|
# gitea.sh pr-depend <owner>/<repo> <pr-index> <dep-owner>/<dep-repo> <dep-index>
|
||||||
# 把 PR 掛上前置 PR 依賴;依賴未關閉前 Gitea 會阻擋合併
|
# 把 PR 掛上前置 PR 依賴;依賴未關閉前 Gitea 會阻擋合併
|
||||||
@@ -219,6 +221,42 @@ import json,sys
|
|||||||
p=json.load(sys.stdin)
|
p=json.load(sys.stdin)
|
||||||
print("%s %s %s" % (p.get("state","?"), str(p.get("merged")).lower(), str(p.get("mergeable")).lower()))
|
print("%s %s %s" % (p.get("state","?"), str(p.get("merged")).lower(), str(p.get("mergeable")).lower()))
|
||||||
' ;;
|
' ;;
|
||||||
|
pr-get)
|
||||||
|
# 印出 PR 的標題、base 分支與描述,供呼叫端比對本機資料是否已經跟 PR 不一致。
|
||||||
|
# 格式固定三段,描述放最後一段,因為只有它會多行:
|
||||||
|
# 第 1 行 title<TAB>{標題}
|
||||||
|
# 第 2 行 base<TAB>{base 分支}
|
||||||
|
# 第 3 行 body (單獨一個字,當描述的起始標記)
|
||||||
|
# 第 4 行起 描述原文,一直到檔尾
|
||||||
|
# 呼叫端取標題用 head -n1 | cut -f2-,取 base 用 sed -n 2p | cut -f2-,
|
||||||
|
# 取描述用 tail -n +4,全程不必解析 JSON。
|
||||||
|
or="${1:?owner/repo required}"; idx="${2:?pr index required}"
|
||||||
|
if ! out=$(req GET "/repos/$or/pulls/$idx"); then
|
||||||
|
printf '%s\n' "$out" >&2; exit 4
|
||||||
|
fi
|
||||||
|
printf '%s' "$out" | python3 -c '
|
||||||
|
import json,sys
|
||||||
|
p=json.load(sys.stdin)
|
||||||
|
print("title\t%s" % (p.get("title") or ""))
|
||||||
|
print("base\t%s" % ((p.get("base") or {}).get("ref") or ""))
|
||||||
|
print("body")
|
||||||
|
sys.stdout.write(p.get("body") or "")
|
||||||
|
' ;;
|
||||||
|
pr-edit)
|
||||||
|
# 更新 PR 的標題與描述。描述從檔案讀,才裝得下多行內容與全形標點。
|
||||||
|
or="${1:?owner/repo required}"; idx="${2:?pr index required}"
|
||||||
|
title="${3:?title required}"; body_file="${4:?body file required}"
|
||||||
|
[ -f "$body_file" ] || { echo "找不到描述檔: $body_file" >&2; exit 2; }
|
||||||
|
tmp=$(mktemp)
|
||||||
|
python3 -c '
|
||||||
|
import json,sys
|
||||||
|
print(json.dumps({"title":sys.argv[1],"body":open(sys.argv[2],encoding="utf-8").read()}))
|
||||||
|
' "$title" "$body_file" > "$tmp"
|
||||||
|
if ! out=$(req PATCH "/repos/$or/pulls/$idx" "$tmp"); then
|
||||||
|
rm -f "$tmp"; printf '%s\n' "$out" >&2; exit 4
|
||||||
|
fi
|
||||||
|
rm -f "$tmp"
|
||||||
|
echo "OK $or#$idx" ;;
|
||||||
pr-comments)
|
pr-comments)
|
||||||
# 印出 PR 的所有留言,每行「{時間}<TAB>{作者}<TAB>{類型}<TAB>{內容單行化}」。
|
# 印出 PR 的所有留言,每行「{時間}<TAB>{作者}<TAB>{類型}<TAB>{內容單行化}」。
|
||||||
# 三個來源都要讀:issue 留言、review 本體的評語、review 內逐行的程式碼留言。
|
# 三個來源都要讀:issue 留言、review 本體的評語、review 內逐行的程式碼留言。
|
||||||
|
|||||||
Executable
+128
@@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# pr-watch.sh — 盯著一支 PR,直到它合併或關閉。
|
||||||
|
# 用法: pr-watch.sh <owner>/<repo> <pr-index> [state-file]
|
||||||
|
# 規則: 資料一律經同目錄的 gitea.sh(pr-status、pr-comments),不自行拼 API。
|
||||||
|
# 輪詢間隔預設 60 秒,JSC_PR_WATCH_INTERVAL 可覆寫,單位是秒的正整數。
|
||||||
|
# 不自動逾時退場,盯到 PR 合併或關閉為止。
|
||||||
|
# 已回報過的留言不重複回報:最後處理過的留言時間戳寫進狀態檔,
|
||||||
|
# 預設 $JSC_HOME/pr-watch/{owner}-{repo}-{index}.seen,JSC_HOME 未設定時用 ~/.jsc。
|
||||||
|
# 狀態檔還不存在時,把既有留言整批當成新留言回報一次。寧可重複回報,
|
||||||
|
# 也不要漏掉開始盯之前就留下的審查意見。
|
||||||
|
# 每輪先看 PR 狀態,再看留言。PR 收尾那一輪若還有沒回報的留言,
|
||||||
|
# 照樣印出來再退出,退出碼仍是 0:留言不會因為 PR 剛好合併就消失。
|
||||||
|
# 輸出: 全部繁中,留言行維持 gitea.sh pr-comments 的原格式
|
||||||
|
# 「{時間}<TAB>{作者}<TAB>{類型}<TAB>{內容}」,呼叫端可直接用 cut -f 取欄位。
|
||||||
|
# 退出碼: 0 = PR 已合併或關閉,印出最終狀態;10 = 有新留言待呼叫端跑決策樹;
|
||||||
|
# 2 = 參數錯誤或第一輪就連不上 Gitea;3 = 查不到該 PR。
|
||||||
|
# 護欄: gitea.sh pr-status 對不存在的 PR 會印「? none none」並且 exit 0。
|
||||||
|
# 所以 state 只認 open 與 closed、merged 只認 true 與 false,對不上就回 3。
|
||||||
|
# 少了這道白名單,PR 編號打錯會變成永遠輪詢一支不存在的 PR。
|
||||||
|
# 第一輪查詢失敗算環境沒接好,直接回 2;之後才失敗算連線抖動,警告一句繼續輪詢。
|
||||||
|
set -u
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
GITEA="$script_dir/gitea.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "用法: pr-watch.sh <owner>/<repo> <pr-index> [state-file]" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$#" -ge 2 ] && [ "$#" -le 3 ] || usage
|
||||||
|
REPO="$1"; INDEX="$2"; STATE="${3:-}"
|
||||||
|
|
||||||
|
case "$REPO" in
|
||||||
|
*/*) ;;
|
||||||
|
*) echo "錯誤: 第一個參數要寫成 {owner}/{repo},收到「$REPO」。" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
case "$INDEX" in
|
||||||
|
''|*[!0-9]*) echo "錯誤: PR 編號要是數字,收到「$INDEX」。" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
INTERVAL="${JSC_PR_WATCH_INTERVAL:-60}"
|
||||||
|
case "$INTERVAL" in
|
||||||
|
''|*[!0-9]*) echo "錯誤: JSC_PR_WATCH_INTERVAL 要是正整數秒,收到「$INTERVAL」。" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
[ "$INTERVAL" -gt 0 ] || { echo "錯誤: JSC_PR_WATCH_INTERVAL 要大於 0。" >&2; exit 2; }
|
||||||
|
|
||||||
|
if [ -z "$STATE" ]; then
|
||||||
|
jsc_home="${JSC_HOME:-$HOME/.jsc}"
|
||||||
|
owner=${REPO%%/*}; name=${REPO#*/}
|
||||||
|
STATE="$jsc_home/pr-watch/$owner-$name-$INDEX.seen"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMP_NEW=$(mktemp) || { echo "錯誤: 無法建立暫存檔。" >&2; exit 2; }
|
||||||
|
trap 'rm -f "$TMP_NEW"' EXIT
|
||||||
|
trap 'rm -f "$TMP_NEW"; exit 130' INT TERM
|
||||||
|
|
||||||
|
SEEN=""
|
||||||
|
[ -f "$STATE" ] && SEEN=$(head -n1 "$STATE" 2>/dev/null)
|
||||||
|
|
||||||
|
save_seen() { # 時間戳 -> 寫回狀態檔
|
||||||
|
ts="$1"
|
||||||
|
[ -n "$ts" ] || return 0
|
||||||
|
dir=$(dirname -- "$STATE")
|
||||||
|
mkdir -p "$dir" 2>/dev/null || { echo "警告: 建不出狀態檔目錄 $dir,這批留言下次會重複回報。" >&2; return 0; }
|
||||||
|
printf '%s\n' "$ts" > "$STATE" 2>/dev/null \
|
||||||
|
|| echo "警告: 寫不進狀態檔 $STATE,這批留言下次會重複回報。" >&2
|
||||||
|
SEEN="$ts"
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_new() { # 新留言寫進 $TMP_NEW;有東西才回 0
|
||||||
|
# 時間戳一律由同一台 Gitea 產生,格式與時區都一致,所以字串比大小就夠用;
|
||||||
|
# pr-comments 自己也是照這個字串排序的。
|
||||||
|
"$GITEA" pr-comments "$REPO" "$INDEX" 2>/dev/null \
|
||||||
|
| awk -F '\t' -v seen="$SEEN" 'NF >= 4 && ($1 "") > (seen "")' > "$TMP_NEW"
|
||||||
|
[ -s "$TMP_NEW" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
flush_new() { # 印出新留言並記住最後一筆的時間戳
|
||||||
|
cat "$TMP_NEW"
|
||||||
|
save_seen "$(awk -F '\t' 'END { print $1 }' "$TMP_NEW")"
|
||||||
|
}
|
||||||
|
|
||||||
|
first_round=1
|
||||||
|
while :; do
|
||||||
|
if ! status=$("$GITEA" pr-status "$REPO" "$INDEX" 2>/dev/null); then
|
||||||
|
if [ "$first_round" -eq 1 ]; then
|
||||||
|
echo "錯誤: 連不上 Gitea 或 gitea.sh 執行失敗。請確認 GITEA_HOST 與 GITEA_TOKEN,再重新盯。" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo "警告: 這一輪查不到 PR 狀態,$INTERVAL 秒後再試。" >&2
|
||||||
|
sleep "$INTERVAL"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
first_round=0
|
||||||
|
|
||||||
|
state=$(printf '%s\n' "$status" | awk '{ print $1 }')
|
||||||
|
merged=$(printf '%s\n' "$status" | awk '{ print $2 }')
|
||||||
|
case "$state" in
|
||||||
|
open|closed) ;;
|
||||||
|
*) echo "錯誤: 查不到 PR $REPO#$INDEX。請確認存取庫與 PR 編號。" >&2; exit 3 ;;
|
||||||
|
esac
|
||||||
|
case "$merged" in
|
||||||
|
true|false) ;;
|
||||||
|
*) echo "錯誤: 查不到 PR $REPO#$INDEX。請確認存取庫與 PR 編號。" >&2; exit 3 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$state" = closed ]; then
|
||||||
|
if [ "$merged" = true ]; then
|
||||||
|
printf '最終狀態: PR %s#%s 已合併。\n' "$REPO" "$INDEX"
|
||||||
|
else
|
||||||
|
printf '最終狀態: PR %s#%s 已關閉,沒有合併。\n' "$REPO" "$INDEX"
|
||||||
|
fi
|
||||||
|
if collect_new; then
|
||||||
|
echo "收尾前還有沒回報的留言:"
|
||||||
|
flush_new
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if collect_new; then
|
||||||
|
printf '新留言 %s 則(%s#%s),請接手跑決策樹:\n' "$(wc -l < "$TMP_NEW" | tr -d ' ')" "$REPO" "$INDEX"
|
||||||
|
flush_new
|
||||||
|
exit 10
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep "$INTERVAL"
|
||||||
|
done
|
||||||
Reference in New Issue
Block a user