c2638668e3
leo 2026-08-20:「同一個 plugin 你用,薄殼也用,保證兩邊同步」
「我要你幫雲端做薄殼,永遠都有問題,你要做的就是這組設定
你自己可以 dogfooding」
搬進來:41 支 hook(51 條註冊)/7 支 command/2 支 skill/23 支腳本。
不搬 .env、wiki、docs——那些是知識不是環境。
51 條 hook 路徑全部從 $CLAUDE_PROJECT_DIR/.claude/hooks/ 改成 ${CLAUDE_PLUGIN_ROOT}/hooks/,
零漏網。那正是薄殼一直壞掉的根:雲端 cwd 不是真身,寫死路徑就斷。
尚未驗證:Claude Code 能不能從私有 Gitea repo 裝 marketplace(要憑證)。
下一步就是在本機實際裝一次,通了才動雲端 bootstrap.sh。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
313 lines
19 KiB
Bash
Executable File
313 lines
19 KiB
Bash
Executable File
#!/bin/sh
|
||
# main-and-prod-push-guard.sh — PreToolUse(Bash):**兩層手動確認閘**
|
||
#
|
||
# 🔴 立這道閘的來由(leo 2026-08-10):
|
||
# 「**subagent 要推 gitea main 應該要你手動確認,要推 prod 要我手動確認。**」
|
||
#
|
||
# 當天的事故(本閘的直接成因):
|
||
# 總管派 subagent 去整理 `polaris/mira` 的散落分支,指令寫「驗過了 → 合併(這是預設)」,
|
||
# 引的是 leo 08-05 那句「驗過就要 merge」。subagent 照做,**合併並直接 push 到 gitea main**。
|
||
# ⇒ 而頂層 CLAUDE.md 規則二的四題公式**白紙黑字列著「push 到 main」是人閘觸發項**。
|
||
# ⇒ **規則早就存在,但沒有任何機制驗證有沒有照做**——這是同款的第 N 次
|
||
# (history-first/KBDB-first/派工-first/stage-first 都是這個形狀)。
|
||
# ⇒ 閘漏的是總管:派工指令裡沒把那道閘標出來。**所以閘要長在機器上,不是長在我的記性上。**
|
||
#
|
||
# 兩條,各自對應一個人:
|
||
# ① subagent(`CLAUDE_CODE_CHILD_SESSION=1`)**一律不准 push 到 main** → 交回總管
|
||
# ② **prod 部署**(會讓封測者/用戶拿到東西)需要 leo 親手解保險 `.github-armed`
|
||
# ——補 `stage-before-prod-guard.sh` 的破口:那支只比對
|
||
# `arcrun-rag-bundles`/`github-arm`/`publish-github` 三個關鍵字,
|
||
# **`wrangler deploy` 打 prod 它抓不到**(2026-08-10 實查)。
|
||
#
|
||
# 設計紀律(沿用 stage-before-prod-guard 用血換來的兩條):
|
||
# • **fail-closed**:任何內部錯誤都不准變成靜默放行。exit 1 不擋,只有 exit 2 才擋。
|
||
# • **先排除「談論/讀取」再比對關鍵字**:誤攔比漏攔更容易殺死一道閘
|
||
# (被擋得莫名其妙,人就會想辦法繞過它)。
|
||
set -eu
|
||
|
||
INPUT="$(cat)"
|
||
CMD=$(printf '%s' "$INPUT" | python3 -c '
|
||
import sys, json
|
||
try: print(json.load(sys.stdin).get("tool_input", {}).get("command", "") or "")
|
||
except Exception: print("")
|
||
' 2>/dev/null || printf '')
|
||
|
||
[ -z "$CMD" ] && exit 0
|
||
|
||
# ── 先放行明確不發佈的動作(讀取、查狀態、寫本地版控、演練)──────────────
|
||
# 關鍵字出現在 commit 訊息、在 sed/grep 的參數裡,都不是「執行」。
|
||
case "$CMD" in
|
||
sed\ *|cat\ *|grep\ *|head\ *|tail\ *|wc\ *|less\ *|ls\ *|awk\ *|rg\ *|echo\ *) exit 0 ;;
|
||
*"git commit"*|*"git add"*|*"git tag"*|*"git stash"*) exit 0 ;;
|
||
*"git status"*|*"git log"*|*"git diff"*|*"git show"*|*"git branch"*) exit 0 ;;
|
||
*" --dry-run"*|*"--dry-run "*) exit 0 ;;
|
||
esac
|
||
|
||
# ── ① push 到 main 要有「總管決定了」的戳記 ────────────────────────────
|
||
#
|
||
# 🔴 **原本的判斷方式是錯的,上線當天就被自己擋到才發現(2026-08-10)**:
|
||
# 本來用 `CLAUDE_CODE_CHILD_SESSION=1` 當「這是 subagent」的標記——
|
||
# **實測總管主 session 也是 1**(Claude Desktop,`CLAUDE_CODE_ENTRYPOINT=claude-desktop`)。
|
||
# ⇒ 那個變數不是身分標記 ⇒ 這道閘**把該放行的人也鎖住了**,總管推不了 main。
|
||
# ⚠️ `guard-cross-project.sh` 用同一個變數,**可能也一直在誤判**(另記,待查)。
|
||
#
|
||
# leo 2026-08-10 把權責講清楚了:
|
||
# 「**subagent 的總管是你,是否可以推 gitea main 由你來決定,我管推到 prod。**」
|
||
# ⇒ 這道閘要守的不是「誰在敲鍵盤」,是「**有沒有人做過那個決定**」。
|
||
# ⇒ 沒有可靠的身分辨識時,改成**正向確認**:推 main 前要有一枚新鮮的戳記。
|
||
# subagent 當然造得出那枚戳記——但它得刻意繞過一段明講「不要這樣做」的訊息。
|
||
# **閘擋的是無心,不是惡意。** 而現在這個版本連無心都擋不了(它誰都擋)。
|
||
STAMP="/tmp/.main-push-ok"
|
||
|
||
# 🔴 2026-08-11 這道閘被自己的戳記穿透了,修法寫在這裡:
|
||
# 總管為了推「頂層 InkStoneCo」而 touch 了戳記,15 分鐘之內
|
||
# **一條並行的 subagent 把 commit 推上了 `matrix/arcrun` 的 main**,本閘毫無反應。
|
||
# ⇒ 舊版有兩個洞,缺一不可:
|
||
# ① **不綁 repo**:替 A repo 開的門,B repo 也走得過
|
||
# ② **不會用掉**:一次 touch 在 15 分鐘內可以放行無限多次推送
|
||
# ⇒ 現在改成 **綁 repo + 單次用完即丟**,而且**只在真的要擋的那一刻才檢查**
|
||
# (放在檔頭會被任何一條無關的 Bash 指令把戳記燒掉)。
|
||
stamp_ok() {
|
||
[ -f "$STAMP" ] || return 1
|
||
NOW=$(date +%s 2>/dev/null || echo 0)
|
||
MT=$(stat -f %m "$STAMP" 2>/dev/null || stat -c %Y "$STAMP" 2>/dev/null || echo 0)
|
||
case "$NOW$MT" in *[!0-9]*) return 1 ;; esac
|
||
[ "$NOW" -gt 0 ] && [ "$MT" -gt 0 ] || return 1
|
||
[ $((NOW - MT)) -lt 900 ] || return 1
|
||
|
||
# 綁 repo:戳記內容要對得上「現在人在哪個 repo」
|
||
#
|
||
# 🔴 2026-08-12 補洞:舊版寫成「內容非空才比對」⇒ **`touch` 造出來的空檔跳過整個綁定**,
|
||
# 等於一把萬用鑰匙——正是 08-11 那次穿透的形狀(替 A repo 開的門 B repo 也走得過)。
|
||
# 而且 `.claude/settings.local.json` 裡真的放行過 `touch /tmp/.main-push-ok`。
|
||
# ⇒ 現在**空內容一律不算數**:要嘛寫得出 repo 路徑且對得上,要嘛不放行。
|
||
HERE=$(git rev-parse --show-toplevel 2>/dev/null || printf '')
|
||
WANT=$(head -1 "$STAMP" 2>/dev/null || printf '')
|
||
[ -n "$WANT" ] || return 1
|
||
[ -n "$HERE" ] || return 1
|
||
[ "$WANT" = "$HERE" ] || return 1
|
||
|
||
rm -f "$STAMP" 2>/dev/null || true # 單次:用完即丟
|
||
return 0
|
||
}
|
||
|
||
if true; then
|
||
case "$CMD" in
|
||
*"git push"*)
|
||
# 只擋打到 main/master 的;推自己的 feature 分支照常放行
|
||
case "$CMD" in
|
||
# 🔴 2026-08-12 拿掉 `push -u` / `push --set-upstream` 這兩個條件。
|
||
# 它們本來是想抓「沒寫分支的 push」,但實際抓到的是
|
||
# `git push -u gitea fix/xxx`——**subagent 發表自己分支的標準動作**
|
||
# (第一次推當然要 -u)。⇒ 舊版等於「agent 永遠推不出自己的分支」,
|
||
# 而 leo 2026-08-12 的設計是「主線禁止動,大家都走 PR」,推分支是那條路的第一步。
|
||
# 08-12 當天四張 PR 全是繞成 `git push gitea a:a` 才推出去的。
|
||
# `*main*`/`*master*` 兩條照舊——真正該擋的是目標分支,不是有沒有帶旗標。
|
||
*main*|*master*)
|
||
stamp_ok && exit 0
|
||
|
||
# ── 擋下的同時,把「誰想推什麼」留成一份請求(leo 2026-08-12)───────────
|
||
#
|
||
# leo 原話:「**它會問你的意見,所以每個你叫起來的 subagent 都有名字。**」
|
||
#
|
||
# 做得到的與做不到的,先講清楚:
|
||
# ❌ **做不到「同步問總管」**——hook 跑在子 session 自己的行程裡,總管在另一個行程。
|
||
# 要同步問只能 block 等一個檔案出現,那會把 subagent 掛死在那裡。
|
||
# ✅ **做得到「當場擋 + 留下原始請求」**:總管在自己的迴圈裡讀這個目錄,
|
||
# 看到的是 repo/分支/逐筆 commit 的**原始資料**,不是 subagent 的散文轉述。
|
||
# ——這才是名字真正值錢的地方:**不是判斷你是誰,是留下是誰要求的**。
|
||
#
|
||
# 🔴 身分的方向刻意不改:**沒有名字不等於總管**(那是 fail-open——
|
||
# 子 session 繼承環境變數,把名字拿掉就升格了)。放行的唯一憑證仍然只有
|
||
# 那枚綁 repo、用完即丟的戳記。名字只是署名,不是權限。
|
||
# 📌 名字只在 `claude -p` 起的子 session 上可靠(乾淨的環境邊界);
|
||
# Agent tool 起的 subagent 與總管同一個行程、共用環境 ⇒ 那條路上名字塞不進也擦不掉。
|
||
# 而改子 repo code 的正路本來就是 `claude -p`,所以夠用。
|
||
_who="${CLAUDE_AGENT_NAME:-未署名}"
|
||
_hookdir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) || _hookdir=""
|
||
_reqdir="${_hookdir%/hooks}/pending-main-push"
|
||
if [ -n "$_hookdir" ] && mkdir -p "$_reqdir" 2>/dev/null; then
|
||
_root=$(git rev-parse --show-toplevel 2>/dev/null || printf 'unknown')
|
||
# 檔名只用 ASCII(`未署名` 之類會被 tr 打成一排 dash,看不出是誰)
|
||
_slugwho=$(printf '%s' "${CLAUDE_AGENT_NAME:-unnamed}" | tr -c 'A-Za-z0-9._-' '-')
|
||
case "$_slugwho" in *[!-]*) : ;; *) _slugwho=unnamed ;; esac
|
||
# ⚠️ 先 printf 再 tr:`basename` 會帶一個換行,直接餵 tr 會變成結尾多一根 dash
|
||
_slugrepo=$(printf '%s' "$(basename "$_root")" | tr -c 'A-Za-z0-9._-' '-')
|
||
_slug="${_slugwho}--${_slugrepo}"
|
||
# ⚠️ 這幾行刻意用 `printf '%s\n' "整句"`,不要把內容寫進 printf 的格式字串裡。
|
||
# 2026-08-12 實撞:格式字串裡同時有反引號與 %s 時,那幾行整行不見(而前後行都在),
|
||
# ——**寫完當場肉眼檢查產出的檔案才發現**,hook 自己不會叫。內容一律當資料傳。
|
||
_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || printf '?')
|
||
_when=$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || printf '?')
|
||
_fence='```'
|
||
{
|
||
printf '%s\n\n' "# 推 main 的請求:$_who"
|
||
printf '%s\n' "- repo:$_root"
|
||
printf '%s\n' "- 分支:$_branch"
|
||
printf '%s\n\n' "- 時間:$_when"
|
||
printf '%s\n' "- 它想跑的指令:"
|
||
printf '%s\n%s\n%s\n\n' "$_fence" "$CMD" "$_fence"
|
||
printf '%s\n\n%s\n' "## 還沒推上去的 commit(原始資料,不是轉述)" "$_fence"
|
||
git log --oneline '@{upstream}..HEAD' 2>/dev/null \
|
||
|| git log --oneline -20 2>/dev/null \
|
||
|| printf '(列不出來)\n'
|
||
printf '%s\n\n%s\n\n%s\n' "$_fence" "## 改了哪些檔" "$_fence"
|
||
git diff --stat '@{upstream}..HEAD' 2>/dev/null | tail -40 || printf '(列不出來)\n'
|
||
printf '%s\n\n---\n%s\n' "$_fence" "總管裁完請刪掉這個檔——留著代表「還沒裁」。"
|
||
} > "$_reqdir/$_slug.md" 2>/dev/null || true
|
||
fi
|
||
|
||
cat >&2 <<'MSG'
|
||
🚫 推 main 要先有「總管決定了」的戳記(leo 2026-08-10 立)
|
||
|
||
leo 原話:
|
||
「**subagent 要推 gitea main 應該要你手動確認,要推 prod 要我手動確認。**」
|
||
「**subagent 的總管是你,是否可以推 gitea main 由你來決定,我管推到 prod。**」
|
||
|
||
━━━ 你是 subagent ━━━
|
||
**不要造那枚戳記。** 把改動留在自己的分支上,推那條分支,交件裡寫清楚:
|
||
· 分支名
|
||
· 這幾筆各是什麼、**各自**驗過了沒有(逐筆,不要整包說「驗過了」)
|
||
· 你建議合併還是先擱著,理由是什麼
|
||
總管看過才會併進 main。**這是一句話的事,不要自己想辦法過閘。**
|
||
|
||
📮 **你的請求已經自動留下來了**(`.claude/pending-main-push/` 底下,用你的
|
||
`CLAUDE_AGENT_NAME` 署名;沒有這個變數就署「未署名」)。裡面是 repo/分支/
|
||
逐筆 commit 的原始資料 ⇒ **你不必在交件裡重抄一遍那些,總管會直接讀。**
|
||
|
||
━━━ 你是總管 ━━━
|
||
逐筆看過那些 commit(`git log --oneline gitea/main..<branch>`、`git diff --stat`),
|
||
確定它們該進 main,再:
|
||
|
||
git rev-parse --show-toplevel > /tmp/.main-push-ok && <你的 git push 指令>
|
||
|
||
戳記 **綁這個 repo、只能用一次、15 分鐘失效**——它代表「**這一次、這個 repo,我看過了**」。
|
||
|
||
🔴 **為什麼從 `touch` 改成寫入 repo 路徑(2026-08-11 被穿透過一次)**:
|
||
總管為了推頂層而 `touch` 了戳記,**15 分鐘內一條並行的 subagent 把 commit 推上了另一個 repo 的 main**,
|
||
本閘毫無反應。舊版既不綁 repo、也不會用掉 ⇒ 一次確認等於全域開門。
|
||
**在多條 subagent 並行的情況下,「時間窗」本身就是漏洞。**
|
||
|
||
【為什麼不是形式主義】2026-08-10 真的發生過:
|
||
總管派工寫「驗過了 → 合併(這是預設)」,subagent 照做並直接推上 gitea main。
|
||
而頂層 CLAUDE.md 規則二的四題公式**白紙黑字列著「push 到 main」是人閘觸發項**。
|
||
⇒ 內容是好的,但**該不該先問,和內容對不對,是兩件事**。
|
||
⇒ 而且那次是**總管的指令漏了那道閘**——所以現在由機器守,不靠誰記得。
|
||
|
||
【真的該推 main 的例外】不存在。交回總管,一句話的事。
|
||
MSG
|
||
exit 2
|
||
;;
|
||
esac
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
# ── ② prod 部署要 leo 親手解保險 ────────────────────────────────────────
|
||
# 先排除 stage/staging 的同名動作(它們本來就該自由跑)
|
||
#
|
||
# 🔴 2026-08-13 leo 拍板:「**你可以標示整套測試環境,告訴閘『這些是 stage』,
|
||
# 做這些事時白名單跳過**」。
|
||
#
|
||
# 為什麼要補(同款第三次):`prod-write-guard.sh:188-191` 在 2026-08-12 就補過
|
||
# 一模一樣的例外,理由白紙黑字寫在那裡——
|
||
# 「`youlin`(youlin-hsieh-dev)**就是測試場**,不是 prod⋯⋯
|
||
# 本閘原本只認 `staging` 這個字 ⇒ 打 youlin 做實驗被誤擋」
|
||
# **但同一批補丁漏了這一支。** ⇒ 修法只改其中一份、沒有機制保證另一份跟上
|
||
# (同日另兩例:`kbdb-api-wall-guard.sh` 本機版與 plugin 版不同步,擋了九次)。
|
||
#
|
||
# 🔴 判準不變,只是把「測試環境」講完整:**放行的是目標,不是動作。**
|
||
# 命令看不出打哪裡 ⇒ 不放行。想被放行就把目標寫進命令裡,
|
||
# **不要用假的 `# staging` 註解騙閘**(2026-08-13 一條 subagent 明確拒絕這樣做,是對的)。
|
||
#
|
||
# 整套測試環境=下列任一出現在命令裡:
|
||
# · `youlin-hsieh-dev` ── 帳號名(D37 定的 stage 帳號)
|
||
# · `1129efd7df2e8899d537e9c8fbabb6cb` ── 該帳號 ID(用 env 指過去時只有它看得見)
|
||
# · `_YOULIN_` ── 憑證變數名(如 `CLOUDFLARE_API_TOKEN_YOULIN_CC_USE`)
|
||
# · `X-Arcrun-API-Key: youlin` ── 打該實例 API 時的身分標頭
|
||
case "$CMD" in
|
||
*staging*|*-staging*|*"--env stage"*|*"--env=stage"*|*stage.*) exit 0 ;;
|
||
*youlin-hsieh-dev*|*1129efd7df2e8899d537e9c8fbabb6cb*|*_YOULIN_*|*"X-Arcrun-API-Key: youlin"*) exit 0 ;;
|
||
esac
|
||
|
||
# 🔴 2026-08-12 leo 拍板「加閘」:**geek6688(出貨機)是明文授權「總管可以直接動」的那一台。**
|
||
#
|
||
# 依據——`system-dev/wiki/credentials-map.md` 早就寫死:
|
||
# `CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE`|geek6688 帳號 CF token(**leo 2026-08-12 開**)
|
||
# ——**總管唯一可以直接動的實例**|用途:**把那台拉到最新/推出貨工作流/
|
||
# `ARCRUN_SHIP_BASE` 指過去後的第 17~19 站**
|
||
# ⇒ 規則早就存在,只是這道閘不知道。本段就是把它教給機器。
|
||
#
|
||
# 為什麼該放行:geek6688 的職務就是「**不受出貨控制**」——出貨機與被出貨的是同一套程式,
|
||
# 要出的貨若本身是引擎更新會形成死結,所以要有一台先更新的機器。
|
||
# 每出一次貨都要 leo 親手 arm 它一次 = 把他變成出貨流程裡的按鈕,
|
||
# **而那正是這台實例存在的意義要拔掉的東西**。
|
||
#
|
||
# 🔴 **範圍刻意開到最窄:只認「命令裡明確指名 geek6688 這台」。**
|
||
# 放行的是**目標**,不是動作——打 leo21c/uncle6/任何其他實例一律照舊要 arm。
|
||
# 命令看不出打哪裡 ⇒ 不放行(與上方 stage 同一個原則:那正是該讓它看得出來的理由)。
|
||
case "$CMD" in
|
||
*geek6688*|*CC_SHIPPING_CORE*|*ACCOUNT_ID_GEEK6688*) exit 0 ;;
|
||
esac
|
||
|
||
case "$CMD" in
|
||
*"wrangler deploy"*|*"wrangler publish"*|*"wrangler versions deploy"*) ;;
|
||
*) exit 0 ;;
|
||
esac
|
||
|
||
PROJ="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
||
ARMED="$PROJ/.github-armed"
|
||
|
||
# ── 🔐 leo 解保險的第二條路:Gitea 票上回一句話(2026-08-13,示範接這一道閘)──
|
||
#
|
||
# leo 2026-08-13:「如果你會被通知,就不需要通過 terminal 來 arm 了」。
|
||
# 身分分離做完後(`Leo/InkStoneCo#32`),票上作者是 `Leo` 的留言機器偽造不出來,
|
||
# 可以當人閘證據——於是 arm 可以搬到手機。
|
||
# 🪦 2026-08-16 起不再是單一頻道票(原 issues/34)——票號由發請求的當下指定,
|
||
# 解哪個任務的保險就貼在那個任務自己的票上(issues/34#issuecomment-2804)。
|
||
#
|
||
# 這條路完全獨立於上面的 `$ARMED`(`.github-armed`,終端機那條路照舊在,兩者並存)。
|
||
# 只有本機**已經有人跑過 `scripts/gitea-arm-request.sh`**(本地留下待核請求)時才會
|
||
# 去打一次 Gitea——不是每次 wrangler deploy 都主動打;沒有待核請求就跳過,
|
||
# 不浪費一次 API 呼叫,也不構成輪詢(只在「機器需要解閘的當下」查一次)。
|
||
if [ ! -f "$ARMED" ] && [ -d "$PROJ/.claude/gitea-arm/pending" ] \
|
||
&& [ -n "$(ls -A "$PROJ/.claude/gitea-arm/pending" 2>/dev/null)" ]; then
|
||
# 🔴 `--consume`:這一行放行的就是 prod 部署本身(下面直接 `exit 0`)
|
||
# ⇒ 核准必須當場用掉。2026-08-16 起 check 預設唯讀,不帶這個旗標=同一組碼
|
||
# 可以無限次放行 prod,比原本的「探測會誤消耗」嚴重得多。
|
||
if "$PROJ/scripts/gitea-arm-check.sh" --consume >/tmp/.gitea-arm-check-last.log 2>&1; then
|
||
exit 0 # leo 已在 Gitea 回覆過有效代碼,且核對通過(單次用完,見該腳本)
|
||
fi
|
||
fi
|
||
|
||
if [ ! -f "$ARMED" ]; then
|
||
cat >&2 <<'MSG'
|
||
🚫 prod 部署要 leo 親手確認(leo 2026-08-10:「要推 prod 要我手動確認」)
|
||
|
||
偵測到 `wrangler deploy`,而且命令裡沒有任何 stage/staging 字樣 ⇒ 視同**打 prod**。
|
||
**推 prod 就發佈了**——封測者/用戶當場拿到。
|
||
|
||
【怎麼過這道閘】把指令交給 leo 自己跑,並且照 `/issue-handle` 的規矩帶三樣:
|
||
· 他要打開什麼(確切網址/確切指令,**你要先 `--dry-run` 打過**)
|
||
· 他該看到什麼(正確的樣子長怎樣,例如版本字串會變成什麼)
|
||
· 什麼情況算失敗(看到什麼就回你一聲)
|
||
|
||
【這道閘補的破口】`stage-before-prod-guard.sh` 只比對
|
||
`arcrun-rag-bundles`/`github-arm`/`publish-github` 三個關鍵字,
|
||
**抓不到 `wrangler deploy`**(2026-08-10 實查)。它擋的是 bundle 出貨鏈,不是所有發佈。
|
||
|
||
【真的是 stage?】命令裡明確帶 `--env stage`/`staging` 就會自動放行。
|
||
若你確實在打 stage 卻被擋,代表**命令本身看不出打哪裡**——那正是該讓它看得出來的理由。
|
||
|
||
【2026-08-13 新增:leo 現在也能用手機解,不必開電腦】
|
||
1. 跑 `scripts/gitea-arm-request.sh <這件任務對應的票號> "任務描述"` 生一組代碼,
|
||
貼在**這件任務自己那張票上**(不是固定某張頻道票——2026-08-16 起票號由你指定)
|
||
2. 把印出來的那句轉告 leo(Telegram/人話一句+一個代碼)
|
||
3. leo 在 Gitea 上(App 或網頁都行)回那則貼上代碼
|
||
4. 重跑一次你剛剛被擋的指令——這道閘會自動去核對,核對過就放行一次
|
||
MSG
|
||
exit 2
|
||
fi
|
||
|
||
exit 0
|