97 lines
3.7 KiB
Bash
Executable File
97 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# herdr-issue 共用函式。每個腳本都 source 這個檔。
|
||
set -euo pipefail
|
||
|
||
LABEL="in-progress"
|
||
LABEL_COLOR="0e8a16"
|
||
LABEL_DESC="Herdr 的 agent 正在實作"
|
||
INTERVAL="${HERDR_ISSUE_INTERVAL:-300}"
|
||
MODEL="${HERDR_ISSUE_MODEL:-claude-opus-5-5}"
|
||
SCRIPTS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
||
die() { echo "herdr-issue: $*" >&2; exit 1; }
|
||
|
||
# 從 stdin 讀 JSON,執行 python 片段,d 是解析後的物件。
|
||
jq_py() { python3 -c "import sys,json
|
||
def find(o,k):
|
||
if isinstance(o,dict):
|
||
if k in o: return o[k]
|
||
for v in o.values():
|
||
r=find(v,k)
|
||
if r is not None: return r
|
||
elif isinstance(o,list):
|
||
for v in o:
|
||
r=find(v,k)
|
||
if r is not None: return r
|
||
return None
|
||
d=json.load(sys.stdin)
|
||
$1"; }
|
||
|
||
# 議題的完整 JSON(state、labels、body、title、closedAt)。
|
||
issue_json() { tea issues "$2" --repo "$1" --output json; }
|
||
|
||
issue_state() { issue_json "$1" "$2" | jq_py 'print(d["state"])'; }
|
||
|
||
issue_labels() { issue_json "$1" "$2" | jq_py 'print("\n".join(l["name"] for l in d.get("labels") or []))'; }
|
||
|
||
# 某個標籤底下還有幾個 open 議題。
|
||
open_count_with_label() {
|
||
tea issues list --repo "$1" --state open --labels "$2" --limit 500 --output json --fields index \
|
||
| jq_py 'print(len(d))'
|
||
}
|
||
|
||
ensure_label() {
|
||
if ! tea labels list --repo "$1" --output json | jq_py "sys.exit(0 if any(l['name']=='$LABEL' for l in d) else 1)"; then
|
||
tea labels create --repo "$1" --name "$LABEL" --color "$LABEL_COLOR" --description "$LABEL_DESC" >/dev/null
|
||
fi
|
||
}
|
||
|
||
# agent 狀態:idle|working|blocked|done|unknown|missing
|
||
agent_status() {
|
||
herdr agent get "issue-$1" 2>/dev/null | jq_py 'print(find(d,"agent_status") or "unknown")' 2>/dev/null || echo missing
|
||
}
|
||
|
||
# 標籤為 #N 的 tab ID,沒有就空字串。
|
||
tab_for_issue() {
|
||
herdr tab list --workspace "${HERDR_WORKSPACE_ID:?}" \
|
||
| jq_py "print(next((t['tab_id'] for t in find(d,'tabs') or [] if t.get('label')=='#$1'),''))"
|
||
}
|
||
|
||
# worktree 路徑:clone 同層的 <repo>-issue-N
|
||
worktree_path() { echo "$(dirname "$2")/$(basename "$2")-issue-$1"; }
|
||
|
||
notify() { herdr notification show "$1" --body "$2" --sound "${3:-request}" >/dev/null 2>&1 || true; }
|
||
|
||
# 目前目錄必須是 owner/repo 的 clone;印出 clone 根目錄。
|
||
clone_root_for() {
|
||
local root url
|
||
root="$(git rev-parse --show-toplevel 2>/dev/null)" || die "目前目錄不是 git repo,請在 $1 的 clone 裡執行"
|
||
url="$(git -C "$root" remote get-url origin 2>/dev/null || true)"
|
||
case "$url" in
|
||
*"$1"|*"$1.git"|*"$1/") echo "$root" ;;
|
||
*) die "目前 clone 的 origin 是 $url,不是 $1" ;;
|
||
esac
|
||
}
|
||
|
||
# head 分支為 issue-N 的最新 PR:印出「index state」或空字串。
|
||
pr_for_issue() {
|
||
tea pulls list --repo "$1" --state all --limit 500 --output json --fields index,state,mergeable,head \
|
||
| jq_py "m=[p for p in d if p.get('head')=='issue-$2']
|
||
print(' '.join([m[0]['index'],m[0]['state'],str(m[0].get('mergeable'))]) if m else '')"
|
||
}
|
||
|
||
# 擋住議題 N 的 open 依賴(Gitea 原生 issue dependencies),一行一個:同 repo 印 #M,跨 repo 印 owner/repo#M。
|
||
# tea api 遇到 HTTP 錯誤仍 exit 0,所以用回應是否為 JSON 陣列判斷成敗;失敗就 die。
|
||
open_blockers() {
|
||
tea api --repo "$1" "/repos/{owner}/{repo}/issues/$2/dependencies" | python3 -c "import sys,json
|
||
raw=sys.stdin.read()
|
||
try: d=json.loads(raw)
|
||
except Exception: d=None
|
||
if not isinstance(d,list):
|
||
sys.stderr.write('herdr-issue: 讀取 #$2 的依賴失敗:'+raw.strip()[:300]+'\n'); sys.exit(1)
|
||
for i in d:
|
||
if i.get('state')=='open':
|
||
r=(i.get('repository') or {}).get('full_name','')
|
||
print(('#' if r in ('','$1') else r+'#')+str(i['number']))"
|
||
}
|