Files
herdr-issue/skills/herdr-issue/scripts/lib.sh
T

97 lines
3.7 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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']))"
}