refactor: jsc-herdr 技能改名,herdr 改為 cmd、gitea-issue 改為 gitea

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
root
2026-10-14 04:50:49 +00:00
co-authored by Claude Sonnet 5.5
parent 5701b8b8e5
commit 346cbbad2c
16 changed files with 18 additions and 18 deletions
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env bash
# 用法:start.sh <owner/repo> <N>
# 在目前 clone 旁建 worktree、開 Herdr tab、啟動 claude、送提示詞並確認 agent 有反應、掛 in-progress 標籤。
# 送出提示詞後 15 秒內沒觀察到 working 或 blocked 就視為失敗並回滾(關 tab、移除 worktree、刪本次新建的分支)。
# 最後一行印出 JSON:{"tab":..,"pane":..,"path":..,"resumed":..,"state":"working"|"blocked"}
source "$(dirname "$0")/lib.sh"
REPO="${1:?}"; N="${2:?用法:start.sh <owner/repo> <N>}"
LINK="https://gitea.jsc.idv.tw/$REPO/issues/$N"
test "${HERDR_ENV:-}" = 1 || die "不在 Herdr 裡(HERDR_ENV≠1)"
CLONE="$(clone_root_for "$REPO")"
BRANCH="issue-$N"
WT="$(worktree_path "$N" "$CLONE")"
if [ "$(agent_status "$N")" != missing ]; then
die "issue-$N 的 agent 還活著(tab $(tab_for_issue "$N")),不重複啟動"
fi
[ "$(issue_state "$REPO" "$N")" = open ] || die "#$N 不是 open"
BLOCKERS="$(open_blockers "$REPO" "$N")" || exit 1
[ -z "$BLOCKERS" ] || die "#$N 被還沒關閉的議題擋住:$(echo "$BLOCKERS" | tr '\n' ' ')(Gitea 依賴設定)。等它們關閉,或到 Gitea 拿掉依賴再啟動"
git -C "$CLONE" fetch --quiet origin
RESUMED=false
if git -C "$CLONE" show-ref --verify --quiet "refs/heads/$BRANCH"; then
RESUMED=true
if ! git -C "$CLONE" worktree list --porcelain | grep -qx "worktree $WT"; then
git -C "$CLONE" worktree add --quiet "$WT" "$BRANCH"
fi
else
git -C "$CLONE" worktree add --quiet -b "$BRANCH" "$WT" origin/main
fi
TAB=""
# 回滾到跟沒跑過一樣:關 tab、移除 worktree;分支只在本次新建時刪除。
rollback() {
[ -n "$TAB" ] && herdr tab close "$TAB" >/dev/null 2>&1 || true
if git -C "$CLONE" worktree list --porcelain | grep -qx "worktree $WT"; then
git -C "$CLONE" worktree remove --force "$WT" >/dev/null 2>&1 || true
fi
$RESUMED || git -C "$CLONE" branch -D "$BRANCH" >/dev/null 2>&1 || true
}
TAB_JSON="$(herdr tab create --workspace "${HERDR_WORKSPACE_ID:?}" --cwd "$WT" --label "#$N" --no-focus)"
TAB="$(jq_py 'print(find(d,"tab")["tab_id"])' <<<"$TAB_JSON")"
PANE="$(jq_py 'print(find(d,"root_pane")["pane_id"])' <<<"$TAB_JSON")"
START_ERR="$(mktemp)"
for _ in 1 2 3 4 5 6; do
if herdr agent start "issue-$N" --kind claude --pane "$PANE" --timeout 90000 -- --model "$MODEL" >/dev/null 2>"$START_ERR"; then
rm -f "$START_ERR"; break
fi
sleep 2
done
if [ -f "$START_ERR" ]; then
cat "$START_ERR" >&2; rm -f "$START_ERR"
rollback
die "agent 啟動失敗,已回滾"
fi
# git 身分:與 tea 預設使用者一致(只設在這個 worktree,不動 clone 的設定)
GIT_USER="$(command tea login list --output json | jq_py "print(next((l['user'] for l in d if l['name']=='$HERDR_ISSUE_LOGIN'),''))")"
GIT_EMAIL="$(tea api "/user" | jq_py 'print(d.get("email") or "")' 2>/dev/null || true)"
git -C "$CLONE" config extensions.worktreeConfig true
# agent 在 worktree 裡自己跑的 git(push、fetch)也要有帳密:把 helper 設到 clone 的本地設定(worktree 共用),只對這個主機生效
GIT_HOST_URL="$(command tea login list --output json | jq_py "print(next((l['url'] for l in d if l['name']=='$HERDR_ISSUE_LOGIN'),''))")"
if [ -n "$GIT_HOST_URL" ]; then
git -C "$CLONE" config --local --replace-all "credential.$GIT_HOST_URL.helper" "$SCRIPTS/git-credential.sh"
fi
git -C "$WT" config --worktree http.sslVerify false
git -C "$WT" config --worktree user.name "$GIT_USER"
[ -z "$GIT_EMAIL" ] || git -C "$WT" config --worktree user.email "$GIT_EMAIL"
if $RESUMED; then RESUME_LINE="分支已有先前進度,先看 git log 和議題核取方塊再接續。"; else RESUME_LINE=""; fi
PROMPT="$(sed -e "s|{LINK}|$LINK|g" -e "s|{REPO}|$REPO|g" -e "s|{N}|$N|g" -e "s|{S}|$SCRIPTS|g" -e "s|{LOGIN}|$HERDR_ISSUE_LOGIN|g" -e "s|{RESUME}|$RESUME_LINE|" "$SCRIPTS/prompt.txt")"
# 送出後必須觀察到 agent 進入 working 或 blocked;否則 Herdr 回非 0(agent_prompt_stalled、timeout、agent_blocked 等)。
PROMPT_ERR="$(mktemp)"
if PROMPT_OUT="$(herdr agent prompt "issue-$N" "$PROMPT" --wait --until working --until blocked --timeout 15000 2>"$PROMPT_ERR")"; then
rm -f "$PROMPT_ERR"
else
echo "gitea: 送出提示詞後 agent 沒有反應,已回滾" >&2
cat "$PROMPT_ERR" >&2; rm -f "$PROMPT_ERR"
rollback
exit 1
fi
STATE="$(jq_py 'print(find(d,"agent_status") or "")' <<<"$PROMPT_OUT" 2>/dev/null || true)"
case "$STATE" in working|blocked) ;; *) STATE="$(agent_status "$N")" ;; esac
ensure_label "$REPO"
tea issues edit "$N" --repo "$REPO" --remove-labels "$LABEL" >/dev/null 2>&1 || true
tea issues edit "$N" --repo "$REPO" --add-labels "$LABEL" >/dev/null
printf '{"tab":"%s","pane":"%s","path":"%s","resumed":%s,"state":"%s"}\n' "$TAB" "$PANE" "$WT" "$RESUMED" "$STATE"