5d938f5d3a
gitea-arm-to-github-armed.sh 原本用腳本自己的上一層決定 .github-armed 寫在哪;
plugin 發佈後那是 ~/.claude/plugins/cache/inkstone/isep/<ver>/,
於是保險寫進快取、印「已解」、ship.mjs 與各閘讀不到(arcrun-rag#202 c7598)。
改寫到 gitea_arm_proj_dir(${CLAUDE_PROJECT_DIR:-$(pwd)}),與
main-and-prod-push-guard/github-contact-guard/stage-before-prod-guard 讀的位置、
以及 check 自己讀 token/待核請求的位置一致。擋下時印的 request 路徑改絕對路徑。
測試:hooks/tests/gitea-arm-to-github-armed.test.sh 18/18;修之前 11 條紅。
TESTING.md 補 A42。版本待總管定。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
128 lines
7.6 KiB
Bash
128 lines
7.6 KiB
Bash
#!/usr/bin/env bash
|
||
# gitea-arm-to-github-armed.sh 的迴歸測試——**保險寫在哪,讀的人就要在哪讀得到**。
|
||
#
|
||
# 🔴 它在守什麼(inkstone/ISEP#156,2026-09-17 實撞,inkstone/arcrun-rag#202):
|
||
# ISEP 以 plugin 形式裝在 `~/.claude/plugins/cache/inkstone/isep/<ver>/`,
|
||
# 舊版用 `ROOT=$(dirname $0)/..` 決定 `.github-armed` 寫在哪
|
||
# ⇒ 保險被寫進 **plugin 快取資料夾**;而出貨線(ship.mjs → d20-guard.mjs)
|
||
# 與每一支讀它的閘(main-and-prod-push-guard/github-contact-guard/stage-before-prod-guard)
|
||
# 讀的都是**專案根**(`${CLAUDE_PROJECT_DIR:-$(pwd)}`)
|
||
# ⇒ leo 在票上回了碼、腳本印「✅ 保險已解」,出貨照樣卡住。
|
||
# **比單純失敗更糟:它對 leo 說謊。**
|
||
#
|
||
# 判準是「要求某個東西在場」:保險檔必須出現在讀的人會讀的那個位置,
|
||
# 並且**真的被那些讀的人認得**(不是只驗檔案存在)。
|
||
#
|
||
# 全程離線:`gitea-arm-check.sh` 的核准本身(Leo 真的回覆)必須打真的 Gitea,
|
||
# 那部分由 gitea-arm-check.test.sh 與 inkstone/InkStoneCo#34 的人工實測負責。
|
||
# 這裡把「核准成功」換成 stub,**只驗核准之後保險落在哪、被不被認得**;
|
||
# fail-closed 那條則用**真的** check(無待核請求 → 本地就擋,不碰網路)。
|
||
#
|
||
# 用法:bash hooks/tests/gitea-arm-to-github-armed.test.sh .
|
||
set -u
|
||
REPO="$(cd "${1:?用法: $0 <ISEP repo 根目錄>}" && pwd)"
|
||
SRC="$REPO/scripts/gitea-arm-to-github-armed.sh"
|
||
[ -f "$SRC" ] || { echo "❌ 找不到 $SRC(參數要傳 ISEP repo 根目錄)"; exit 1; }
|
||
|
||
T=$(mktemp -d)
|
||
trap '[ -n "${KEEP:-}" ] && echo "保留 $T" || rm -rf "$T"' EXIT
|
||
|
||
# ── 造一個長得像 plugin 快取的安裝位置,與一個長得像 InkStoneCo 的專案根 ──
|
||
PLUG="$T/.claude/plugins/cache/inkstone/isep/9.9.9"
|
||
PROJ="$T/InkStoneCo"
|
||
mkdir -p "$PLUG/scripts/lib" "$PROJ" "$T/elsewhere"
|
||
cp "$SRC" "$PLUG/scripts/"
|
||
cp "$REPO/scripts/lib/gitea-arm-common.sh" "$PLUG/scripts/lib/"
|
||
cp "$REPO/scripts/gitea-arm-request.sh" "$PLUG/scripts/" 2>/dev/null
|
||
# stub:扮演「leo 已在票上核准」——輸出格式照 gitea-arm-check.sh 成功時的 `ARMED: <mission>`
|
||
cat > "$PLUG/scripts/gitea-arm-check.sh" <<'STUB'
|
||
#!/bin/sh
|
||
[ "${1:-}" = "--consume" ] || { echo "stub: 應該帶 --consume" >&2; exit 1; }
|
||
echo "ARMED: 測試出貨 ISEP#156"
|
||
STUB
|
||
chmod +x "$PLUG/scripts/"*.sh
|
||
|
||
PASS=0; FAIL=0
|
||
ok(){ echo " ✅ $1"; PASS=$((PASS+1)); }
|
||
ng(){ echo " ❌ $1"; FAIL=$((FAIL+1)); }
|
||
chk(){ if eval "$2"; then ok "$1"; else ng "$1"; fi; }
|
||
|
||
echo "── ① 從 plugin 路徑執行,CLAUDE_PROJECT_DIR 指向專案根(總管 session 的實際形狀)──"
|
||
( cd "$T/elsewhere" && CLAUDE_PROJECT_DIR="$PROJ" sh "$PLUG/scripts/gitea-arm-to-github-armed.sh" ) >"$T/out1" 2>&1
|
||
rc=$?
|
||
chk "離開碼 0" '[ $rc -eq 0 ]'
|
||
chk "★ 專案根出現 .github-armed" '[ -f "$PROJ/.github-armed" ]'
|
||
chk "★ plugin 快取資料夾裡沒有 .github-armed(09-17 實撞的位置)" '[ ! -e "$PLUG/.github-armed" ]'
|
||
chk "cwd 也沒有被寫(不是靠碰巧站對位置)" '[ ! -e "$T/elsewhere/.github-armed" ]'
|
||
chk "印出的「提前上保險」路徑就是真正寫下的那份" 'grep -qF "rm $PROJ/.github-armed" "$T/out1"'
|
||
chk "第 1 行是未來的到期 epoch" '[ "$(sed -n 1p "$PROJ/.github-armed")" -gt "$(date +%s)" ]'
|
||
chk "第 2 行帶任務說明" '[ "$(sed -n 2p "$PROJ/.github-armed")" = "測試出貨 ISEP#156" ]'
|
||
|
||
echo "── ② 讀的人真的認得這份保險(不是只驗檔案存在)──"
|
||
# ②a 出貨線:ship.mjs 的 prod --confirm preflight 呼叫的就是 d20-guard.mjs 的 checkArmed(INKSTONE_ROOT)
|
||
D20=""
|
||
for c in "${INKSTONE_ROOT:-}" "$REPO/../InkStoneCo" "$REPO/../../InkStoneCo" "$HOME/Documents/tech_projects/InkStoneCo"; do
|
||
[ -n "$c" ] && [ -f "$c/products/arcrun-rag/installer/scripts/d20-guard.mjs" ] \
|
||
&& { D20="$(cd "$c" && pwd)/products/arcrun-rag/installer/scripts/d20-guard.mjs"; break; }
|
||
done
|
||
if [ -n "$D20" ] && command -v node >/dev/null 2>&1; then
|
||
node --input-type=module -e "
|
||
const { checkArmed } = await import(process.argv[1]);
|
||
const r = checkArmed(process.argv[2]);
|
||
console.log('checkArmed OK mission=' + r.mission);
|
||
" "$D20" "$PROJ" >"$T/out2" 2>&1
|
||
rc=$?
|
||
chk "★ ship.mjs 的 checkArmed(專案根) 認得($(head -1 "$T/out2"))" '[ $rc -eq 0 ]'
|
||
node --input-type=module -e "
|
||
const { checkArmed } = await import(process.argv[1]);
|
||
checkArmed(process.argv[2]);
|
||
" "$D20" "$PLUG" >/dev/null 2>&1
|
||
rc=$?
|
||
chk "對照:checkArmed(plugin 快取) 讀不到 ⇒ 上一條有鑑別力" '[ $rc -ne 0 ]'
|
||
else
|
||
echo " ⏭️ SKIP 找不到 arcrun-rag 的 d20-guard.mjs 或 node(不算失敗,也不算驗過)"
|
||
fi
|
||
|
||
# ②b 閘:同一個位置(`${CLAUDE_PROJECT_DIR:-$(pwd)}/.github-armed`)
|
||
mk(){ python3 -c "import json,sys;print(json.dumps({'tool_name':'Bash','tool_input':{'command':sys.argv[1]},'cwd':sys.argv[2]}))" "$1" "$T/elsewhere"; }
|
||
gate(){ # gate <hook> <cmd> <CLAUDE_PROJECT_DIR> → 回 hook 離開碼
|
||
mk "$2" | ( cd "$T/elsewhere" && env -u CLAUDE_CODE_CHILD_SESSION CLAUDE_PROJECT_DIR="$3" bash "$1" ) >/dev/null 2>&1
|
||
}
|
||
# 閘會寫 github-contact-log/狀態目錄——一律寫進暫存的專案根,不碰真的 InkStoneCo
|
||
mkdir -p "$PROJ/system-dev/docs/3-specs/autonomy-dispatch" "$T/empty/system-dev/docs/3-specs/autonomy-dispatch"
|
||
WR="npx wrangler deploy"
|
||
GH="git push https://github.com/example/example.git main"
|
||
gate "$REPO/hooks/main-and-prod-push-guard.sh" "$WR" "$PROJ"; rc=$?
|
||
chk "★ main-and-prod-push-guard:保險在專案根 ⇒ wrangler deploy 放行" '[ $rc -ne 2 ]'
|
||
gate "$REPO/hooks/main-and-prod-push-guard.sh" "$WR" "$T/empty"; rc=$?
|
||
chk "對照:沒有保險 ⇒ 同一條被擋" '[ $rc -eq 2 ]'
|
||
gate "$REPO/hooks/github-contact-guard.sh" "$GH" "$PROJ"; rc=$?
|
||
chk "★ github-contact-guard:保險在專案根 ⇒ push github 放行" '[ $rc -ne 2 ]'
|
||
gate "$REPO/hooks/github-contact-guard.sh" "$GH" "$T/empty"; rc=$?
|
||
chk "對照:沒有保險 ⇒ 同一條被擋" '[ $rc -eq 2 ]'
|
||
|
||
echo "── ③ 沒設 CLAUDE_PROJECT_DIR(leo 在終端機)⇒ 跟閘一樣退回 cwd ──"
|
||
rm -f "$PROJ/.github-armed"
|
||
( cd "$PROJ" && env -u CLAUDE_PROJECT_DIR sh "$PLUG/scripts/gitea-arm-to-github-armed.sh" ) >"$T/out3" 2>&1
|
||
rc=$?
|
||
chk "離開碼 0" '[ $rc -eq 0 ]'
|
||
chk "寫在 cwd(專案根),不在 plugin 快取" '[ -f "$PROJ/.github-armed" ] && [ ! -e "$PLUG/.github-armed" ]'
|
||
|
||
echo "── ④ fail-closed:沒拿到核准就一個檔都不寫(用真的 check,不碰網路)──"
|
||
rm -f "$PROJ/.github-armed" "$PLUG/.github-armed"
|
||
cp "$REPO/scripts/gitea-arm-check.sh" "$PLUG/scripts/gitea-arm-check.sh"
|
||
printf 'GITEA_TOKEN_CLAUDE_CODE=test-token-not-real\n' > "$PROJ/.env"
|
||
( cd "$T/elsewhere" && CLAUDE_PROJECT_DIR="$PROJ" sh "$PLUG/scripts/gitea-arm-to-github-armed.sh" ) >"$T/out4" 2>&1
|
||
rc=$?
|
||
chk "沒有待核請求 ⇒ 離開碼非 0" '[ $rc -ne 0 ]'
|
||
chk "專案根與 plugin 快取都沒有 .github-armed" '[ ! -e "$PROJ/.github-armed" ] && [ ! -e "$PLUG/.github-armed" ]'
|
||
# 出路要走得通:訊息印的那支 request 腳本必須是真的存在的絕對路徑
|
||
# (相對的 `scripts/…` 會對貼上那個人的 cwd 解析,站在 InkStoneCo 會叫到舊複本——ISEP#112 同一個病)
|
||
# 取「本檔自己印的」那一行(⛔ 之後);中文冒號不是空白,所以用「⛔ 之後第一個以 / 開頭的 token」
|
||
REQ=$(sed -n '/⛔/,$p' "$T/out4" | grep -oE '[^[:space:]:]*gitea-arm-request\.sh' | grep '^/' | head -1)
|
||
chk "★ 出路印的是存在的絕對路徑(印出:${REQ:-無})" '[ -n "$REQ" ] && [ -f "$REQ" ]'
|
||
|
||
echo
|
||
echo "通過 $PASS 條,失敗 $FAIL 條"
|
||
[ "$FAIL" -eq 0 ]
|