Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 736a601589 | |||
| 1b64f3cc4e | |||
| fa2f76177e | |||
| 950c3e1919 | |||
| a671948fa5 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "isep",
|
||||
"description": "InkStone Environment Plugin —— leo 的 Claude Code 環境唯一真相源:48 支機械閘(59 條註冊,白話盤點見 docs/hooks-inventory.md)、7 支 slash command、2 支 skill、27 支腳本,外加治理規範與標籤真相源。本機與雲端裝同一份,沒有子集。",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.1",
|
||||
"keywords": [
|
||||
"inkstone",
|
||||
"guardrails",
|
||||
|
||||
@@ -7,3 +7,12 @@ cloud-env*.txt
|
||||
# import 共用零件時 python 自己生的,不是原始碼
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# 推 main 被擋下時留的請求(main-and-prod-push-guard.sh 寫的)。
|
||||
# 這是**本機當下的狀態**,不是原始碼:一份檔案的語意是「有一筆推 main 正在等總管裁」。
|
||||
# 進版控會有兩個後果(inkstone/ISEP#59 comment 4779 第 ① 條,兩個都實際發生過):
|
||||
# ① `git add -A` 把它帶進 commit ⇒ 別人 clone 下來就多一筆**別人的**待裁決
|
||||
# ② 被追蹤的檔每次被覆寫都變成 ` M` ⇒ `git status` 永遠是髒的
|
||||
# (寫成 `目錄/*` 而不是 `目錄/`:後者連目錄本身都排除,下一行的例外就永遠生效不了)
|
||||
pending-main-push/*
|
||||
!pending-main-push/README.md
|
||||
|
||||
+71
-4
@@ -14,7 +14,9 @@
|
||||
> 🔴 **這兩個數字上一版是錯的(2026-08-26 實際數過才發現)**:本頁原本寫「43 個檔、53 條註冊」,
|
||||
> 而當時真實是 **45 個檔、55 條註冊**——中間有兩支閘進來時沒有回頭改這裡。
|
||||
> 現在的寫法是實際數出來的:
|
||||
> `ls hooks/*.sh | wc -l` = 48;`grep -c '"command"' hooks/hooks.json` = 59。
|
||||
> `ls hooks/*.sh | wc -l` = 48;`grep -c '"command":' hooks/hooks.json` = 59。
|
||||
> ⚠️ **冒號不能省**:`grep -c '"command"'`(沒冒號)會連 `"type": "command"` 一起數到,回 **118**。
|
||||
> 本頁 2026-08-27 之前寫的是沒冒號那版——**照著它跑會拿到一個跟本頁不符的數字**。
|
||||
> **一份會偷偷過期的盤點表,跟沒有盤點表差不多**——見本頁最後「怎麼跟實況對帳」那段。
|
||||
>
|
||||
> 📌 **`0.6.0`(`inkstone/ISEP#59`,2026-08-27)這兩個數字是併完之後當場數出來的**:
|
||||
@@ -23,6 +25,10 @@
|
||||
> 加上 `ISEP#72` 的 `reply-identity-guard.sh` 已在 main 上。
|
||||
> 一加一減之後檔數不變、註冊條數落在 59——**這正是「不能靠加減推算、只能實數」的例子**:
|
||||
> 上一版寫 58,就是把兩批改動的加減兜出來、沒有真的數過。
|
||||
>
|
||||
> 🔴 **`0.6.1`(2026-08-27)補回表格漏掉的兩列**:`isep-presence-beacon.sh`(E 組)、
|
||||
> `milestone-due-guard.sh`(A 組)。**標頭寫 48 支的同時,下面的表格只列得出 46 支**
|
||||
> ——數字是對的,人話少了兩行。發現的方法與防它再犯的指令,見下面「落差偵測」第二組 `comm`。
|
||||
|
||||
---
|
||||
|
||||
@@ -51,6 +57,7 @@
|
||||
| `leo21c-write-guard.sh` | AI 想寫東西到你私人帳號 `leo21c` 就擋下——這個帳號只准你自己用,AI 的實驗一律走別的測試帳號。 | 🛑 擋 |
|
||||
| `release-tag-guard.sh` | AI 想打版本 tag,但 `plugin.json` 裡寫的版本號跟 tag 對不上就擋下——避免「這裡說 0.2.0、那裡說 0.1.0」各說各話。 | 🛑 擋 |
|
||||
| `ticket-api-bypass-guard.sh` | AI 想繞過 `scripts/ticket` 工具、直接打 API 新增 Gitea 東西(開新票/milestone/label/PR,會漏掉「新增前先搜過」這道檢查)就擋下——不管是隱式 POST(urllib 傳 data= 沒寫 method)還是小寫 `requests.post(...)`。 | 🛑 擋 |
|
||||
| `milestone-due-guard.sh` | AI 想開一個新的里程碑(milestone)卻沒填期限就擋下,**填 `9999-01-01` 一樣擋**——那是「沒有期限」穿了一件期限的衣服,盤點時每一格看起來都有值,其實一格都沒有。只管「建里程碑」這個動作,查詢/改別的欄位都放行。 | 🛑 擋 |
|
||||
| `reply-identity-guard.sh` | 直接打 Gitea API 貼留言到票上時,內文第一行沒有 `【身份】`(`總管`/`subagent`/`leo`)就擋下——票上多條線並行時,看不出哪一則是誰寫的。純讀取、走 `scripts/ticket` 正門、指令裡加 `reply-identity-ok` 都放行。 | 🛑 擋 |
|
||||
| `comment-carries-task-guard.sh` | AI 要在票裡留一則「等某某上線才驗得了」這種**還沒做完的事**,卻沒有把它開成一張子票時,擋一次。留言沒人會回頭讀,票的 open/closed 撈一次就看得到。 | 🛑 擋(同一輪只擋一次) |
|
||||
|
||||
@@ -103,6 +110,7 @@
|
||||
|
||||
| 閘名 | 對你意味著什麼 | 動作 |
|
||||
|---|---|---|
|
||||
| `isep-presence-beacon.sh` | 對話一開始印一行 `🟢 ISEP vX.Y.Z 已載入(N 支閘|來源:…)`。**這行不是裝飾,是唯一能證明「這個 session 真的有閘」的東西**——它自己就住在 plugin 裡,看得到它就表示 plugin 載入了;某個 session 從頭到尾沒有這行,那個 session 是零閘狀態,先修 plugin 再做事。同一台機器可能同時有兩份 ISEP(marketplace 裝的、repo 裡 vendor 的),所以那行會講出這次是哪一份在說話。 | 📝 記錄(context 注入) |
|
||||
| `session-start-recall.sh` | 對話一開始就自動把「全局現況」(Gitea 各 repo 的票、KBDB 的藏書地圖)推到 AI 眼前,不必等它自己想到要查。 | 📝 記錄(context 注入) |
|
||||
| `skill-deploy-drift-guard.sh` | 如果「全機真正在用的 skill」跟「repo 裡版控的正本」內容對不上,就在開場講出來——避免用著一份沒人知道已經跟正本分家的舊拷貝。 | 📝 記錄 |
|
||||
|
||||
@@ -177,7 +185,62 @@ $ comm -13 /tmp/fs_hooks.txt /tmp/registered_hooks.txt
|
||||
(無輸出)
|
||||
```
|
||||
|
||||
**這次順便抓到的另一個落差**:`.claude-plugin/plugin.json` 的說明文字寫「42 支機械閘(52 條註冊)」,
|
||||
🔴 **上面那兩行驗不出「表格漏列」**——它們比的是 `hooks.json`,不是這張表。
|
||||
「有閘、但這頁沒有它那一行人話」要另外一組指令才看得見,就是下面這組
|
||||
(2026-08-27 就是靠它抓到本頁漏了 `isep-presence-beacon.sh`/`milestone-due-guard.sh` 兩列):
|
||||
|
||||
```
|
||||
$ ls hooks/*.sh | xargs -n1 basename | sort > /tmp/inv_fs.txt
|
||||
$ grep -oE '^\| `[a-zA-Z0-9_.-]+\.sh`' docs/hooks-inventory.md \
|
||||
| grep -oE '[a-zA-Z0-9_.-]+\.sh' | sort -u > /tmp/inv_table.txt
|
||||
|
||||
# 有這支閘、但這頁沒有它那一列 → 要補人話
|
||||
$ comm -23 /tmp/inv_fs.txt /tmp/inv_table.txt
|
||||
(無輸出)
|
||||
|
||||
# 這頁列了、但檔案已經不在了 → 要刪那一列
|
||||
$ comm -13 /tmp/inv_fs.txt /tmp/inv_table.txt
|
||||
(無輸出)
|
||||
```
|
||||
|
||||
🔴 **`^\| \`` 這個錨點不能拿掉**——它的意思是「**這一行的第一欄就是這支閘**」,
|
||||
也就是「表格真的列了它」。把錨點拿掉、改成抓全頁所有反引號裡的 `*.sh`,
|
||||
反向那半會吐出 **3 個假警報**(2026-08-27 實跑):
|
||||
|
||||
```
|
||||
claim-verify-police.sh ← 出現在「已於 ISEP#60 移除」那段**移除紀錄**裡
|
||||
subagent-claim-worksheet.sh ← 同上
|
||||
github-arm.sh ← 它在 `scripts/`,不在 `hooks/`;是 github-contact-guard
|
||||
那一列裡告訴你「怎麼解鎖」的逃生口
|
||||
```
|
||||
|
||||
**三個都不是幽靈,三個都不該刪**:刪掉前兩個等於刪掉「G 組為什麼是空的」這段歷史,
|
||||
刪掉第三個等於讓那一列不再告訴你怎麼過閘。
|
||||
⇒ **0 次正確攔截、3 次誤攔**——照這個訊號去改,會把好東西改壞。
|
||||
|
||||
那「頁面上出現了一個根本不存在的檔名」怎麼辦?**那要另外一格量,而且判準不是文字是事實**:
|
||||
|
||||
```
|
||||
# C. 頁面提到的每個 *.sh,現在存在,或者 git 歷史裡存在過
|
||||
$ for n in $(grep -oE '`[a-zA-Z0-9._-]+\.sh`' docs/hooks-inventory.md | tr -d '`' | sort -u); do
|
||||
[ -f "hooks/$n" ] || [ -f "scripts/$n" ] && continue
|
||||
[ -n "$(git log --all --oneline --diff-filter=AD -- "hooks/$n" "scripts/$n")" ] && continue
|
||||
echo "$n"
|
||||
done
|
||||
(無輸出)
|
||||
```
|
||||
|
||||
**「現在沒有、但曾經有」=歷史,合法;「從來沒有過」=打錯字或憑空捏造,才是幽靈。**
|
||||
這一格問的是 git,不是問這頁的用字,所以它不會因為誰換個寫法就失效。
|
||||
|
||||
📌 **三格都要跑,而且它們量的是不同東西**:
|
||||
A1 缺人話、A2 表格有幽靈、C 全頁有幽靈。
|
||||
**標頭的 48/59 對不代表這頁沒漏**——補進 2 支真的、同時留著 2 支假的,數字照樣湊得到 48。
|
||||
**數量相等不等於集合相等**,所以驗的一律是集合(`comm`),不是 `wc -l`。
|
||||
|
||||
**這次順便抓到的另一個落差**(🕐 **這一段是 2026-08-20 的快照,裡面每個數字都是那天的,不是現況**——
|
||||
留著是因為它是「兩份文件的數字不會自己跟上」這個病的第一個案例;現況數字看本頁標頭):
|
||||
`.claude-plugin/plugin.json` 的說明文字寫「42 支機械閘(52 條註冊)」,
|
||||
`README.md` 也寫「42 支 + `hooks.json`」「共 52 條註冊」——但實測是 **43 支檔案、53 條註冊**。
|
||||
差 1 支、差 1 條,猜測是今天(2026-08-20)新增的 `leo21c-write-guard.sh`/`release-tag-guard.sh`/
|
||||
`ticket-api-bypass-guard.sh` 這批(檔頭日期都是今天)加了之後,兩份文件的數字沒有跟著更新。
|
||||
@@ -232,12 +295,16 @@ $ comm -13 /tmp/fs_hooks.txt /tmp/registered_hooks.txt
|
||||
- **`micromanage-guard.sh`/`irreversible-dispatch-guard.sh`/`no-ticket-no-dispatch.sh` 的雙重矩比對(`Task` + `Agent`)是刻意保險還是舊名稱沒清掉**——見上面「重複掛載」段落,我沒有把握單靠讀檔案判斷,需要看實際觸發紀錄或問總管。
|
||||
- **`stage-before-prod-guard.sh` 跟 `main-and-prod-push-guard.sh` 的分工邊界**:`main-and-prod-push-guard.sh` 的檔頭明講自己是在「補 `stage-before-prod-guard.sh` 的破口」(那支只認 3 個關鍵字,抓不到 `wrangler deploy`),但兩支都還掛著、都還在管「prod 出貨」這件事。這是「新的補洞、舊的continua」還是「舊的該退休了」,這份盤點表不下判斷,留給你在下一步的分類會議裡定奪。
|
||||
|
||||
除了以上兩點,其餘 41 支的行為都能從檔頭與源碼直接讀出,沒有「猜」的部分。
|
||||
除了以上兩點,其餘各支的行為都能從檔頭與源碼直接讀出,沒有「猜」的部分。
|
||||
|
||||
> 📌 這句話原本寫「其餘 **41** 支」——那是 43 支閘那一版(`v0.2.1`)數的,之後沒有人重跑過。
|
||||
> 2026-08-27 把數字拿掉而不是改成 46:**改成 46 等於宣稱我逐支重讀過源碼,而我沒有。**
|
||||
> 要恢復一個數字,就重跑一次上面「抽驗」那件事,然後把當天的日期寫上去。
|
||||
|
||||
---
|
||||
|
||||
## 這份表怎麼跟實況對帳(半年後怎麼發現漂移)
|
||||
|
||||
1. **有沒有新閘沒被收進這張表**:跑本文「落差偵測」段落的兩行 `comm` 指令,比對 `hooks/*.sh` 的檔名清單跟這張表列出的閘名清單(不是跟 `hooks.json`,那個只驗證有沒有註冊,驗不了有沒有寫進這張人話表)。
|
||||
1. **有沒有新閘沒被收進這張表**:跑本文「落差偵測」段落**第二組**(`inv_fs` vs `inv_table` 的兩行 `comm`)**加第三格 C**,比對 `hooks/*.sh` 的檔名清單跟這張表列出的閘名清單。**不是第一組**——第一組比的是 `hooks.json`,只驗證有沒有註冊,驗不了有沒有寫進這張人話表。2026-08-27 漏掉的那兩列,第一組是綠的、第二組才紅。**抓表格列的那組 `grep` 一定要帶 `^\| \`` 錨點**,理由與拿掉之後會冒出的 3 個假警報,就寫在那一段裡。
|
||||
2. **有沒有閘的行為跟這裡寫的不一樣了**:抽幾支重新讀一次源碼,跟這裡的「一句話」對一遍——就是本文「抽驗 5 支」做的事,可以照同樣方法定期重做。
|
||||
3. **`.claude-plugin/plugin.json` 與 `README.md` 的數字**:這兩處各自寫了一次「幾支、幾條註冊」,前面已經抓到一次對不上(42/52 vs 實際 43/53)。這兩個數字沒有機制保證跟著 `hooks/` 目錄自動更新,是本表發現的第一個具體漂移案例。
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,101 @@
|
||||
# hooks/tests/lib/hook-sandbox.sh — 讓「會寫檔的閘」在測試時寫到暫存區,不寫進 repo。
|
||||
# 不是獨立掛的閘(沒進 hooks.json),給 hooks/tests 與 scripts/test-* `source` 用。
|
||||
#
|
||||
# 🔴 為什麼需要這支(inkstone/ISEP#59 comment 4779 第 ① 條,實測重演過):
|
||||
# main-and-prod-push-guard.sh 擋下「推 main」的同時,會把那次請求寫成一份
|
||||
# `<hooks 的上一層>/pending-main-push/<誰>--<repo>.md`——**那份檔案的語意是
|
||||
# 「有一筆推 main 正在等總管裁」**。而測試的測資本來就全是「推 main」,
|
||||
# 於是每跑一次測試,工作區就多/改幾筆**偽造的待裁決**:
|
||||
#
|
||||
# $ git status --short # 跑完測試之後(修之前的實測)
|
||||
# M pending-main-push/unnamed--ISEP.md ← 被測試覆寫(它是被追蹤的)
|
||||
# ?? pending-main-push/unnamed--A.md ← 跨 repo 測試建的臨時 repo
|
||||
#
|
||||
# 兩個後果,後者比較貴:
|
||||
# ① `git add -A` 很容易把它們帶進 commit(08-27 那次真的帶進去了,事後才拔掉)
|
||||
# ② **永遠在響的警報**:總管的迴圈讀這個目錄,讀到的每一筆都該是真的在等他裁。
|
||||
# 測試每跑一次就偽造一筆 ⇒ 下一筆真的請求會混在雜訊裡。
|
||||
#
|
||||
# 做法:把整個 `hooks/` 複製到暫存區再跑那支複本。閘算 `pending-main-push` 的位置
|
||||
# 靠的是 `$0` 的上一層,所以複本會寫進暫存區,**產品程式碼一行都不用改**
|
||||
# (不必為了測試在閘上多開一個「寫去哪」的開關——那種開關會變成關掉紀錄的路)。
|
||||
#
|
||||
# 用法:
|
||||
# . "$(dirname "$0")/lib/hook-sandbox.sh"
|
||||
# hook_sandbox "$REAL_HOOK" || exit 1
|
||||
# G=$HOOK_SANDBOX_HOOK # 之後一律測 $G
|
||||
# before=$(hook_sandbox_hostsum) # repo 那份 pending-main-push 的指紋
|
||||
# ... 跑測試 ...
|
||||
# hook_sandbox_assert "$before" # 回 0=乾淨且沙盒裡真的有留下請求
|
||||
|
||||
# hook_sandbox <真跡 hook 的路徑> → 建好沙盒,設三個變數,回 0/1
|
||||
# HOOK_SANDBOX_HOOK 沙盒裡那支同名 hook(之後一律測這支)
|
||||
# HOOK_SANDBOX 沙盒根(`pending-main-push` 會長在這底下)
|
||||
# HOOK_SANDBOX_HOST 真跡 repo 的 pending-main-push(拿來比對有沒有被弄髒)
|
||||
#
|
||||
# 🔴 **刻意不用 `printf` 把路徑印出來讓呼叫端 `G=$(hook_sandbox …)` 接**——
|
||||
# 命令替換跑在子殼裡,函式設的那三個變數**回不到呼叫端**。
|
||||
# 寫這支的當下就這樣寫,第一次跑的結果是:`HOOK_SANDBOX` 在呼叫端是空的
|
||||
# ⇒ hostsum 兩次都回 `NODIR`(相等)、沙盒的請求數當然是 0
|
||||
# ⇒ **第一條斷言變成「拿空的比空的」的假綠**。
|
||||
# 所以改成「設變數、不印」,而且 hook_sandbox_assert 開頭會擋空值(見下)。
|
||||
hook_sandbox() {
|
||||
_hs_real=$1
|
||||
_hs_hooks=$(CDPATH= cd -- "$(dirname -- "$_hs_real")" && pwd) || return 1
|
||||
HOOK_SANDBOX=$(mktemp -d) || return 1
|
||||
cp -R "$_hs_hooks" "$HOOK_SANDBOX/hooks" || return 1
|
||||
HOOK_SANDBOX_HOST="${_hs_hooks%/hooks}/pending-main-push"
|
||||
HOOK_SANDBOX_HOOK="$HOOK_SANDBOX/hooks/$(basename "$_hs_real")"
|
||||
[ -f "$HOOK_SANDBOX_HOOK" ] || return 1
|
||||
}
|
||||
|
||||
# hook_sandbox_hostsum → 印出真跡 repo 那份 pending-main-push 的指紋(檔名+內容)
|
||||
# 目錄不存在時印 NODIR——「本來就沒有」跟「空的」要分得出來。
|
||||
hook_sandbox_hostsum() {
|
||||
if [ -d "${HOOK_SANDBOX_HOST:-/nonexistent}" ]; then
|
||||
find "$HOOK_SANDBOX_HOST" -type f 2>/dev/null | LC_ALL=C sort | while IFS= read -r _f; do
|
||||
cksum "$_f" 2>/dev/null || printf 'UNREADABLE %s\n' "$_f"
|
||||
done
|
||||
else
|
||||
printf 'NODIR\n'
|
||||
fi
|
||||
}
|
||||
|
||||
# hook_sandbox_assert <before 的指紋> → 印出兩行結果,全過回 0
|
||||
# 🔴 兩件都要驗,只驗前者會養出「把紀錄關掉也算通過」的假綠:
|
||||
# ① repo 那份 pending-main-push 一個位元都沒動(測試沒有偽造待裁決)
|
||||
# ② 沙盒那份真的多了請求檔(**留請求這件事本身還在做**)
|
||||
hook_sandbox_assert() {
|
||||
_hs_before=$1
|
||||
# fail-closed:沙盒沒建起來(或變數沒傳到這裡)時,下面兩條會變成「空的比空的」=假綠。
|
||||
if [ -z "${HOOK_SANDBOX:-}" ] || [ -z "${HOOK_SANDBOX_HOST:-}" ]; then
|
||||
printf ' ❌ %-58s\n' "沙盒變數是空的——這兩條斷言等於沒跑,當失敗處理"
|
||||
HS_PASS=0; HS_FAIL=2; return 1
|
||||
fi
|
||||
_hs_after=$(hook_sandbox_hostsum)
|
||||
_hs_rc=0; HS_PASS=0; HS_FAIL=0
|
||||
if [ "$_hs_before" = "$_hs_after" ]; then
|
||||
printf ' ✅ %-58s\n' "repo 的 pending-main-push 沒被測試碰過"; HS_PASS=$((HS_PASS+1))
|
||||
else
|
||||
printf ' ❌ %-58s\n' "repo 的 pending-main-push 被測試寫髒了"
|
||||
printf ' before/after diff:\n'
|
||||
printf '%s\n' "$_hs_before" > "$HOOK_SANDBOX/.before"
|
||||
printf '%s\n' "$_hs_after" > "$HOOK_SANDBOX/.after"
|
||||
diff "$HOOK_SANDBOX/.before" "$HOOK_SANDBOX/.after" | sed 's/^/ /'
|
||||
_hs_rc=1; HS_FAIL=$((HS_FAIL+1))
|
||||
fi
|
||||
_hs_n=$(find "$HOOK_SANDBOX/pending-main-push" -type f 2>/dev/null | wc -l | tr -d ' ')
|
||||
if [ "${_hs_n:-0}" -gt 0 ]; then
|
||||
printf ' ✅ %-58s(%s 筆)\n' "請求有被留下來,只是留在沙盒裡" "$_hs_n"; HS_PASS=$((HS_PASS+1))
|
||||
else
|
||||
printf ' ❌ %-58s\n' "沙盒裡一筆請求都沒有——留請求的機制可能被關掉了"
|
||||
_hs_rc=1; HS_FAIL=$((HS_FAIL+1))
|
||||
fi
|
||||
return $_hs_rc
|
||||
}
|
||||
|
||||
hook_sandbox_cleanup() {
|
||||
case "${HOOK_SANDBOX:-}" in
|
||||
/*/*) rm -rf "$HOOK_SANDBOX" ;; # 只刪 mktemp 給的那種深路徑,不接受空值/根目錄
|
||||
esac
|
||||
}
|
||||
@@ -9,10 +9,21 @@
|
||||
# 路徑務必給絕對路徑——測試會 cd 進臨時建立的 A/B repo 再呼叫它,相對路徑
|
||||
# 到那時就對不上了(自己撞過一次:exit=127 command not found)。
|
||||
set -u
|
||||
G="$1"
|
||||
REAL="$1"
|
||||
|
||||
# 🔴 測沙盒裡的複本,不測真跡:這支閘擋下推 main 時會把請求寫進
|
||||
# `<repo>/pending-main-push/`,而這裡每一條測資都是推 main
|
||||
# ⇒ 直接測真跡,每跑一次就在 ISEP 的工作區偽造幾筆「還沒裁」
|
||||
# (實測留下過 `pending-main-push/unnamed--A.md`——A 是這支自己建的臨時 repo,
|
||||
# inkstone/ISEP#59 comment 4779 第 ① 條)。收尾會驗兩件(見 lib 檔頭)。
|
||||
. "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/lib/hook-sandbox.sh"
|
||||
hook_sandbox "$REAL" || { echo "❌ 沙盒建不起來"; exit 1; }
|
||||
G=$HOOK_SANDBOX_HOOK
|
||||
HOSTSUM_BEFORE=$(hook_sandbox_hostsum)
|
||||
|
||||
STAMP=/tmp/.main-push-ok
|
||||
WORK=$(mktemp -d)
|
||||
trap 'rm -rf "$WORK"; rm -f "$STAMP"' EXIT
|
||||
trap 'rm -rf "$WORK"; rm -f "$STAMP"; hook_sandbox_cleanup' EXIT
|
||||
|
||||
for d in A B; do
|
||||
git init -q -b main "$WORK/$d"
|
||||
@@ -101,5 +112,9 @@ touch -t "$(date -v-16M +%Y%m%d%H%M.%S 2>/dev/null || date -d '-16 minutes' +%Y%
|
||||
t "16 分鐘前開的戳記 → 已過期,推 B 應擋" "$A" "cd $B && git push origin HEAD:main" 2
|
||||
rm -f "$STAMP"
|
||||
|
||||
echo "── 測試自己不准弄髒工作區(inkstone/ISEP#59)──"
|
||||
hook_sandbox_assert "$HOSTSUM_BEFORE"
|
||||
pass=$((pass+HS_PASS)); fail=$((fail+HS_FAIL))
|
||||
|
||||
echo "────── 通過 $pass / 失敗 $fail"
|
||||
[ "$fail" = 0 ]
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# 八向實測 main-and-prod-push-guard.sh
|
||||
# 放在檔案裡跑,因為測試字串本身會觸發「舊版」那支閘(第五次誤攔)。
|
||||
G="$1" # 要測的 hook 路徑
|
||||
REAL="$1" # 要測的 hook 路徑(真跡)
|
||||
|
||||
# 🔴 不要直接測真跡:這支閘擋下推 main 的同時會把請求寫進 `<repo>/pending-main-push/`,
|
||||
# 而這裡的測資全是推 main ⇒ 每跑一次就在工作區偽造幾筆「還沒裁」(inkstone/ISEP#59)。
|
||||
# 改測沙盒裡的複本,閘照原樣跑,請求寫進暫存區。收尾會驗兩件(見 lib 檔頭)。
|
||||
. "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/lib/hook-sandbox.sh"
|
||||
hook_sandbox "$REAL" || { echo "❌ 沙盒建不起來"; exit 1; }
|
||||
G=$HOOK_SANDBOX_HOOK
|
||||
trap 'hook_sandbox_cleanup' EXIT
|
||||
HOSTSUM_BEFORE=$(hook_sandbox_hostsum)
|
||||
|
||||
pass=0; fail=0
|
||||
|
||||
t() { # t <說明> <指令> <期望 exit>
|
||||
@@ -25,5 +35,9 @@ t "直接推預設分支" 'git push origin ma'"in" 2
|
||||
t "HEAD:預設分支" 'git push origin HEAD:ma'"in" 2
|
||||
t "推 master" 'git push -q origin mas'"ter" 2
|
||||
|
||||
echo "── 測試自己不准弄髒工作區(inkstone/ISEP#59)──"
|
||||
hook_sandbox_assert "$HOSTSUM_BEFORE"
|
||||
pass=$((pass+HS_PASS)); fail=$((fail+HS_FAIL))
|
||||
|
||||
echo "────── 通過 $pass / 失敗 $fail"
|
||||
[ "$fail" = "0" ]
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# `pending-main-push/` — 被擋下的推 main 請求(本機狀態,不進版控)
|
||||
|
||||
`hooks/main-and-prod-push-guard.sh` 擋下一次推 main 的同時,會把那次請求寫成
|
||||
`<誰>--<哪個 repo>.md` 丟進這個目錄:repo/分支/逐筆 commit/改了哪些檔,**原始資料**。
|
||||
|
||||
> leo 2026-08-12:「**它會問你的意見,所以每個你叫起來的 subagent 都有名字。**」
|
||||
> 閘做不到「同步問總管」(不同行程),做得到的是「**當場擋 + 留下是誰要求的**」。
|
||||
|
||||
## 規約
|
||||
|
||||
- **一份檔案 = 一筆還沒裁的請求。** 總管裁完就刪掉它——留著代表還沒裁。
|
||||
- **這個目錄的內容不進版控**(`.gitignore` 有一段寫了為什麼),只有本檔在版控裡。
|
||||
- **測試不准在這裡留東西。** 推 main 的測資本來就都會觸發這支閘,
|
||||
所以那幾支測試改成跑 `hooks/tests/lib/hook-sandbox.sh` 建的複本,請求寫進暫存區;
|
||||
每支測完都會驗兩件:這裡沒被碰過、而且沙盒裡**真的有**留下請求
|
||||
(只驗前者的話,「把留紀錄的功能關掉」也會綠——那是假綠)。
|
||||
@@ -1,43 +0,0 @@
|
||||
# 推 main 的請求:未署名
|
||||
|
||||
- repo:/Users/youlinhsieh/Documents/tech_projects/ISEP
|
||||
- 分支:fix/push-guard-target-not-substring
|
||||
- 時間:2026-08-21 01:28:51
|
||||
|
||||
- 它想跑的指令:
|
||||
```
|
||||
git push -q origin master
|
||||
```
|
||||
|
||||
## 還沒推上去的 commit(原始資料,不是轉述)
|
||||
|
||||
```
|
||||
43c328d Merge pull request 'feat/milestone-must-have-due' (#50) from feat/milestone-must-have-due into main
|
||||
bcb736e Merge pull request 'fix/worksheet-dedup-by-content' (#49) from fix/worksheet-dedup-by-content into main
|
||||
6772ca6 每個里程碑都要有真的期限,9999 也擋
|
||||
1b55512 待驗工作單改用宣稱內容去重,驗過的不再冒出來
|
||||
1920d4c Merge pull request '身為 leo,我要雲端 clone 下來就有閘,我才不用先處理憑證' (#48) from fix/b4-real-probe into main
|
||||
47ed778 改走「直接複製進薄殼 repo」,並修掉一支會偷跑指令的閘
|
||||
03d9782 查了官方文件才發現:setup script 根本讀不到環境變數,而 exit 1 會鎖死 session
|
||||
36d8e05 認證驗證加 timeout:掛住比失敗更糟
|
||||
1356372 B4 的探針我自己沒撞過,實撞後發現它根本不會擋
|
||||
d306158 Merge pull request '身為 leo,我要雲端 env 檔能直接產在桌面,我才不用去翻隱藏目錄' (#46) from feat/cloud-env-outdir into main
|
||||
9099c3f make-cloud-env.sh:輸出路徑可指定,且不再對使用者指定的目錄動權限
|
||||
5bceb03 v0.3.1
|
||||
4e73b8b Merge pull request '身為 leo,我要雲端驗收步驟在閘死掉時真的變紅,我才不會再被三個綠燈騙一次' (#45) from fix/testing-b-section-discriminating into main
|
||||
291787e TESTING.md B 段整段換掉——舊版在閘全滅時會回綠
|
||||
3a95121 雲端零閘的兩個真因:setup 不自驗+沒有 release 撐版本號 (#44)
|
||||
daa1674 雲端零閘的兩個真因:setup 從不驗證自己+沒有任何 release 撐版本號
|
||||
c48495d Merge pull request 'fix(hooks): sdd-guard.sh 修「解析失敗仍照擋、且訊息洩漏 /nonexistent」' (#42) from fix/sdd-guard-path-resolution into main
|
||||
8718658 fix(hooks): sdd-guard.sh 修「解析失敗仍照擋、且訊息洩漏 /nonexistent」(InkStoneCo#22)
|
||||
e6d183d Merge pull request '產生雲端 env 設定給 leo 貼(InkStoneCo#14)' (#41) from feat/cloud-env-generator into main
|
||||
f855d82 產生雲端 env 設定,不要 leo 自己拼湊(InkStoneCo#14)
|
||||
```
|
||||
|
||||
## 改了哪些檔
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
總管裁完請刪掉這個檔——留著代表「還沒裁」。
|
||||
@@ -1,23 +0,0 @@
|
||||
# 推 main 的請求:未署名
|
||||
|
||||
- repo:/Users/youlinhsieh/Documents/tech_projects/InkStoneCo
|
||||
- 分支:main
|
||||
- 時間:2026-08-21 01:28:23
|
||||
|
||||
- 它想跑的指令:
|
||||
```
|
||||
git push -q origin master
|
||||
```
|
||||
|
||||
## 還沒推上去的 commit(原始資料,不是轉述)
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
## 改了哪些檔
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
總管裁完請刪掉這個檔——留著代表「還沒裁」。
|
||||
@@ -4,7 +4,19 @@
|
||||
# 開頭包一層讀取指令)都不准擋;推自己的分支、含子字串但目標不是 main/master
|
||||
# 的分支(如 fix/custom-domain-setup)也不准擋。
|
||||
cd "$(dirname "$0")/.." || exit 1
|
||||
H=hooks/main-and-prod-push-guard.sh
|
||||
|
||||
# 🔴 測沙盒裡的複本,不測真跡(inkstone/ISEP#59 comment 4779 第 ① 條):
|
||||
# 這支閘擋下推 main 的同時會把請求寫進 `<repo>/pending-main-push/`,那份檔案的
|
||||
# 語意是「有一筆推 main 正在等總管裁」。這裡的測資本來就都是推 main
|
||||
# ⇒ 直接測真跡的話,**每跑一次測試就偽造一筆待裁決**(實測:`unnamed--ISEP.md`
|
||||
# 每跑一次被覆寫一次,而它當時還是被追蹤的 ⇒ `git status` 永遠是髒的)。
|
||||
# cwd 仍然留在 repo 根——戳記比對問的是「人站在哪個 repo」,那件事沒有變。
|
||||
. hooks/tests/lib/hook-sandbox.sh
|
||||
hook_sandbox "$PWD/hooks/main-and-prod-push-guard.sh" || { echo "❌ 沙盒建不起來"; exit 1; }
|
||||
trap 'hook_sandbox_cleanup' EXIT
|
||||
HOSTSUM_BEFORE=$(hook_sandbox_hostsum)
|
||||
H=$HOOK_SANDBOX_HOOK
|
||||
|
||||
PASS=0; FAIL=0
|
||||
run(){ # $1=want $2=cmd
|
||||
printf '%s' "{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":$(python3 -c 'import json,sys;print(json.dumps(sys.argv[1]))' "$2")}}" \
|
||||
@@ -31,6 +43,10 @@ run 0 'grep -n "git push" hooks/main-and-prod-push-guard.sh'
|
||||
echo "── 真違規不能因為這次改動而漏擋:戳記過期/戳記綁錯 repo 一樣要擋 ──"
|
||||
run 2 'git push gitea HEAD:main' # 沒有任何戳記檔時
|
||||
|
||||
echo "── 測試自己不准弄髒工作區(inkstone/ISEP#59)──"
|
||||
hook_sandbox_assert "$HOSTSUM_BEFORE"
|
||||
PASS=$((PASS+HS_PASS)); FAIL=$((FAIL+HS_FAIL))
|
||||
|
||||
echo
|
||||
echo "$PASS/$((PASS+FAIL)) 通過"
|
||||
[ "$FAIL" -eq 0 ]
|
||||
|
||||
Reference in New Issue
Block a user