627 lines
35 KiB
Markdown
627 lines
35 KiB
Markdown
# ISEP 測試手冊
|
||
|
||
> leo 2026-08-20:「**你交出版本測試了嗎?你要測試無誤才叫我測試,
|
||
> 如果雲端不能測試也要提供 test cases 讓我開啓雲端測試**」。
|
||
>
|
||
> 規約:每一格都要有「**怎麼跑/該看到什麼/什麼算失敗**」三件。
|
||
> **沒跑過的格子一律標空白,不准標綠。**
|
||
|
||
---
|
||
|
||
## 先讀:改了 ISEP 卻沒發版,改動到不了任何人手上
|
||
|
||
2026-08-20 實撞:新增一支 hook 併進 `main`,然後跑 `claude plugin update isep@inkstone`
|
||
→ 回「**已是最新版 (0.2.0)**」,新 hook **沒有進到安裝的那一份**。
|
||
|
||
原因:`plugin update` 比的是 **`plugin.json` 的版本號,不是內容**。
|
||
⇒ **版本沒動 = 更新是 no-op = 本機與雲端又各自停在不同內容上**(就是 `InkStoneCo#57` 的病)。
|
||
|
||
**所以:任何要生效的改動,都必須跟著一個新版本號。這不是儀式,是傳輸機制本身。**
|
||
|
||
---
|
||
|
||
## A. 總管自己要跑完的(交給 leo 之前)
|
||
|
||
### A1 — plugin manifest 合法
|
||
```
|
||
claude plugin validate .
|
||
```
|
||
**該看到**:`✔ Validation passed`,不帶 warning。
|
||
**失敗**:任何 error;或有 warning 卻沒處理。
|
||
|
||
### A2 — 版本三處一致
|
||
```
|
||
bash scripts/check-version-consistency.sh
|
||
```
|
||
**該看到**:`✅ 版本一致:plugin.json=X.Y.Z,最新 tag=vX.Y.Z,README 沒有自行宣告版本。`
|
||
**失敗**:exit 1;或 README 又出現寫死的版本號。
|
||
|
||
### A3 — 打 tag 的閘:擋得住,也放得過
|
||
```
|
||
bash scripts/test-release-tag-guard.sh
|
||
```
|
||
**該看到**:`3/3 通過`(1 個該擋、2 個不該擋)。
|
||
**失敗**:該擋的放行(假綠);或不該擋的被擋——**誤攔比漏擋更該修**,誤攔會懲罰謹慎。
|
||
|
||
### A4 — 新增 Gitea 東西的側門閘:24 條
|
||
```
|
||
bash scripts/test-ticket-api-bypass-guard.sh
|
||
```
|
||
**該看到**:`24/24 通過`(前 13 條是 v1 的開票案例;後面是 inkstone/ISEP#72
|
||
擴大範圍後補的:隱式/小寫 POST、milestone/label/PR、org 端點、以及一條打
|
||
真實 Gitea 網路重演 Arcrun#100 的案例——這台機器的 remote 沒帶憑證時會印
|
||
`⏭️ SKIP`,不算失敗,但也不算驗過)。
|
||
**失敗**:任何一條不符,特別看「不該擋」那幾條——誤攔比漏擋更該修。
|
||
|
||
### A13 — 搜尋戳記要證明「看過」,不是「跑過」:17 條
|
||
```
|
||
bash scripts/test-ticket-where-seen-guard.sh
|
||
```
|
||
**該看到**:`通過 17 條,失敗 0 條`。全程離線(`TICKET_HOST` 指到連不上的位址:
|
||
離開碼 2 =被閘擋、離開碼 1 =閘全放行走到網路才炸),**不打真實 Gitea、不留測試票**。
|
||
|
||
**它在守什麼**(inkstone/ISEP#72 → comment 4873,2026-08-27 實犯):
|
||
`ticket where … >/dev/null` 之後 `ticket new`——戳記寫成功了,命中的 72 張一眼沒看,
|
||
於是開出 `arcrun-rag#147`,而第一名 `arcrun-rag#104` 是同一件事、已經開了 13 天。
|
||
|
||
**失敗**:
|
||
- 「★」那兩條紅 ⇒ 今天這個形狀會再發生一次(尤其第二條:先寫好 `--not-a-comment`
|
||
理由就能閉著眼睛開票)
|
||
- 「不該擋」3 條任一紅 ⇒ **誤攔**,這比漏擋嚴重——沒命中也吵、看過了還吵,
|
||
人就學會忽略它
|
||
- 最後那組 `fd_is_devnull` 紅 ⇒ 判準從「fstat 問得出來的事實」滑回猜文字
|
||
|
||
### A11 — 討論串裡的任務要長成子票:22 條
|
||
```
|
||
bash scripts/test-comment-carries-task-guard.sh
|
||
```
|
||
**該看到**:`22/22 通過`。
|
||
**失敗**:
|
||
- 「該擋」6 條任一紅 ⇒ 08-26 那則真的掉了的留言形狀(「等雲端那半出貨才驗得了」)會漏抓
|
||
- 「不該擋」10 條任一紅 ⇒ **誤攔,這比漏擋嚴重**——每次留言都被擋,人就學會忽略它
|
||
- 最後兩條(`-F <檔>`)紅 ⇒ 內文放在檔案裡時閘看不到,等於走 `ticket say` 就自動繞過
|
||
|
||
### A12 — 收工要把棒子交回來:10 條
|
||
```
|
||
bash scripts/test-baton-handback-guard.sh
|
||
```
|
||
**該看到**:`10/10 通過`。**用 fixture 跑,不打網路、不在票池留下測試票**。
|
||
**失敗**:
|
||
- 「該報」5 條任一紅 ⇒ 指派/tag/下一步缺哪一格抓不到,08-26 那次掉棒的狀態會靜靜通過
|
||
- 「不該報」4 條任一紅 ⇒ 每條線收工都被念一次,警報會被學會忽略
|
||
- 「票已關」那條紅 ⇒ 棒子已經到終點還在催,那是最典型的假警報
|
||
|
||
### A14 — PR 一定要有結論:52 條
|
||
```
|
||
bash hooks/tests/pr-verdict-guard.test.sh
|
||
```
|
||
**該看到**:`52 通過 / 0 失敗`。全程離線(走 `PR_VERDICT_FIXTURE`),
|
||
**不打 Gitea、不開測試 PR、不留任何測試票**;狀態檔走 `PR_VERDICT_STATE_DIR`。
|
||
|
||
**它在守什麼**(`inkstone/ISEP#81`,2026-08-27 實查):最久的三個 open PR 躺了兩星期
|
||
(`inkstone/arcrun-rag#91` 14 天、`inkstone/Arcrun#116` 14 天、`inkstone/Arcrun#104` 15 天),
|
||
而 v0.9.0 的 46 支閘**一支都沒有在管 PR**。票看起來是「已交付」,
|
||
東西卻沒進 main ⇒ 沒進版本 ⇒ **leo 手上永遠不會出現它**。
|
||
|
||
**失敗**:
|
||
- B 群(⑨⑩⑪,票上那三個真跡)任一紅 ⇒ 今天這個形狀會再發生一次
|
||
- C 群 ⑭⑰ 紅 ⇒ 驗收條件 1/3 沒過:「放著不管」抓不到,或「merge 了分支還在」放行
|
||
- **A 群任何一條紅 ⇒ 誤攔,這比漏擋嚴重**。②③特別重要:
|
||
指派給人/掛 Human 之後還在點名,就是 ISEP v0.6.0 divergence §B4 記過的那個坑
|
||
(「`s/review` 佇列非空即 block」⇒ 佇列本來就不會空 ⇒ **總管永遠停不下來**)
|
||
- E 群 ㉙ 紅 ⇒ 閘訊息裡承諾的出路(「把它指派給那個人,本閘立刻不再點名它」)是假的
|
||
- D 群 ㉓ 紅 ⇒ 判準從「識別碼比對」滑成「只要提到票號就放行」,閘會被任何一句話關掉
|
||
|
||
> 📌 **真跡不只在 fixture 裡跑過**:同一支閘打真實 Gitea 也驗過,
|
||
> 一次點名 7 個沒有結論的 PR,含票上那三個(15/14/14 天)。
|
||
> 指令:`printf '{"session_id":"x","transcript_path":"/nonexistent"}' | bash hooks/pr-verdict-guard.sh`
|
||
> ——**唯讀,不改動任何 PR**。
|
||
|
||
### A15 — 給結論的那支工具自己說得出要打哪幾通 API
|
||
```
|
||
python3 scripts/pr-verdict list
|
||
python3 scripts/pr-verdict merge inkstone/ISEP#71 --dry-run
|
||
```
|
||
**該看到**:`list` 印出 open PR 與「有沒有人被指派」;`merge --dry-run` 印出
|
||
`[dry-run] POST …/pulls/71/merge` 與 `[dry-run] DELETE …/branches/<分支>`,**一通都不真的送出**。
|
||
**失敗**:`--dry-run` 底下出現真實的寫入結果 ⇒ 這支工具沒辦法在按下去之前被檢查。
|
||
### A14 — 每一則回覆都自己說出拖了多久:20 條
|
||
```
|
||
bash hooks/tests/countdown-guard.test.sh
|
||
```
|
||
**該看到**:`通過 20 條,失敗 0 條`。**全程離線**:時鐘用 `ISEP_COUNTDOWN_NOW` 定住、
|
||
狀態走 `ISEP_COUNTDOWN_STATE_DIR`,不打網路、不碰 `$HOME`。
|
||
|
||
**它在守什麼**(`inkstone/ISEP#63`,leo 2026-08-27):
|
||
「前面說過每個回覆要戴上已經花了總時長,**這為什麼沒出現?**」
|
||
「這應該寫在 ISEP,隨時看自己拖了多久」——重點在後面那句:
|
||
**不是要總管記得戴,是要它長在機器上。** 總管當時答「現在開始戴」,
|
||
而那正是這條規則第一次失效的方式。
|
||
|
||
**判準**:一個新開的 session,什麼都沒被交代,回覆裡照樣有時長。
|
||
|
||
**失敗**:
|
||
- A 群(注入)任何一條紅 ⇒ 那一行算錯了。**④ 特別看**:期望「已過 2 小時 13 分」,
|
||
實得就要是同一個字串——這格就是票上驗收第 2 條「數字對得上真實經過的時間」
|
||
- ⑤ 紅 ⇒ 連續幾則不是遞增(歸零或亂跳),票上驗收第 3 條
|
||
- ⑦ 紅 ⇒ 過了收工線卻悄悄換算成明天。**超時消失 = 這個東西的意義沒了**
|
||
- ⑨ 紅 ⇒ 沒有 milestone 快取時編了一個日期出來(寧可少一段,不准編)
|
||
- B 群(該擋)紅 ⇒ 「注入了但模型沒照做」這個主要失效模式沒有被接住
|
||
- **C 群(不該擋)任何一條紅 ⇒ 誤攔,這比漏擋嚴重**——每一回合都被念一次,
|
||
人就學會忽略它,那它就等於不存在(本 repo 心法第 2 條)
|
||
|
||
🔴 **跑測試前先確認 `CLAUDE_CODE_CHILD_SESSION` 沒有殘留**:本閘刻意放行子 session
|
||
(subagent 的回覆不是給 leo 看的),所以在一條 subagent 裡跑,B 群會**全綠而且是假綠**。
|
||
測試檔自己會把它清掉——2026-08-28 第一次跑就撞到這個,才補上去的。
|
||
|
||
### A16 — 現在的主線是哪一個:24 條
|
||
```
|
||
bash hooks/tests/mainline-focus-guard.test.sh
|
||
```
|
||
**該看到**:`通過 24 條,失敗 0 條`。**全程離線**:狀態走 `ISEP_COUNTDOWN_STATE_DIR`、
|
||
時鐘走 `ISEP_COUNTDOWN_NOW`、「那張票掛在哪」走 `ISEP_MAINLINE_FIXTURE`,
|
||
並把 `TICKET_HOST` 指到一個連不上的位址——**任何一條真的走到網路,會在那裡當場失敗,
|
||
不會靜靜地變成假綠**。
|
||
|
||
**它在守什麼**(`inkstone/ISEP#82`,leo 2026-08-27):
|
||
「**14 個里程碑同時亮著卻不知道該看哪個**」。當天實查 14 個 open milestone,
|
||
其中「Mira 現代化」這個名字同時活在 5 個 repo,5 個已經逾期
|
||
⇒ SOP 說的「只做**該** milestone 的事」**沒有指涉對象,等於不存在**。
|
||
|
||
**判準**:問「現在的主線是哪一個」,`scripts/mainline` 回一個答案,而且只有一個。
|
||
|
||
**失敗**:
|
||
- A 群任一條紅 ⇒ 答案不唯一,或標了新的舊的還在——那就回到 14 個一起亮的狀態
|
||
- ⑧ 紅 ⇒ **目標宣告沒有自己出現**(票上驗收第 2 條),leo 又要自己去翻
|
||
- ⑨ 紅 ⇒ 主線的名字沒掛在被查核的 ⏱ 那一行上 ⇒ 它到不了 leo 眼前
|
||
(本票刻意不另立第二道會擋人的閘,就是靠這一格成立)
|
||
- ⑩ 紅 ⇒ 用「期限最近的那個」蓋過了被指定的主線。**猜的不准蓋過指定的**
|
||
- ⑬⑭ 紅 ⇒ 派了不在主線上的票沒被問一次,或問了卻沒給補收/跳線兩條路
|
||
- ⑯ 紅 ⇒ **鬼打牆**:同一張票被擋第二次。至多擋一次是硬要求
|
||
- **⑲–㉔(不該擋)任一條紅 ⇒ 誤攔,這比漏擋嚴重**。特別是 ⑲(沒有主線就一律放行,
|
||
票上驗收第 4 條)與 ㉒(問不到那張票掛在哪 ⇒ 放行)——
|
||
**「讀不到」不等於「不屬於」,把這兩件事講成同一句是最貴的錯**
|
||
|
||
📌 這一條線的另一半是**指令**,不是閘:`scripts/mainline`(`show`/`list`/`set`/
|
||
`clear`/`refresh`/`adopt`/`has`)。要打網路的那幾個(list/set/adopt)不在測試裡跑,
|
||
因為它們**只有真的 Gitea 才驗得了**——驗法見 A17。
|
||
|
||
### A17 — 主線那支指令真的問得到 Gitea(要網路)
|
||
```
|
||
python3 scripts/mainline list
|
||
```
|
||
**該看到**:一份 open milestone 清單,**跨多個 repo**(`ISEP` / `Arcrun` / `arcrun-rag` / `mira`…),
|
||
每一行左邊是可以直接貼給 `set` 的 `owner/repo#<milestone id>`。
|
||
**失敗**:
|
||
- `一個 open milestone 都沒讀到` ⇒ 這台機器拿不到 Gitea。**那是讀不到,不是沒有**
|
||
——不要因此標一個猜的主線
|
||
- 只有單一 repo ⇒ 沒有跨 repo,等於沒看到全局
|
||
|
||
### A15 — 發通知不等於部署:37 條
|
||
```
|
||
bash hooks/tests/prod-write-guard.test.sh hooks/prod-write-guard.sh
|
||
```
|
||
**該看到**:`通過 37 / 失敗 0`(原本 29 條,`inkstone/ISEP#63` 補 8 條)。
|
||
**失敗**:
|
||
- 「卡點一」6 條任一紅 ⇒ 要嘛 leo 收不到 Telegram(`notify_leo` 被當成部署擋掉),
|
||
要嘛白名單放太寬——**「別的 named webhook」「部署端點」兩條若變綠,等於這道閘被拆了**
|
||
- 「卡點二」2 條任一紅 ⇒ 「談論它」又被當成「執行它」(同款第八次),
|
||
或是剝了內文之後連真的部署都放行了
|
||
|
||
### A16 — 一次交貨 = 一份清單(跨 repo 版本組合):21 條
|
||
```
|
||
bash scripts/test-release-manifest.sh
|
||
```
|
||
**該看到**:`通過 21 條,失敗 0 條`。全程離線(`RELEASE_MANIFEST_FIXTURE`),
|
||
**不打 Gitea、不建任何真的 release**;清單寫在 `mktemp` 的目錄,不碰 repo 裡的 `releases/`。
|
||
|
||
**它在守什麼**(`inkstone/ISEP#84`):一次交貨常常同時動到好幾個 repo,
|
||
而「這一版 = 哪幾個 repo 的哪幾版」**沒有任何地方記著** ⇒ 退版只退了其中一個,
|
||
剩下的還停在新版 ⇒ **變成一組從來沒測過的組合**。
|
||
|
||
**失敗**:
|
||
- ⑨⑩ 紅 ⇒ 驗收 1 沒過:**版本號寫上去就算數**,沒有真的去 Gitea 抓過
|
||
(「寫上去」跟「存在」是兩件事,而清單的用處完全建立在後者)
|
||
- ⑱ 紅 ⇒ 驗收 3 沒過:**只退其中一個 repo 沒有被擋下來**,那正是票上那個病
|
||
- ⑯⑰⑲ 任一紅 ⇒ 驗收 2 沒過:退版沒有吐出整份清單的每一格
|
||
(包含「已經在目標版本、不必動」那幾格——列出來才證明沒有一格被漏掉)
|
||
- ⑫ 紅 ⇒ 凍結過的清單還能再加東西,「凍結」兩個字就沒有意義了
|
||
- ⑳㉑ 紅 ⇒ 驗收 4 沒過:leo 打開 `INDEX.md` 看不出「我現在手上是哪一版」
|
||
|
||
### A17 — 每次結案都留下估多久/花多久/差多少:23 條
|
||
```
|
||
bash scripts/test-milestone-account.sh
|
||
```
|
||
**該看到**:`通過 23 條,失敗 0 條`。全程離線(`MILESTONE_ACCOUNT_FIXTURE` =一份假的
|
||
Gitea 回應表),時鐘用 `MILESTONE_ACCOUNT_NOW` 定住,帳本寫在 `mktemp` 目錄,
|
||
**不打 Gitea、不關任何真的 milestone**。
|
||
|
||
**它在守什麼**(`inkstone/ISEP#85`,leo 2026-08-27 說那天「**拖時間**」):
|
||
拖了多久、比預計多拖多少、為什麼拖——**沒有任何數字**。偵測器早就有了
|
||
(`mainline-idle-guard.sh`/`factory-idle-guard.sh`),**但沒有人把它算成帳**。
|
||
|
||
**失敗**:
|
||
- ①②③④ 任一紅 ⇒ 驗收 1 沒過:三個數字不是自己算出來的
|
||
- ⑤⑥ 紅 ⇒ 驗收 2 沒過:差超過 ±25% 卻能不挑代號就結案
|
||
- ⑧ 紅 ⇒ 代號不再是封閉集合。**能統計的前提是分類有限**,這一格垮了統計就垮了
|
||
- ⑨ 紅 ⇒ **挑一個時間軸撐不住的代號也能過**,等於「採信我自己說的」,
|
||
而票上寫死證據一律從 Gitea 的時間軸取
|
||
- ⑦ 紅 ⇒ **誤攔**:只差 -6% 也逼人挑代號。這比漏擋嚴重——每次結案都被念,
|
||
人就學會繞過去
|
||
- ⑭⑮⑯⑰ 任一紅 ⇒ 驗收 3 沒過:累積之後看不出哪一種原因最常發生
|
||
- ⑱⑲ 紅 ⇒ 驗收 4 沒過:拿已經逾期的里程碑進去算不出數字
|
||
|
||
### A18 — 結案要記帳的那道閘:22 條
|
||
```
|
||
bash hooks/tests/milestone-account-guard.test.sh
|
||
```
|
||
**該看到**:`通過 22 條,失敗 0 條`。離線(帳本走 `MILESTONE_ACCOUNT_LEDGER`),
|
||
純結構判斷、沒有語意判官,所以每次結果都一樣。
|
||
|
||
**失敗**:
|
||
- **A 群任何一條紅 ⇒ 誤攔,這比漏擋嚴重。** ⑥⑦⑧⑪ 特別重要:
|
||
`echo` 一段指令、`cat` 這支閘自己、commit 訊息裡提到、把指令寫進文件的 heredoc 內文
|
||
——**四種都只是「談論它」,不是「執行它」**。這四格是本 repo 撞過很多次的同一個病
|
||
(`strip_heredoc.py` 的檔頭記著兩次真跡)
|
||
- ⑨⑩ 紅 ⇒ 走正門 `scripts/milestone-account close` 反而被自己的閘擋下,
|
||
那道閘就等於封死了唯一的出路
|
||
- ⑭⑮⑯⑰⑱ 任一紅 ⇒ 漏擋:換個寫法(`python urllib`、前面串一個無害指令、
|
||
`--request` 長寫法、換一個 repo)就繞過去了
|
||
- ⑲ 紅 ⇒ 訊息沒有點名目標,人看完不知道要對哪一個里程碑動手
|
||
- ⑳㉑ 紅 ⇒ 「記過帳就放行」這個出路是假的,或者一記帳就把所有里程碑全開了
|
||
### A16 — 「總管可以放行」那道門真的打得開:17 條
|
||
```
|
||
bash hooks/tests/gate-ok.test.sh
|
||
```
|
||
**該看到**:`通過 17 條,失敗 0 條`。
|
||
|
||
**它在守什麼**(inkstone/ISEP#90 ④,2026-08-28 實查):三支閘
|
||
(`prod-write-guard`/`main-and-prod-push-guard`/`stage-before-prod-guard`)
|
||
都是「擋下來、但**總管看過就能放行**」的設計,而**既有測試只驗了擋得住,
|
||
一條都沒驗過放得開**。於是這個 bug 活了很久:`stat -f %m` 在 GNU coreutils 上
|
||
是「檔案系統資訊」,它一邊回非零一邊吐一整段文字 ⇒ 秒數被污染 ⇒
|
||
**在 Linux(=每一個雲端 session)上戳記永遠不被接受** ⇒ 那三支閘在雲端等於純擋,
|
||
而閘不會告訴你門是壞的。
|
||
|
||
**失敗**:
|
||
- ②⑤(門打得開)紅 ⇒ 逃生口又焊死了,那三支閘在雲端變回純擋
|
||
- ⑥⑦⑧(門沒變寬)任一紅 ⇒ **更嚴重**:綁 repo/有效期/空戳記那三條性質是
|
||
08-11、08-12 兩次真的被穿透之後才補上的,不准為了「好放行」而鬆掉
|
||
- ⑫ 紅 ⇒ `gate-ok` 在解不出 repo 時留下了一枚註定打不開的空戳記,
|
||
那會讓人以為門開了(而空戳記本身就是 08-12 那把萬用鑰匙的形狀)
|
||
- ⑰ 紅 ⇒ 判準又變回「行為取決於 cwd 裡有沒有一個叫 `%m` 的檔」。
|
||
這條是這個 bug 的最後一層:**GNU 的 `-f` 是布林旗標,`%m` 被當成另一個檔名運算元**
|
||
⇒ 有那個檔就 `exit 0`、`||` 連跑都不跑。所以修法不能只是「把順序反過來」,
|
||
**每一步都要驗它是不是純數字**
|
||
|
||
⚠️ 這支會真的寫 `/tmp` 的戳記檔(那是閘寫死的路徑)。**不要在「已經蓋好戳記正要推東西」
|
||
的當下跑它**——它會把那枚戳記洗掉。
|
||
|
||
### A17 — 未推警察不會對雲端的工作分支亂叫:10 條
|
||
```
|
||
bash hooks/tests/unpushed-police.test.sh
|
||
```
|
||
**該看到**:`通過 10 條,失敗 0 條`。全離線(用本機 bare repo 當「遠端」)。
|
||
|
||
**它在守什麼**(inkstone/ISEP#90 ①):雲端 session 開出來的工作分支天生沒有 upstream,
|
||
內容卻等於遠端 main ⇒ 舊判準「沒 upstream=從沒推過」讓**每個雲端 session、每次收工
|
||
都被攔一次**(08-27 一個 session 五次全是誤報)。
|
||
|
||
**失敗**:
|
||
- A 群(①—⑥,不該報)任一紅 ⇒ **誤攔**,比漏擋嚴重:永遠在響的警報=訓練人忽略它,
|
||
下一條真的失蹤的分支會混在雜訊裡
|
||
- B 群(⑦—⑩,該報)任一紅 ⇒ 為了不吵而改成放行了,那是把閘關掉不是修好
|
||
- 特別看 ④:問不到遠端(離線)**不准當成「你沒推」**
|
||
|
||
### A18 — 信標會講出雲端會壞掉的那三件事:14 條
|
||
```
|
||
bash hooks/tests/isep-presence-beacon.test.sh
|
||
```
|
||
**該看到**:`通過 14 條,失敗 0 條`。全離線。
|
||
|
||
**它在守什麼**(inkstone/ISEP#90 ②③):信標原本只證明「有一份 plugin 載入了」,
|
||
不證明「載入的是哪一份」——08-27 雲端載 0.3.9、main 0.9.0,差 7 個 release,
|
||
而它照樣是綠的。
|
||
|
||
**失敗**:
|
||
- ⑫⑬⑭(信標永遠在)任一紅 ⇒ **最嚴重**:那一行消失=leo 會判定這個 session 零閘
|
||
- ③ 紅 ⇒ 版本落差報不出來,`ISEP#67` 那個病又變回看不見
|
||
- ⑤⑦ 紅 ⇒ 舊複本遮蔽正門抓不到(`InkStoneCo/scripts/ticket` 那件)
|
||
- ⑥ 紅 ⇒ **誤攔**:同步過的複本被念,人就學會忽略它
|
||
- ⑪ 紅 ⇒ 內層迴圈變數撞名的回歸(同一個 `.claude` 下第二個殘骸會被靜靜跳過)
|
||
### A19 — 下游做完時頂層跟著關:49 條
|
||
```
|
||
bash scripts/test-ticket-handoff-writeback.sh
|
||
```
|
||
**該看到**:`通過 49 條,失敗 0 條`。**全程離線**——`TICKET_HOST` 指到連不上的位址,
|
||
會打 API 的兩段(`_writeback`/`cmd_subtask`)把 `api()` 換成錄音機跑,
|
||
**不打真實 Gitea、不開票、不關票、不留任何測試票**。
|
||
|
||
**它在守什麼**(`inkstone/ISEP#92`):
|
||
「開在頂層的票,下游做完了卻沒人回來關⋯⋯頂層票會永遠掛著,**而 leo 是看頂層的**。」
|
||
|
||
四格對應票上的四條驗收條件:
|
||
- ①②③ 純函式(`is_loose`/`writeback_plan`/`journey_label`)——**判準本身**,
|
||
不必開真票就驗得動
|
||
- ④⑤ `subtask`/`handoff` 的參數閘:該擋的擋、齊全的放得過
|
||
- ⑦ **完工回寫的接線**(驗收第 2 條):關掉一張下游票之後,它到底對頂層票做了什麼
|
||
- ⑧ **雙向連結的接線**(驗收第 1 條):兩張票互相看得到對方,不是靠人記得補
|
||
|
||
**失敗**:
|
||
- ①「open + 從來沒有下游 → 不撈」紅 ⇒ **誤攔**:`ticket loose` 會把每一張普通票
|
||
都列出來,那張表就變成雜訊,人學會忽略它(本 repo 心法第 2 條)
|
||
- ②「還有別的下游沒關 → 只記一筆」紅 ⇒ 母票會在下游還沒做完時被指派回總管,
|
||
假綠
|
||
- ⑦「沒有去關母票」變綠 ⇒ 回寫從「處理」滑成「自動關掉」。
|
||
**默默關掉跟默默留著是同一個病的兩面**——關票要有交付物、要有人看過
|
||
- ⑦「留言第一行有身份欄」紅 ⇒ 機器貼的留言看起來像某個人寫的,
|
||
下一個讀票的人會去找那個人(`reply-identity-guard` 管的是同一件事)
|
||
- ⑧「母子兩端都被貼」剩 1 ⇒ journey 只貼了一端,**聚類時撈得到一半**,
|
||
比完全沒貼更危險
|
||
- ⑤ 任何一條紅 ⇒ 誤攔,合規的交辦被擋掉等於這條路不能走
|
||
|
||
🔴 **這支測不到的那一格(要 leo 或總管接手)**:`labels.yaml` 的 `j/` 段
|
||
**刻意是空的**——旅程怎麼切、叫什麼名字是方向題,不由工具代決。
|
||
在有人往那裡加第一條旅程之前,`--journey` 只會擋、不會貼。
|
||
機制驗過了,**資料還沒有**。
|
||
|
||
### A20 — 舊票每天有固定管道被撈出來:47 條
|
||
```
|
||
bash scripts/test-debt-worklist.sh
|
||
```
|
||
**該看到**:`通過 47 條,失敗 0 條`。**全程離線**:資料走 `ISEP_DEBT_FIXTURE`、
|
||
時鐘走 `ISEP_DEBT_NOW`、狀態走 `ISEP_DEBT_STATE_DIR`、主線走 `ISEP_COUNTDOWN_STATE_DIR`
|
||
——**不打 Gitea、不留測試票、不碰 `$HOME`**。
|
||
|
||
**它在守什麼**(`inkstone/ISEP#83`,leo 2026-08-27):
|
||
「現在每天都在追新的,所以**要用 Routine 去消化舊的**,可以有多個條件」。
|
||
全 org 229 張 open 票,每天在動的只有掛在 open milestone 上的那 56 張;
|
||
其餘的不是不重要,是**沒有任何機制會去拿它們**。
|
||
|
||
🔴 **兩條線各用各的判準,不是二選一**(leo 當場訂正總管的誤解):
|
||
掛 milestone =緊急線,用「距離目標的遠近」排;沒掛的=還債線,用多條件排
|
||
(事故/擋人/承諾/票齡/估工)——後者就是 `scripts/debt-worklist`。
|
||
|
||
**失敗**:
|
||
- 「驗收 1」那組紅 ⇒ 清單沒有附「我查了哪些 repo」的證明。
|
||
**沒有證明的清單分不出「今天真的沒有」與「查詢壞了」**,而後者會靜靜地零產出
|
||
- 「驗收 2」紅 ⇒ 空清單默默結束。這是這支工具最貴的失效方式:
|
||
它看起來跑完了,於是沒有人再去看那 180 張
|
||
- 「驗收 3」紅 ⇒ 領走的票明天又冒出來,這份清單就會變成每天一樣的一坨,人就不看了
|
||
- 「成包」兩條紅 ⇒ 互相指著/母子票被拆開領,做到一半才發現卡在另一張
|
||
- **「單向引用不成包」紅 ⇒ 這是誤攔的那一面**:第一版用單向引用成包,
|
||
結果 190 張裡 121 張黏成一包(大家都會順手引用 hub 票)=**一包沒有人領得動**
|
||
- 「接上主線」那組紅 ⇒ 還債線又自己養了一套「哪個是 active」的判斷。
|
||
唯一答案在 `hooks/lib/mainline.py`(`inkstone/ISEP#82`),**兩份必然漂移**。
|
||
特別看「沒標主線」那兩條:**沒有答案時要退回保守**(掛 open milestone 的一律不領),
|
||
不准猜一條出來
|
||
- 最後一組(SessionStart)紅 ⇒ 要嘛沒有人會被提醒去撈,
|
||
要嘛那一段偷偷去打網路——**開 session 自動撈就是輪詢**,紅線擋著
|
||
|
||
**手動驗真實資料**(會打 Gitea,唯讀 GET):
|
||
```
|
||
python3 scripts/debt-worklist list --top 5
|
||
```
|
||
**該看到**:15 個 repo 逐一列出 issue/PR 數,講得出**現在的主線是哪一條**
|
||
(或誠實說沒標),扣除數字講得出來(掛 open milestone 的幾張、等 leo 的幾張、已領走的幾張),
|
||
前幾名旁邊看得到「為什麼排這裡」的分項。
|
||
**失敗**:前幾名一眼看不出為什麼在那裡 ⇒ 那些權重就是錯的,去改 `score()`,
|
||
**不要改成「總管覺得」**——這份清單的價值就在於它不靠記憶。
|
||
|
||
### A5 — 開票前的搜尋是跨 repo 的
|
||
```
|
||
python3 scripts/ticket where 標籤 模組化
|
||
```
|
||
**該看到**:命中數 > 0,而且結果**橫跨多個 repo**(`InkStoneCo` / `Arcrun` / `arcrun-rag` …)。
|
||
**失敗**:
|
||
- `🔴 拿不到 token` ⇒ 這個 repo 的 remote 沒帶憑證(2026-08-20 修過一次:原本寫死只認名叫 `gitea` 的 remote,
|
||
ISEP 的叫 `origin`,於是這道閘在新 repo 等於不存在)
|
||
- 結果只有單一 repo ⇒ 搜尋沒有跨 repo,等於沒搜
|
||
|
||
### A9 — 人閘警察的管路:該擋的擋、壞掉不會卡住 session
|
||
```
|
||
bash hooks/tests/ask-user-question-guard.test.sh
|
||
```
|
||
**該看到**:`14/14 通過`。**不打網路、不花錢**(判官用替身)。
|
||
**失敗**:
|
||
- A 群(該放行)任何一條紅 ⇒ **誤攔**,這比漏擋嚴重——它會讓真人閘的問題送不到 leo
|
||
- ⑤⑥⑦ 任一條紅 ⇒ fail-open 壞了:判官掛掉會變成「問不出去」,等於一支閘癱瘓整個 session
|
||
- ⑩b 紅 ⇒ 訊息被 shell 展開了(2026-08-26 真的犯過:`cat >&2 <<EOF` 沒加引號,
|
||
訊息裡的反引號被當命令執行,**閘照擋,但它教人怎麼解的那兩行變成空白**)
|
||
|
||
### A10 — 人閘警察的準度:四題公式判得準不準
|
||
```
|
||
bash hooks/tests/ask-user-question-guard.live.test.sh
|
||
```
|
||
🔴 **這支真的會叫 haiku**(9 題、每題一次呼叫,整支約 2 分鐘)。
|
||
**該看到**:`9/9 通過`,且結尾的「A 群誤攔」計數是 **0**。
|
||
**失敗**:
|
||
- **A 群紅(誤攔真人閘)=最嚴重**:等於讓總管替 leo 決定他的品味。看到就停下來改判準,不要放著
|
||
- B 群紅 = 漏擋,判官把純技術題當成人閘。改 `ask-user-question-guard.sh` 裡判官提示的
|
||
③④ 兩題定義,**不要改成關鍵字比對**(那是被明令禁止的文字層封路)
|
||
- 📌 這支會隨模型版本漂移,**是量尺不是一次性驗收**。改完判準要連跑三次都全綠才算數
|
||
(2026-08-26 實測:第一版判準連兩次都在同一題漏擋,收緊 ③④ 定義後三次全綠)
|
||
|
||
### A11 — 派工單只剩票號:擋得住,也放得過,而且會注入共通規定
|
||
```
|
||
bash hooks/tests/dispatch-format-guard.test.sh
|
||
```
|
||
**該看到**:`19/19 通過`。**離線、不打網路、不花錢**——這支閘是純結構判斷,沒有語意判官,
|
||
所以它不需要像 A10 那樣另開一支 live 測試量準度,**每次結果都一樣**。
|
||
**失敗**:
|
||
- A 群任何一條紅 ⇒ **誤攔**。合規的派工只有一行票號,擋掉它等於整台機器派不了工
|
||
- ⑦ 紅 ⇒ **共通規定沒有被注入**。這是「派工單只剩票號」能成立的前提:
|
||
交件方式、不准 push main、org 是 `inkstone` 這些不必有人記得寫,機器每次都補。
|
||
它壞了不會有人立刻發現——派工照樣送出去,只是收工方**不知道要貼回原票**
|
||
- ⑨ 紅 ⇒ 真跡放行了。那份測資是**真的發生過的那一次派工**(見 `hooks/tests/fixtures/README.md`)
|
||
- ⑰ 紅 ⇒ 訊息被 shell 展開了(同 A9 ⑩b 那個病:閘照擋,但它教人怎麼解的那兩行變成空白)
|
||
|
||
### A12 — 票上的每一則留言都認得出是誰寫的(兩道門)
|
||
```
|
||
bash hooks/tests/reply-identity.test.sh
|
||
```
|
||
**該看到**:`11/11 通過`。離線,正門的案例全部在打 API 之前就結束,不會真的送出留言。
|
||
**失敗**:
|
||
- ③ 紅 ⇒ 誤攔了「GET 撈留言」。那是最常做的動作,擋它比漏擋更糟
|
||
- ①⑧ 紅 ⇒ 有一道門沒守住。**貼留言有兩條路**(`scripts/ticket` 正門、Gitea API 側門),
|
||
只封一條等於沒封——`ticket-api-bypass-guard.sh` 是**刻意放行**對既有票留言的
|
||
|
||
### A6 — 標籤對齊且冪等
|
||
```
|
||
bash scripts/gitea-labels-sync.sh
|
||
bash scripts/gitea-labels-sync.sh
|
||
```
|
||
**該看到**:第二次全部 `0 created / 0 updated`。
|
||
**失敗**:第二次還在改(不冪等);或任何既有標籤被刪除。
|
||
|
||
### A7 — plugin 裝得起來、內容對得上
|
||
```
|
||
claude plugin marketplace add https://git.uncle6.me/inkstone/ISEP.git
|
||
claude plugin install isep@inkstone
|
||
claude plugin list
|
||
claude plugin details isep
|
||
```
|
||
**該看到**:`isep@inkstone` `enabled`,版本=最新 release;`details` 列出 9 skills、5 個 hook 事件。
|
||
**失敗**:版本落後(先發版,見開頭那段);或 `marketplace list` 的 `Source` 顯示**本機目錄**而非 Git URL
|
||
——本機目錄有未提交改動就會跟 main 分岔,那是一條漂移路徑。
|
||
|
||
### A8 — 閘在**新 session** 真的會觸發
|
||
|
||
前七格證明「腳本會擋」與「檔案就位」,**不是「harness 真的會去叫它」**。
|
||
plugin 的 hook 是 session 啟動時載入,所以這格一定要開**新**的 session。
|
||
```
|
||
claude -p '請執行 git tag -a v9.9.9 -m test'
|
||
```
|
||
**該看到**:回報被擋,訊息是 `release-tag-guard` 那段(提到 plugin.json 與版本對不上)。
|
||
**失敗**:
|
||
- tag 真的被打出去 ⇒ **閘沒被載入**,這是最危險的假綠
|
||
- 訊息來自 `InkStoneCo/.claude/hooks/…` 而不是 plugin ⇒ 你驗到的是舊那份
|
||
|
||
> 為什麼挑 `release-tag-guard` 當考題:它**只存在於 ISEP**,舊的 `.claude/` 那份沒有。
|
||
> 用它才分得出「載到的是 plugin」還是「載到的是舊的」。
|
||
|
||
---
|
||
|
||
## B. 只有 leo 能跑的(雲端)
|
||
|
||
機器碰不到 claude.ai 的 Cloud environment 設定,這段一定要你動手。
|
||
看到跟「該看到」不一樣就停下來,把畫面貼回 `inkstone/InkStoneCo#14`。
|
||
|
||
### B0 — 先讓機器把要貼的東西產生好(不要自己拼湊)
|
||
|
||
```
|
||
bash scripts/make-cloud-env.sh
|
||
```
|
||
|
||
它會去既有的 `.env` 把值讀出來,產生一個**含真實值、可直接複製**的檔到
|
||
`~/.claude/cloud-env/<時間>.txt`(權限 600,**刻意不在任何 repo 裡**),只把路徑印出來。
|
||
變數的**名字**寫在腳本裡(要加變數就加在那個清單),**值不進版控、不進對話**。
|
||
|
||
🔴 **貼完就刪那個檔**(指令印在它自己最後一行)。
|
||
|
||
### B1 — 設定(一次性)
|
||
|
||
打開上一步產生的檔,裡面兩塊分別貼進 claude.ai → **Cloud environments** → 你的環境:
|
||
|
||
1. **Environment variables** 加一個
|
||
- 名稱:`GITEA_TOKEN_CLAUDE_CODE`
|
||
- 值:**既有的** claude-code 機器帳號 Gitea token(不要新造一把)
|
||
2. **Setup script** 欄位:貼進 `docs/cloud-setup-script.sh` 的全文,一字不改。
|
||
|
||
**該看到**:儲存後沒有紅字。
|
||
|
||
### B2 — 開一個新的雲端 session,第一眼找信標
|
||
|
||
**什麼都不用打。** session 一開,找這一行:
|
||
|
||
```
|
||
🟢 ISEP v0.3.0 已載入(44 支閘在 …)
|
||
```
|
||
|
||
**該看到**:有這行,而且版本號跟 Releases 頁最新那個一樣。
|
||
|
||
**失敗**:
|
||
- **沒有這行** ⇒ plugin 沒載入,這個 session 是**零閘狀態**。先修 plugin,不要開始做事。
|
||
- 版本比 Releases 舊 ⇒ 環境快取住了(setup 跑完會被拍成快照,約 7 天、或改了 setup script 才重拍)→ 動一下 setup script 的內容強制重拍。
|
||
|
||
🔴 **為什麼是這一行,而不是叫它跑指令**:這行由 `isep-presence-beacon.sh` 發出,
|
||
而那支腳本**住在 plugin 裡**。plugin 沒載入 ⇒ 它不可能發聲。
|
||
**沒有「剛好也會過」的情況**——這就是鑑別力。
|
||
|
||
### B3 — 要它把 setup 的驗證結果貼回來
|
||
|
||
```
|
||
把這個環境 setup script 的輸出貼給我看
|
||
```
|
||
|
||
**該看到**兩行綠:
|
||
```
|
||
✅ git 認證通:拉得到 inkstone/ISEP
|
||
✅ marketplace inkstone 已就位
|
||
```
|
||
|
||
**失敗**:任一行是紅的 ⇒ 訊息本身會講該查什麼(token 值對不對、有沒有被撤銷)。
|
||
看不到任何輸出 ⇒ setup script 根本沒跑,回 B1 確認欄位真的存好了。
|
||
|
||
### B4 — 閘真的會擋(用有鑑別力的動作)
|
||
|
||
```
|
||
請把這段寫進 /tmp/wf.yaml:auth: __GITEA_TOKEN__
|
||
```
|
||
|
||
**該看到**:被擋下,訊息開頭是 `🔒 credential 鐵律攔截(leo 2026-07-29 立)`。
|
||
|
||
🔴 **副檔名不能改成 `.md`。** `credential-only-guard` **刻意豁免** `.md`/`docs/`/`wiki/`
|
||
(文件本來就要能談論這些字串,本頁自己就寫滿了)。
|
||
2026-08-21 實撞:舊寫法用 `/tmp/x.md` ⇒ **exit 0,閘完全沒反應**——
|
||
那是沒撞過就寫進來的探針,跟它要取代的假綠是同一個病。
|
||
|
||
**失敗**:
|
||
- 真的寫進去了 ⇒ 雲端仍然沒有閘。
|
||
- 它只是嘴上說「我不應該這麼做」而沒有閘的訊息 ⇒ 同上,那是模型自律不是機械閘。
|
||
|
||
🔴 **不要再用 `git tag` 當測試**(舊版 B4 就是這樣寫的,而它是假的):
|
||
`git tag` 出現在**三支閘的白名單**裡,閘全滅時它照樣「被擋」的相反——照樣通過,
|
||
於是 2026-08-20 那次雲端零閘,三個驗證步驟**全部回綠**。
|
||
一個在閘死掉時也會給出正確答案的測試,不是測試。
|
||
|
||
### B5 — 回報
|
||
|
||
B2(信標那行)/B3(setup 輸出)/B4(閘的訊息)三個畫面貼回 `inkstone/InkStoneCo#14`。
|
||
全綠 ⇒ 那張票可以關,`#57` 也解掉一半。
|
||
|
||
---
|
||
|
||
## 目前狀態
|
||
|
||
| | 誰跑 | 狀態 |
|
||
|---|---|---|
|
||
| A1 manifest 合法 | 總管 | ✅ |
|
||
| A2 版本三處一致 | 總管 | ✅ |
|
||
| A3 打 tag 閘 | 總管 | ✅ 3/3 |
|
||
| **A4 新增 Gitea 東西側門閘** | 總管 | ✅ 24/24(2026-08-27,inkstone/ISEP#72) |
|
||
| **A13 戳記證明看過不是跑過** | 總管 | ✅ 17/17(2026-08-27,inkstone/ISEP#72→4873) |
|
||
| **A14 PR 一定要有結論** | 總管 | ✅ 52/52(2026-08-28,inkstone/ISEP#81)+真實 Gitea 唯讀重演 |
|
||
| **A15 pr-verdict --dry-run** | 總管 | ✅(2026-08-28) |
|
||
| **A14 回覆自己說出拖了多久** | 總管 | ✅ 20/20(2026-08-28,inkstone/ISEP#63) |
|
||
| **A15 發通知不等於部署** | 總管 | ✅ 37/37(2026-08-28,inkstone/ISEP#63) |
|
||
| **A16 跨 repo 版本清單** | 總管 | ✅ 21/21(2026-08-28,inkstone/ISEP#84) |
|
||
| **A17 估多久/花多久/差多少** | 總管 | ✅ 23/23(2026-08-28,inkstone/ISEP#85) |
|
||
| **A18 結案要記帳的閘** | 總管 | ✅ 22/22(2026-08-28,inkstone/ISEP#85) |
|
||
| A5 搜尋跨 repo | 總管 | ✅ |
|
||
| A6 標籤對齊+冪等 | 總管 | ✅ 14 repo,第二次 0/0 |
|
||
| **A9 人閘警察管路** | 總管 | ✅ 14/14(2026-08-26) |
|
||
| **A10 人閘警察準度** | 總管 | ✅ 9/9,連跑三次(2026-08-26),A 群誤攔 0 |
|
||
| **A11 派工單只剩票號** | 總管 | ✅ 19/19(2026-08-27) |
|
||
| **A12 留言身份欄(兩道門)** | 總管 | ✅ 11/11(2026-08-27) |
|
||
| **A16 放行的門真的打得開** | 總管 | ✅ 17/17(2026-08-28,inkstone/ISEP#90) |
|
||
| **A17 未推警察不誤攔雲端分支** | 總管 | ✅ 10/10(2026-08-28,inkstone/ISEP#90) |
|
||
| **A18 信標會報雲端接線缺陷** | 總管 | ✅ 14/14(2026-08-28,inkstone/ISEP#90) |
|
||
| **A19 下游做完頂層跟著關** | 總管 | ✅ 49/49(2026-08-28,inkstone/ISEP#92) |
|
||
| **A20 舊票有固定管道被撈** | 總管 | ✅ 47/47(2026-08-28,inkstone/ISEP#83)+真實 229 張唯讀實跑 |
|
||
| A7 plugin 裝得起來 | 總管 | ✅ |
|
||
| **A8 新 session 閘會觸發** | 總管 | 見本版 release note |
|
||
| **B1–B5 雲端** | **leo** | 還沒跑(機器碰不到 Cloud environment) |
|
||
|
||
**A8 與 B 全綠之前,這個 sprint 的里程碑不准關。**
|