ISEP 0.1.0:環境設定收成一個 plugin,本機與雲端共用一份

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>
This commit is contained in:
2026-08-20 11:41:46 +08:00
commit c2638668e3
85 changed files with 11879 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
#!/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 # 指定 repotoken 仍從當前 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/doneclosed 就是 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