Files
ISEP/scripts/gitea-arm-request.sh
T
Leo 4d2036af72 fix: 要 leo 動手的留言第一行掛人閘記號 👤(inkstone/ISEP#123)
leo 一天只看兩眼,要在一長串留言裡一眼認出「這則在等我動手」。
機器產生的 ARM 請求第一行是 `🔐 ARM 請求`(機器術語)沒有 👤,
而他手寫的交棒都以 `👤 Leo:` 開頭——規範在人身上、不在機器上。

- scripts/gitea-arm-request.sh:第一行改成
  `👤 Leo:回這則、貼下面那組碼,就放行這一次 —— <任務>`(🔐/nonce 照舊,
  nonce 仍單獨成行,gitea-arm-check 的比對不受影響)。加離線印內文的
  ISEP_ARM_PRINT_BODY=1 供測試。
- scripts/ticket handback --to Leo:交棒留言第一行改掛 `👤 Leo:<下一步>`,
  🏃 那一塊照舊留著(baton-handback-guard 靠留言有沒有 🏃 判斷交棒了沒,
  拔掉會漏擋);交回總管 claude-code 維持第一行 🏃、不掛 👤。
  body 抽成純函式 handback_comment_lines 供離線驗。
- 掃過的其他 comment-poster 皆非「要 leo 動手」的閘、未改(decide/subtask/
  handoff/triage/compose/claim/pr-verdict/milestone-account/gitea-arm-check/
  isep-notify 退路留言,理由記在 docs/hooks-inventory.md #123 note)。

測試:gitea-arm-check.test.sh 16→19、test-ticket-handoff-writeback.sh 49→54;
comment-carries-task-guard(29/29)、baton-handback-guard(24/24)無回歸。
版本待總管定。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TGxitYq49FzYC7EFkbhzF5
2026-09-18 18:20:45 +00:00

160 lines
7.9 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.
#!/bin/bash
# gitea-arm-request.sh — 開一個「等 leo 在 Gitea 上回覆」的請求
#
# 用法:scripts/gitea-arm-request.sh <票號> "任務描述" [有效分鐘,預設30,上限60]
#
# 這支**machine 自己就能跑**(不像 scripts/github-arm.sh 要求終端機互動)——
# 因為「請求」本身不是安全邊界,**leo 用 Leo 帳號回覆才是**。
# 這支只負責:生一組一次性代碼、貼上呼叫端指定的那張 Gitea 票、把代碼與人話訊息印出來
# (讓呼叫者拿去用 notify_leoTelegram 轉告 leo;本支不碰 Telegram)。
#
# 🪦 2026-08-16 起票號不再寫死(原本固定貼 inkstone/InkStoneCo#34「頻道票」)——
# leo:「我不要把所有的票都放在一個 issues,不然就難 track 歷史記錄」。
# **解哪張票的保險,就把請求貼在那張票上**:要出貨 X 就傳 X 那張票的號碼,
# 不要再統一貼 #34(#34 保留 open 當歷史,見 issues/34#issuecomment-2804)。
#
# 之後由 scripts/gitea-arm-check.sh 核對 leo 有沒有在同一張票回覆同一組代碼。
set -euo pipefail
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
# shellcheck source=lib/gitea-arm-common.sh
. "$SCRIPT_DIR/lib/gitea-arm-common.sh"
ISSUE="${1:-}"
MISSION="${2:-}"
MINUTES="${3:-30}"
if [ -z "$ISSUE" ] || [ -z "$MISSION" ]; then
echo "用法:scripts/gitea-arm-request.sh <票號> \"任務描述\" [分鐘,預設30,上限60]" >&2
echo "例:scripts/gitea-arm-request.sh 41 \"出 prodxxx\"" >&2
exit 1
fi
# 票號可寫 `N`(預設 InkStoneCo)或 `repo#N`(同 org 的別的 repo,例:arcrun-rag#115
case "$ISSUE" in
*"#"*)
_R="${ISSUE%%#*}"; ISSUE="${ISSUE##*#}"
gitea_arm_set_repo "$_R" || exit 1;;
esac
if ! gitea_arm_valid_issue "$ISSUE"; then
echo "❌ 票號要是純數字,或 repo#N(收到:$ISSUE)——解哪張票的保險就填那張票" >&2
exit 1
fi
case "$MINUTES" in
''|*[!0-9]*)
echo "❌ 第三個參數要純數字分鐘(收到:$MINUTES" >&2
exit 1;;
esac
[ "$MINUTES" -le 60 ] || MINUTES=60
[ "$MINUTES" -ge 1 ] || MINUTES=1
NONCE="ARM-$(python3 -c 'import secrets; print(secrets.token_hex(4))')"
NOW=$(date +%s)
EXPIRES=$((NOW + MINUTES * 60))
EXPIRES_HUMAN=$(date -r "$EXPIRES" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date -d "@$EXPIRES" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$EXPIRES")
# ── 留言內文 ───────────────────────────────────────────────────────────
# 🔴 inkstone/ISEP#123**第一行就是人閘記號 `👤` +「要 leo 做什麼」**——
# leo 一天只看兩眼,他要在一長串留言裡一眼認出「這則是在等我動手的」。
# 舊版第一行是 `🔐 ARM 請求`(機器術語),沒有 `👤` ⇒ 他掃過去認不出來,
# 而他手寫的交棒都以 `👤 Leo:` 開頭。這正是「規範在人身上、不在機器上」的形狀。
# (`👤` 是留言記號,跟「不准用 `👤 裁決題:` 當**票名**前綴」是兩件事,見 scripts/ticket 的標題閘。)
COMMENT_BODY=$(MISSION="$MISSION" NONCE="$NONCE" EXPIRES_HUMAN="$EXPIRES_HUMAN" MINUTES="$MINUTES" python3 -c '
import os
mission = os.environ["MISSION"]
nonce = os.environ["NONCE"]
expires_human = os.environ["EXPIRES_HUMAN"]
minutes = os.environ["MINUTES"]
print(f"""👤 Leo:回這則、貼下面那組碼,就放行這一次 —— {mission}
🔐 ARM 請求
有效時限:{minutes} 分鐘內({expires_human} 前)
📱 貼上下面這串就解鎖這一次(用完就失效,別的請求不能借用):
{nonce}
""")
')
# ── 印出內文就退場(測試用,不打網路、不讀 token、不寫本地狀態)──────────
# 離線驗「第一行有 👤」不必碰 Gitea,也不必真的等 leoinkstone/ISEP#123 驗收 3)。
if [ "${ISEP_ARM_PRINT_BODY:-}" = "1" ]; then
printf '%s\n' "$COMMENT_BODY"
exit 0
fi
TOKEN=$(gitea_arm_token) || { echo "❌ 讀不到 GITEA_TOKEN_CLAUDE_CODE(頂層 .env" >&2; exit 1; }
STATE_DIR=$(gitea_arm_state_dir)
PENDING_FILE="$STATE_DIR/pending/${NONCE}.json"
# ── 貼留言到呼叫端指定的那張票 ─────────────────────────────────────────
REQUEST_JSON=$(jq -n --arg body "$COMMENT_BODY" '{body: $body}')
RESP=$(curl -s -w '\n%{http_code}' -X POST \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "$REQUEST_JSON" \
"$GITEA_ARM_API/repos/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/issues/$ISSUE/comments")
HTTP_CODE=$(printf '%s' "$RESP" | tail -1)
RESP_BODY=$(printf '%s' "$RESP" | sed '$d')
if [ "$HTTP_CODE" != "201" ]; then
echo "❌ 貼留言失敗(HTTP $HTTP_CODE),沒有建立請求——票號 #$ISSUE 存在嗎?" >&2
echo "$RESP_BODY" >&2
exit 1
fi
COMMENT_ID=$(printf '%s' "$RESP_BODY" | jq -r '.id // empty')
CREATED_AT=$(printf '%s' "$RESP_BODY" | jq -r '.created_at // empty')
if [ -z "$COMMENT_ID" ] || [ -z "$CREATED_AT" ]; then
echo "❌ Gitea 回應解不出留言 id/時間,視為失敗(fail-closed,沒寫本地狀態)" >&2
exit 1
fi
jq -n \
--arg nonce "$NONCE" \
--arg mission "$MISSION" \
--arg issue "$ISSUE" \
--arg repo "$GITEA_ARM_REPO" \
--argjson requested_at "$NOW" \
--argjson expires_at "$EXPIRES" \
--arg request_comment_id "$COMMENT_ID" \
--arg request_created_at "$CREATED_AT" \
'{nonce:$nonce, mission:$mission, issue:$issue, repo:$repo, requested_at:$requested_at, expires_at:$expires_at,
request_comment_id:$request_comment_id, request_created_at:$request_created_at}' \
> "$PENDING_FILE"
# ── 貼完請求就指派給 leo 掛 Humanleo 2026-08-16:「沒有指派,沒有加 Human」)──
#
# 為什麼要自動:總管同一天才立下「指派給 leo 的每一張都要真的在等他」,
# 貼完 ARM 就忘了做 ⇒ leo 的「指派給您的」清單漏掉那張真正在等他的票。
# ⇒ 這件事不該靠人記得:**貼出 ARM 請求 = 這張票此刻在等 leo**,兩者是同一件事。
_LBL=$(curl -s -H "Authorization: token $TOKEN" \
"$GITEA_ARM_API/repos/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/labels?limit=60" \
| jq -r '.[] | select(.name=="Human") | .id' 2>/dev/null | head -1)
_CUR=$(curl -s -H "Authorization: token $TOKEN" \
"$GITEA_ARM_API/repos/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/issues/$ISSUE" \
| jq -r '[.labels[].id] | @csv' 2>/dev/null | tr -d '"')
if [ -n "$_LBL" ]; then
_IDS=$(printf '%s,%s' "${_CUR:-}" "$_LBL" | tr ',' '\n' | grep -E '^[0-9]+$' | sort -u | paste -sd, -)
curl -s -o /dev/null -X PUT -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
-d "{\"labels\":[${_IDS}]}" \
"$GITEA_ARM_API/repos/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/issues/$ISSUE/labels"
fi
curl -s -o /dev/null -X PATCH -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
-d "{\"assignees\":[\"$GITEA_ARM_APPROVER_LOGIN\"]}" \
"$GITEA_ARM_API/repos/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/issues/$ISSUE"
echo " (已指派給 $GITEA_ARM_APPROVER_LOGIN 並掛上 Human——他的「指派給您的」看得到這張)"
echo "✅ 已在 Gitea 貼出請求:https://git.uncle6.me/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/issues/$ISSUE"
echo ""
echo "── 轉告 leo(照這樣發,一句狀況+一個可回的詞)──────────────────"
echo "[總管] 需要你解一道鎖:$MISSION"
echo "回這則 Gitea 留言貼「$NONCE」就好($MINUTES 分鐘內有效):https://git.uncle6.me/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/issues/$ISSUE"
echo "──────────────────────────────────────────────────────────────"
echo ""
echo "nonce=$NONCE"
echo "issue=$ISSUE"
echo "expires_at=$EXPIRES ($EXPIRES_HUMAN)"
echo "本地請求檔:$PENDING_FILE"