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>
191 lines
11 KiB
Bash
Executable File
191 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# unpushed-police.sh — 未推警察(Stop / SubagentStop hook)
|
||
#
|
||
# 🔴 2026-08-05:本機 LANG=C.UTF-8 下,把變數(分支名/日期)串進含全形標點的字串會**吃掉字元**
|
||
# (實測 `- $b:$cnt 筆` → 分支名整個消失、只剩「:」的殘骸位元組 bc 9a)。
|
||
# LC_ALL=C 下同一段完全正常 ⇒ 固定 locale,別讓報告內容被環境吃掉。
|
||
export LC_ALL=C
|
||
#
|
||
# 病根(leo 2026-08-02 原話):「如果讓改了沒推會被警告?因為太常發生了」
|
||
#
|
||
# 一天之內同一個病發作六次,全部是「**改對了,但沒送到用戶手上**」:
|
||
# ① Gemini key 修好了(Arcrun 0d49989)→ 沒重建 cypher bundle ⇒ 線上 admin/ai=0,
|
||
# leo 與全體封測者填 key 一律無效,rag_chat 全壞一整天
|
||
# ② CIS logo 換好了(512ea9f 7/31 20:39)→ daemon zip 是 15:35 打的 ⇒ 早 5 小時,
|
||
# 發出去的永遠是舊圖(leo 肉眼在 Finder 抓到)
|
||
# ③ manifest.daemon 欄被 release.mjs 吃掉 → 全體「檢查更新」壞掉一天
|
||
# ④ 推了 GitHub 但 jsDelivr @main 還在吐舊的 ⇒ 推對了用戶仍拿不到
|
||
# ⑤ fix/cis-round3-install 分支從未 push(今天才發現)
|
||
# ⑥ 07-30 同款前科:t143-t150 十筆 commit 只在本機,雲端誤報「線上程式碼失蹤」
|
||
#
|
||
# 為什麼要 hook 而非規則:scripts/check-deploy-drift.sh 七月三十就寫好了,
|
||
# 但**沒有任何東西會自動呼叫它**——只在文件裡被提到。
|
||
# 「機制存在 ≠ 會被讀到」。所以這支要自己跳出來,不能等人想到要跑。
|
||
#
|
||
# 機制:收工那刻掃所有已知 repo,有「未 commit」或「已 commit 未推」就 exit 2 提醒。
|
||
# 只提醒不阻擋——push 需要 leo 開閘(D20),hook 無權也不該代按。
|
||
# 豁免:純對話回合(沒動過檔)/已在本回合推過/stop_hook_active。
|
||
|
||
input="$(cat)"
|
||
|
||
stop_active="$(printf '%s' "$input" | python3 -c '
|
||
import sys, json
|
||
try: print(json.load(sys.stdin).get("stop_hook_active", False))
|
||
except Exception: print(False)
|
||
' 2>/dev/null)"
|
||
[ "$stop_active" = "True" ] && exit 0
|
||
|
||
TOP="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}"
|
||
|
||
# 掃描對象:頂層 + 各子 repo(存在才掃)。worktree 一併涵蓋(git -C 會自己解)。
|
||
REPOS="$TOP $TOP/matrix/arcrun $TOP/products/arcrun-rag $TOP/polaris/mira"
|
||
|
||
problems=""
|
||
for r in $REPOS; do
|
||
[ -d "$r/.git" ] || [ -f "$r/.git" ] || continue
|
||
name="${r#$TOP/}"; [ "$name" = "$TOP" ] && name="(頂層 InkStoneCo)"
|
||
|
||
# ① 有改動沒 commit(排除 untracked,那多半是暫存/產物)
|
||
dirty="$(git -C "$r" diff --shortstat 2>/dev/null | head -1)"
|
||
staged="$(git -C "$r" diff --cached --shortstat 2>/dev/null | head -1)"
|
||
|
||
# ② 已 commit 但沒推(**當前分支**)
|
||
unpushed=""
|
||
br="$(git -C "$r" branch --show-current 2>/dev/null)"
|
||
if [ -n "$br" ]; then
|
||
up="$(git -C "$r" rev-parse --abbrev-ref "@{upstream}" 2>/dev/null || true)"
|
||
if [ -n "$up" ]; then
|
||
n="$(git -C "$r" rev-list --count "$up".."$br" 2>/dev/null || echo 0)"
|
||
[ "$n" != "0" ] && unpushed="領先 $up $n 筆"
|
||
else
|
||
# 沒 upstream=這條分支從來沒推過(今天 fix/cis-round3-install 就是這樣)
|
||
has_commits="$(git -C "$r" rev-list --count HEAD 2>/dev/null || echo 0)"
|
||
[ "$has_commits" != "0" ] && unpushed="分支 $br **從未推過**(無 upstream)"
|
||
fi
|
||
fi
|
||
|
||
# ③ 🔴 2026-08-05 t195 補:**散落分支**——有 commit 沒併回當前分支的其他本地分支。
|
||
# 原本只查 ①②(當前分支的髒樹/未推),所以 D36 授權修補躺在 fix/cis-round3-install
|
||
# 整整一週都沒被報過,害 feat/daemon 的收卡端全 401、新卡一張都寫不進去。
|
||
# leo 2026-08-05:「為什麼有分支沒 merge?都是你寫的,驗過就要 merge⋯⋯
|
||
# 如果它會留分支無明顯理由不 merge,那就要整理」。
|
||
# 判準=「這條分支有沒有當前分支沒有的 commit」,不是分支數量多寡。
|
||
stray=""
|
||
held=""
|
||
# 🔴 2026-08-15 訂正:基準**不能用當前簽出的分支**。
|
||
# 共用工作區常停在某條舊功能分支上(08-15 實撞:matrix/arcrun 停在
|
||
# fix/credential-resolve-without-auth-worker,而 main 已經領先它 17 筆)
|
||
# ⇒ 四條分支全被報成「散落」,其中三條早就併進 main 了。
|
||
# **這正是同一晚重複八次的那個病:檢查跑了,但檢查的對象是錯的。**
|
||
# ⇒ 基準改成「整合分支」:gitea/main > origin/main > main > 當前分支。
|
||
base="$br"
|
||
for cand in gitea/main origin/main main; do
|
||
if git -C "$r" rev-parse --verify -q "$cand" >/dev/null 2>&1; then base="$cand"; break; fi
|
||
done
|
||
if [ -n "$base" ]; then
|
||
for b in $(git -C "$r" for-each-ref --format='%(refname:short)' refs/heads/ 2>/dev/null); do
|
||
[ "$b" = "$base" ] && continue
|
||
git -C "$r" merge-base --is-ancestor "$b" "$base" 2>/dev/null && continue
|
||
cnt="$(git -C "$r" rev-list --count "$base".."$b" 2>/dev/null || echo 0)"
|
||
[ "$cnt" = "0" ] && continue
|
||
# ── 刻意保留的分支(2026-08-12 加)─────────────────────────────────
|
||
# 本閘原本只認得「該併」與「該刪」兩種,但**還有第三種:刻意不併**
|
||
# (例:`wip/stopped-agents-2026-08-10` 是 arcrun-rag#56 那六個 Go 檔的唯一一份,
|
||
# 保留理由與刪除條件記在該票上)。
|
||
# 沒有這個狀態 ⇒ 那條分支**每一回合都被叫一次**,而且叫的是同一件已經交代過的事。
|
||
# 🔴 **永遠在響的警報,等於訓練人忽略這個警報**——那才是真正的損失,
|
||
# 因為下一條真的失蹤的分支會混在同一堆雜訊裡。
|
||
# 門檻刻意留著:要寫一行理由進 `.claude/branch-holds.md` 才生效,
|
||
# **不能只在對話裡說**(對話會消失,這正是本閘存在的理由)。
|
||
_holds="$TOP/.claude/branch-holds.md"
|
||
if [ -f "$_holds" ] && grep -qF "$b" "$_holds" 2>/dev/null; then
|
||
# 🔴 這裡**不要**用 sed 去切掉「分支名:」那段前綴。
|
||
# 本檔開頭第 4 行已經記過:`LC_ALL=C` 下處理含全形標點的字串會咬掉位元組,
|
||
# 實測切完會吐出 `刻意保留——\xef\xbf\xbd它是` 這種殘骸。
|
||
# 整行照印就好——那一行本來就寫得像人話,前綴留著也讀得通。
|
||
_why="$(grep -F "$b" "$_holds" | head -1)"
|
||
held="$held
|
||
$_why"
|
||
continue
|
||
fi
|
||
age="$(git -C "$r" log -1 --format='%ad' --date=format:'%m-%d' "$b" 2>/dev/null)"
|
||
# 用真實換行累積,不用 \n 逃脫——否則後面的 printf '%b' 會把分支名再解讀一次
|
||
stray="$stray
|
||
- $b:$cnt 筆未併入 $base(最後動 $age)"
|
||
done
|
||
fi
|
||
|
||
line=""
|
||
[ -n "$dirty" ] && line="$line
|
||
· 未 commit:$dirty"
|
||
[ -n "$staged" ] && line="$line
|
||
· 已 staged 未 commit:$staged"
|
||
[ -n "$unpushed" ] && line="$line
|
||
· $unpushed"
|
||
[ -n "$stray" ] && line="$line
|
||
· 🔀 **散落分支**(有 commit 沒併回 $br):$stray"
|
||
# 刻意保留的分支**不會自己觸發警報**(那正是加它的目的),
|
||
# 但這個 repo 若本來就有別的問題要報,就順便把它列出來當脈絡——
|
||
# 免得「保留」變成另一種看不見。
|
||
[ -n "$line" ] && [ -n "$held" ] && line="$line
|
||
· ⏸️ 刻意保留(不觸發警報,理由在 .claude/branch-holds.md):$held"
|
||
[ -n "$line" ] && problems="$problems
|
||
📍 $name$line"
|
||
done
|
||
|
||
[ -z "$problems" ] && exit 0
|
||
|
||
cat >&2 <<EOF
|
||
🚓 未推警察:有東西改了但還留在本機。
|
||
|
||
【leo 2026-08-02】「如果讓改了沒推會被警告?**因為太常發生了**」
|
||
【判準】改對了 ≠ 送到用戶手上。同一天發作六次,每次都是這個病。
|
||
|
||
$problems
|
||
|
||
收工前擇一處理(**一句話交代也算,別默默留著**):
|
||
1. 該推就推 → Gitea 直接 push(不需開閘);GitHub 要 leo 跑 scripts/github-arm.sh
|
||
2. 還不該推 → 在回覆裡**明說**「X 先不推,因為 Y」,讓 leo 知道有東西在路上
|
||
3. 是暫存/產物不該進版控 → 加 .gitignore 或刪掉,別讓它每次都來吵
|
||
|
||
⚠️ 特別注意「**從未推過**」的分支:那是最容易變成失蹤程式碼的一種
|
||
(07-30 t143-t150 十筆、08-02 fix/cis-round3-install 都是這樣被發現的)。
|
||
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
🔀 **散落分支怎麼處理**(leo 2026-08-05:「驗過就要 merge⋯⋯無明顯理由不 merge 就要整理」)
|
||
|
||
**它害過什麼**:D36 授權修補(07-29)躺在 fix/cis-round3-install,feat/daemon 沒合併
|
||
⇒ 收卡端全 401、**新卡整整一週一張都沒寫進 youlin**,還害人往「token 過期」查錯方向。
|
||
**分支沒合併不是整潔問題,是會產生「修好了卻沒生效」的假象。**
|
||
|
||
每條散落分支擇一,**不准放著不管**:
|
||
A. **驗過了 → 合併**(這是預設):git merge 該分支,或 cherry-pick 需要的 commit
|
||
B. **還沒驗完 → 說明它在等什麼**,並在回覆裡標「等 X 才能併」
|
||
C. **已作廢 → 刪掉**:git branch -D 該分支(留著只會讓下一個人以為它有效)
|
||
|
||
📌 判準是「**這條分支有沒有當前分支缺的東西**」,不是分支數量。
|
||
合併前先看它改了什麼:git log --oneline 當前..該分支、
|
||
git diff 當前...該分支 --stat
|
||
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
🎯 CP 對帳(leo 2026-08-02:「**現在有 CP 要求到最終用戶會看到更新,
|
||
但還是會出現 commit 沒推,這表示完全忽略了 CP**」)
|
||
|
||
CRITICAL-PATH.md 早就寫死:
|
||
· 狀態只有三種:✅ 通(**附實測證據**)/◐ 半通(標明缺什麼)/❌ 斷
|
||
· **「程式碼寫完了」不是狀態**
|
||
· code 寫完沒部署,不准標 ✅
|
||
|
||
規則寫在那裡卻照樣違反,因為**自評的人跟做事的人是同一個**。所以在這裡問你:
|
||
|
||
Q1 這回合改的東西,服務的是哪一條 CP 的哪一步?
|
||
Q2 那一步現在是 ✅ / ◐ / ❌?**上面列出的未推項目,就是它還不能算 ✅ 的證據**
|
||
Q3 最終用戶現在去看,會看到這次的改動嗎?
|
||
看不到 → 它是 ◐,**不准在回覆裡寫「完成」「修好了」**,要寫「已改,未送達」
|
||
|
||
📌 推完 ≠ 用戶拿得到——中間夾 CDN/bundle/實例快取時,
|
||
必須從**用戶端的網址、用用戶端的參數形式**再驗一次
|
||
(08-02 實例:GitHub 推對了,jsDelivr @main 仍吐三代前的 manifest)。
|
||
EOF
|
||
exit 2
|