3e83a4b4f3
三支閘(prod-write-guard/main-and-prod-push-guard/stage-before-prod-guard)
判斷戳記新不新,都寫成這一行:
MT=$(stat -f %m "$STAMP" 2>/dev/null || stat -c %Y "$STAMP" 2>/dev/null || echo 0)
macOS(BSD stat)上 `-f %m` 就是 mtime,對的。
**GNU coreutils 的 `-f` 是「顯示檔案系統資訊」**,而且它一邊回非零、
一邊往 stdout 吐一整段區塊 ⇒ `||` 接上的秒數被那段文字污染
⇒ 下一行的 `case "$NOW$MT" in *[!0-9]*) return 1` 必然命中
⇒ **在 Linux 上那三支閘的戳記永遠不會被接受。**
後果不是少一個便利功能:那三支閘都是「擋下來、但總管看過就能放行」,
而放行那道門在雲端打不開 ⇒ **它們在雲端等於純擋**,
總管照著閘自己印的指示做,做幾次都打不開,閘也不會告訴他門是壞的
(inkstone/InkStoneCo#99 那次「連續四次蓋不出戳記」就是這個形狀)。
· hooks/lib/mtime.sh:先 `-c %Y`(GNU)再 `-f %m`(BSD),**每一步都驗是不是純數字**
——這個 bug 的成因正是「命令失敗了卻還是印了東西」,只看離開碼會再被騙一次。
四支用到的檔各自帶一份 inline fallback:這道門不能因為少一個檔案就再關上一次。
· scripts/gate-ok:把散在各閘訊息裡的七種逃生口收斂成一個名字、一種形狀
(`gate-ok <閘名> [參數]`)。原因是逃生口原本是「臨時湊出來的 Bash 指令」,
而臨時湊的指令沒有穩定形狀可以事先放行——settings.json 的 allow 只能逐條完全比對,
多一個 `&&`、換一個 session id 就落在規則外。它蓋的戳記與閘原本認的完全同一個檔、
同一種語意(單次、綁 repo、綁 session、有效期都沒動),換掉的只有「怎麼蓋」。
測試 hooks/tests/gate-ok.test.sh:16/16。既有的三支測試(29/16/10)不受影響。
拿 main 跑同一份:②⑤(門打得開)紅、⑥⑦⑧(門沒變寬)全綠
⇒ 這支補的正是既有測試從來沒驗過的那半邊。
336 lines
19 KiB
Bash
Executable File
336 lines
19 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
|
||
|
||
# ══════════════════════════════════════════════════════════════════════
|
||
# 「遠端到底有沒有這顆 commit」——本檔唯一的判準(inkstone/ISEP#90,2026-08-28)
|
||
# ══════════════════════════════════════════════════════════════════════
|
||
#
|
||
# 🔴 舊判準是「這條分支有沒有 upstream」,而**雲端 session 開出來的工作分支
|
||
# 天生就沒有 upstream**,內容卻等於遠端 main:
|
||
#
|
||
# 薄殼 HEAD = 081c547d90dc2d80692485af083ca1f71f2094f2
|
||
# GitHub 遠端 main = 081c547d90dc2d80692485af083ca1f71f2094f2 ← 同一顆
|
||
#
|
||
# ⇒ 每個雲端 session、每次收工都被攔一次。2026-08-27 一個 session 攔五次,
|
||
# **五次全是誤報**。而永遠在響的警報=訓練人忽略這個警報
|
||
# (本檔第 ③ 段自己在 08-12 就寫過這句,這次是同一個病長在別的判準上)。
|
||
#
|
||
# 🔴 也不能改成「本機的 remote-tracking ref 有沒有含它」的變體:
|
||
# 那份 ref 會過期——08-28 實測本 session 的 `origin/main` 落後遠端整整 4 天。
|
||
# 本機只答得準「有」,答不準「沒有」⇒ 答「沒有」的時候要去問遠端。
|
||
#
|
||
# 三態,不是兩態:0=遠端有 1=遠端沒有 2=問不到(沒 remote/網路不通/沒權限)。
|
||
# **2 一律不報。** 誤攔比漏擋更該修(leo 2026-08-17:文字層那些閘 8 次誤攔、
|
||
# 0 次正確攔截)——把「離線」當成「你沒推」,就是拿雜訊懲罰謹慎。
|
||
|
||
# 🔴 戳記的 mtime 一律走 lib/mtime.sh:舊寫法(`stat -f %m || stat -c %Y`)在 Linux 上
|
||
# **永遠算不出數字**(GNU 的 `-f` 是「檔案系統資訊」,它一邊回非零一邊吐一整段文字,
|
||
# 於是後面接上的秒數被那段垃圾污染)⇒ 這支閘給總管的逃生口在雲端整個焊死,
|
||
# 而閘不會告訴他門是壞的(inkstone/ISEP#90 ④,全文見 hooks/lib/mtime.sh)。
|
||
# lib 不在時就地補一份同順序的定義——這道門不能因為少一個檔案就再關上一次。
|
||
. "$(dirname "$0")/lib/mtime.sh" 2>/dev/null || file_mtime() {
|
||
_fm=$(stat -c %Y "$1" 2>/dev/null || true)
|
||
case "${_fm:-}" in ''|*[!0-9]*) _fm=$(stat -f %m "$1" 2>/dev/null || true) ;; esac
|
||
case "${_fm:-}" in ''|*[!0-9]*) _fm=0 ;; esac
|
||
printf '%s' "$_fm"
|
||
}
|
||
|
||
GIT_ASK="env GIT_TERMINAL_PROMPT=0 GIT_ASKPASS=/bin/echo GIT_SSH_COMMAND=ssh\ -oBatchMode=yes"
|
||
_TO=""
|
||
command -v timeout >/dev/null 2>&1 && _TO="timeout 15"
|
||
command -v gtimeout >/dev/null 2>&1 && _TO="gtimeout 15"
|
||
|
||
# ls_remote_heads <repo> <remote> → 印出「<sha> <ref>」數行;問不到就印空、離開碼 1
|
||
# 快取 120 秒:Stop hook 每個回合都跑,同一個遠端不必每回合都打一次網路。
|
||
ls_remote_heads() {
|
||
_lr_repo="$1"; _lr_rm="$2"
|
||
_lr_key="$(printf '%s|%s' "$_lr_repo" "$_lr_rm" | cksum | tr -d ' /')"
|
||
# 快取位置可被 UNPUSHED_CACHE_DIR 覆寫——測試要能造「同一個 repo、遠端變了」的場,
|
||
# 不能被上一個案例的快取餵到舊答案(那會是假綠)。
|
||
_lr_cache="${UNPUSHED_CACHE_DIR:-/tmp}/.unpushed-lsremote-$_lr_key"
|
||
if [ -f "$_lr_cache" ]; then
|
||
_lr_now=$(date +%s 2>/dev/null || echo 0)
|
||
_lr_mt=$(file_mtime "$_lr_cache")
|
||
case "$_lr_now$_lr_mt" in *[!0-9]*) _lr_mt=0 ;; esac
|
||
if [ "$_lr_mt" -gt 0 ] && [ $((_lr_now - _lr_mt)) -lt 120 ]; then
|
||
[ -s "$_lr_cache" ] || return 1
|
||
cat "$_lr_cache"; return 0
|
||
fi
|
||
fi
|
||
if _lr_out="$($_TO $GIT_ASK git -C "$_lr_repo" ls-remote --heads "$_lr_rm" 2>/dev/null)" \
|
||
&& [ -n "$_lr_out" ]; then
|
||
printf '%s\n' "$_lr_out" > "$_lr_cache" 2>/dev/null || true
|
||
printf '%s\n' "$_lr_out"
|
||
return 0
|
||
fi
|
||
: > "$_lr_cache" 2>/dev/null || true # 記「這次問不到」,免得每回合都等一次 timeout
|
||
return 1
|
||
}
|
||
|
||
# remote_has_commit <repo> <sha> → 0 遠端有/1 遠端沒有/2 問不到
|
||
remote_has_commit() {
|
||
_rh_repo="$1"; _rh_sha="$2"
|
||
[ -n "$_rh_sha" ] || return 2
|
||
# ① 本機就答得出「有」:任何 remote-tracking ref 含這顆 ⇒ 一定推過,不必打網路
|
||
if [ -n "$(git -C "$_rh_repo" branch -r --contains "$_rh_sha" 2>/dev/null | head -1)" ]; then
|
||
return 0
|
||
fi
|
||
_rh_asked=0
|
||
for _rh_rm in $(git -C "$_rh_repo" remote 2>/dev/null); do
|
||
_rh_ls="$(ls_remote_heads "$_rh_repo" "$_rh_rm")" || continue
|
||
_rh_asked=1
|
||
# 遠端某條 head 就是這顆 ⇒ 推過(雲端那個誤報正是落在這一格)
|
||
printf '%s\n' "$_rh_ls" | awk '{print $1}' | grep -qx "$_rh_sha" && return 0
|
||
# 遠端那顆本機也有時,才問得出祖先關係(本機沒有就只能靠上面的等值比對)
|
||
printf '%s\n' "$_rh_ls" | while read -r _rh_rsha _; do
|
||
[ -n "$_rh_rsha" ] || continue
|
||
git -C "$_rh_repo" cat-file -e "${_rh_rsha}^{commit}" 2>/dev/null || continue
|
||
git -C "$_rh_repo" merge-base --is-ancestor "$_rh_sha" "$_rh_rsha" 2>/dev/null && exit 7
|
||
done
|
||
[ $? = 7 ] && return 0
|
||
done
|
||
[ "$_rh_asked" = 1 ] && return 1
|
||
return 2
|
||
}
|
||
|
||
# remote_sha_of <repo> <遠端名> <分支名> → 印出遠端那顆 sha(拿不到印空)
|
||
remote_sha_of() {
|
||
ls_remote_heads "$1" "$2" 2>/dev/null \
|
||
| awk -v b="refs/heads/$3" '$2 == b { print $1; exit }'
|
||
}
|
||
|
||
TOP="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}"
|
||
|
||
# 掃描對象:頂層 + 各子 repo(存在才掃)。worktree 一併涵蓋(git -C 會自己解)。
|
||
#
|
||
# 🔴 2026-08-28(inkstone/ISEP#90)補雲端那一格:雲端的 `$CLAUDE_PROJECT_DIR`
|
||
# 是**薄殼根**,真身被 bootstrap.sh clone 到 `$TOP/InkStoneCo/`,子 repo 在
|
||
# `$TOP/InkStoneCo/matrix/arcrun` …。舊清單只認地端那種「頂層就是真身」的排法
|
||
# ⇒ **雲端上真身有東西沒推,本閘一輩子不會知道**(它掃的四個路徑一個都不存在)。
|
||
# ⇒ 這跟 ① 的誤攔是同一個病的兩面:在雲端,這支閘既亂叫、又看不到該看的地方。
|
||
REPOS=""
|
||
for _body in "$TOP" "$TOP/InkStoneCo"; do
|
||
[ -d "$_body/.git" ] || [ -f "$_body/.git" ] || continue
|
||
REPOS="$REPOS $_body $_body/matrix/arcrun $_body/products/arcrun-rag $_body/polaris/mira"
|
||
done
|
||
[ -n "$REPOS" ] || REPOS="$TOP"
|
||
|
||
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 但沒推(**當前分支**)
|
||
#
|
||
unknown=""
|
||
# 🔴 判準是「遠端有沒有這顆 commit」,不是「有沒有 upstream」(ISEP#90,見檔頭那段)。
|
||
# 有 upstream 時仍照原本的算法報「領先幾筆」——那個數字有用;
|
||
# 沒 upstream 時去問遠端,問不到就**不報**。
|
||
unpushed=""
|
||
br="$(git -C "$r" branch --show-current 2>/dev/null)"
|
||
if [ -n "$br" ]; then
|
||
head_sha="$(git -C "$r" rev-parse HEAD 2>/dev/null || true)"
|
||
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)"
|
||
if [ "$n" != "0" ]; then
|
||
# upstream 本身可能過期(本機那份 ref 落後遠端 4 天是實測過的)
|
||
# ⇒ 先問遠端;遠端已經有這顆就不是「沒推」。
|
||
remote_has_commit "$r" "$head_sha"
|
||
[ "$?" = 0 ] || unpushed="領先 $up $n 筆"
|
||
fi
|
||
else
|
||
remote_has_commit "$r" "$head_sha"
|
||
case "$?" in
|
||
1) unpushed="分支 $br **從未推過**(遠端沒有 $(printf '%.7s' "$head_sha"))" ;;
|
||
# 0=遠端有這顆(雲端 session 的工作分支就是這種)→ 不報
|
||
# 2=問不到遠端 → 不報,但這件事本身值得知道,收在下面當脈絡
|
||
2) [ -n "$(git -C "$r" rev-list --count HEAD 2>/dev/null)" ] \
|
||
&& unknown="分支 $br 問不到遠端(沒有 remote/網路不通)——**不當成沒推**" ;;
|
||
esac
|
||
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"
|
||
base_label=""
|
||
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
|
||
base_label="$base"
|
||
# 🔴 2026-08-28(inkstone/ISEP#90):**基準的 ref 也會過期。**
|
||
# 08-28 實測:本機 `origin/main` 落後遠端整整 4 天 ⇒ 拿它當基準,
|
||
# 「已經在遠端 main 上」的分支會被整批報成散落——包括**雲端 session 自己
|
||
# 那條工作分支**(它的內容就等於遠端 main)。
|
||
# 08-15 那次訂正的是「基準挑錯了分支」,這次是「基準挑對了但那份是舊的」:
|
||
# 同一個病的第三面——檢查跑了,但檢查的對象是舊的。
|
||
# ⇒ 問一次遠端;拿得到、而且本機真的有那顆 commit(不然算不了祖先),才換過去。
|
||
case "$base_label" in
|
||
*/*) _base_rm="${base_label%%/*}"; _base_br="${base_label#*/}" ;;
|
||
*) _base_rm="origin"; _base_br="$base_label" ;;
|
||
esac
|
||
_base_fresh="$(remote_sha_of "$r" "$_base_rm" "$_base_br")"
|
||
if [ -n "$_base_fresh" ] && git -C "$r" cat-file -e "${_base_fresh}^{commit}" 2>/dev/null; then
|
||
base="$_base_fresh"
|
||
base_label="$_base_rm/$_base_br(遠端實際那顆 $(printf '%.7s' "$_base_fresh"))"
|
||
fi
|
||
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_label(最後動 $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 沒併回 $base_label):$stray"
|
||
# 刻意保留的分支**不會自己觸發警報**(那正是加它的目的),
|
||
# 但這個 repo 若本來就有別的問題要報,就順便把它列出來當脈絡——
|
||
# 免得「保留」變成另一種看不見。
|
||
[ -n "$line" ] && [ -n "$unknown" ] && line="$line
|
||
· ❔ $unknown"
|
||
[ -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
|