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>
81 lines
4.7 KiB
Bash
Executable File
81 lines
4.7 KiB
Bash
Executable File
#!/bin/sh
|
||
# gitea-bootstrap.sh — 新 repo 開通工作管理元件(狀態標籤)。冪等,重跑無害。
|
||
#
|
||
# 為什麼是腳本不是一段叮嚀(leo 2026-08-09:「每到一個新的 repo 就要建立這些管理元件」):
|
||
# 靠人/靠 AI 記得 = 必然漂移。今天一天就抓到三條「規則寫了沒人驗」。
|
||
# label 是 repo-scoped、沒有跨 repo 繼承 ⇒ 每個 repo 都要建一次 ⇒ 必須一行做完。
|
||
#
|
||
# 用法(在該 repo 目錄下跑,或用 -r 指定):
|
||
# scripts/gitea-bootstrap.sh # 用當前 repo 的 gitea remote
|
||
# scripts/gitea-bootstrap.sh -r Leo/some-repo # 指定 repo(token 仍從當前 repo 取)
|
||
#
|
||
# milestone 不在這裡建——milestone = sprint = 一次交貨,每個 repo 的交貨內容不同,
|
||
# 不能預設。建法見 /issue-handle skill。
|
||
set -eu
|
||
|
||
REPO=""
|
||
[ "${1:-}" = "-r" ] && { REPO="${2:?-r 後面要接 owner/repo}"; }
|
||
|
||
REMOTE=$(git remote get-url gitea 2>/dev/null) || {
|
||
echo "✗ 當前目錄沒有名為 gitea 的 remote。請 cd 到目標 repo,或先加 remote。" >&2; exit 1; }
|
||
|
||
TOKEN=$(printf '%s' "$REMOTE" | sed -E 's|.*//[^:]+:([^@]+)@.*|\1|')
|
||
[ "$TOKEN" = "$REMOTE" ] && { echo "✗ gitea remote URL 裡沒有 token,無法取得認證。" >&2; exit 1; }
|
||
HOST=$(printf '%s' "$REMOTE" | sed -E 's|.*@([^/]+)/.*|\1|')
|
||
[ -n "$REPO" ] || REPO=$(printf '%s' "$REMOTE" | sed -E 's|.*://[^/]+/(.+)\.git$|\1|')
|
||
|
||
echo "→ repo: $REPO host: $HOST"
|
||
|
||
# 狀態機(互斥 scope label)。順序=出貨流程:
|
||
# s/todo → s/doing → s/stage →(leo 蓋章 + arm + 推 prod)→ closed
|
||
# 🔴 不建 s/done:closed 就是 done,多一個就是同一件事兩個真相。
|
||
# 🔴 s/triage 存在的理由:**「留白」查不出來**。撈得到 s/todo,卻撈不到
|
||
# 「所有還沒驗傷的」——沒有標籤不是一種狀態,是查詢的死角
|
||
# (leo 2026-08-09 建 Triage 看板時暴露的設計缺陷)。
|
||
# p/ 是另一個軸:s/ 答「走到哪」、p/ 答「多重要」——
|
||
# s/backlog 的東西也可以是 p/high,併成一組就表達不出來。
|
||
set -- \
|
||
's/triage|d4c5f9|新進來的,還沒驗傷——還沒決定要不要做' \
|
||
's/backlog|c2e0c6|驗過了、確定要做,但還沒排進任何 sprint(wishlist/功能需求/待規劃)' \
|
||
's/todo|ededed|已排進 sprint,等開工' \
|
||
's/doing|0e8a16|進行中——現在有人在做' \
|
||
's/stage|5319e7|已推上 stage,等 leo 去 youlin 的 stage 環境驗收(出貨流程第⑤步)' \
|
||
's/pending|fbca04|卡住——等外部/等人,不是沒人做' \
|
||
'p/high|b60205|高——擋住交付或有時間壓力' \
|
||
'p/low|bfd4f2|低——想做,但晚一點沒關係'
|
||
|
||
# 🔴 先抓現有清單再建。**Gitea 允許同名 label、回 201 不是 422**
|
||
# ⇒ 靠「重複會被擋」達成冪等是錯的。2026-08-09 實撞:本腳本第一版
|
||
# 在 arcrun-rag 造出每個標籤各兩份——那會直接弄壞互斥狀態機(同名兩個 id,
|
||
# 貼哪一個都不會把另一個頂掉),事後手動刪掉四個重複 id 才救回來。
|
||
EXISTING=$(curl -s -H "Authorization: token $TOKEN" -H "Cache-Control: no-cache" \
|
||
"https://$HOST/api/v1/repos/$REPO/labels?limit=100" \
|
||
| python3 -c "import json,sys;print(' '.join(l['name'] for l in json.load(sys.stdin)))")
|
||
|
||
created=0; existed=0; failed=0
|
||
for spec in "$@"; do
|
||
name=${spec%%|*}; rest=${spec#*|}; color=${rest%%|*}; desc=${rest#*|}
|
||
case " $EXISTING " in *" $name "*) echo " · 已存在 $name"; existed=$((existed+1)); continue ;; esac
|
||
payload=$(NAME="$name" COLOR="$color" DESC="$desc" python3 -c '
|
||
import json,os
|
||
print(json.dumps({"name":os.environ["NAME"],"color":"#"+os.environ["COLOR"],
|
||
"description":os.environ["DESC"],"exclusive":True}))')
|
||
code=$(printf '%s' "$payload" | curl -s -o /tmp/.bootstrap-out -w '%{http_code}' \
|
||
-X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||
--data-binary @- "https://$HOST/api/v1/repos/$REPO/labels")
|
||
case "$code" in
|
||
201) echo " ✓ 建立 $name"; created=$((created+1)) ;;
|
||
422) echo " · 已存在 $name"; existed=$((existed+1)) ;;
|
||
*) echo " ✗ $name → HTTP $code: $(head -c 120 /tmp/.bootstrap-out)"; failed=$((failed+1)) ;;
|
||
esac
|
||
done
|
||
|
||
# 複驗:從 repo 端讀回來,不信自己送出的指令(2026-08-09 教訓:宣告 vs 證據)
|
||
echo "→ 複驗(從 repo 讀回):"
|
||
curl -s -H "Authorization: token $TOKEN" -H "Cache-Control: no-cache" \
|
||
"https://$HOST/api/v1/repos/$REPO/labels?limit=100" \
|
||
| python3 -c "import json,sys;print(' ',sorted(l['name'] for l in json.load(sys.stdin) if l['name'].startswith('s/')))"
|
||
|
||
echo "→ 新建 $created/已存在 $existed/失敗 $failed"
|
||
[ "$failed" -eq 0 ] || exit 1
|