沒人會叫的事會自己叫+讀不完的必讀檔要被整理(inkstone/ISEP#93、inkstone/ISEP#89)

兩張票放同一條分支:都是「該發生卻不會自己發生的事」,觸發點都在 session 邊界。

## inkstone/ISEP#93 —— 逾期和掛著沒人接的事會主動叫

- scripts/isep-nag        撈三種沒人會叫的事(逾期 milestone/等 leo 的票/掉在地上的棒子)
- scripts/isep-notify     發 Telegram,而且**發不出去的時候不會安靜**
- hooks/overdue-nag-guard.sh  SessionStart 跑一次(不輪詢、不 fan-out、不掛 Actions)
- hooks/tests/overdue-nag.test.sh  35 條,全程離線

實跑撈得出票上點名的那五個逾期 milestone(08-24 三個、08-26 兩個)。
沒東西可報時會說「查過了,沒有」——安靜跟壞掉長得一模一樣。

🔴 工單補的那個限制(今天實測出來的)已經處理:
「能不能發得出去」取決於這個 session 載到的 ISEP 是哪一版
(prod-write-guard v0.10.0 才認得出 notify_leo 不是部署,而 hook 註冊路徑
在 session 啟動當下就寫死了)。所以 isep-notify 會**先拿真的要送的那一則
去問這個 session 註冊的那支閘**,把判定寫成檔(誰都查得到),然後:
  · 放行 ⇒ 送,並驗內層 data.data.ok(外層 200 不算送到)
  · 會擋 ⇒ **不繞路**,改貼回票上並把原文印在眼前
用 git 歷史裡的真跡(c263866 那一版閘)測過「會擋」那條路。

 實測發現通道本身現在是斷的:實例上找不到 notify_leo 工作流(404)。
   不是閘、不是網路、不是金鑰。詳情與修法寫在 docs/TESTING.md 最後一段。
   退路兩次都走通了(inkstone/ISEP#93 comment 5182、5184)。

## inkstone/ISEP#89 —— wiki 太長時有人整理

- scripts/wiki-compress   audit/plan/apply/verify/bench 五個動詞
- hooks/wiki-size-guard.sh  SessionStart 點名太長的檔;寫檔時擋「沒走流程的壓縮」
- hooks/tests/wiki-compress.test.sh  25 條,全程離線、不碰真的 wiki

設計上最重要的一條:**只搬不改**,正文一個字都不動。
「合併同類、濃縮成一行」要重寫正文,而重寫的當下沒有人會發現弄丟了什麼。
所以機器只做「搬 + 目錄 + 標 ×N/↻」,合併留給人。

拿現在的 mistakes.md 複本實壓過(不動真的 wiki):
  7,681 行 → 1,199 行
  260 條一條都沒少(verify 用內文雜湊逐條對帳)
  80 個查詢命中率 80/80,定位成本 2,956 → 131 行,**快 22.5 倍**(bench)
verify 反向測過:真的弄丟一條時它抓得到(抓不到的對帳表比沒有更糟)。

## 盤點數字

在這棵樹上當場數的,不是拿上一版加減推的:
  ls hooks/*.sh | wc -l              → 55(was 53)
  grep -c '"command":' hooks.json    → 71(was 68)
plugin.json/README/hooks-inventory 三處同步改。
**版本號沒動**(0.11.0),待總管定版。
This commit is contained in:
Claude Code
2026-08-28 01:04:16 +00:00
parent bebbbd2c11
commit da9bd53cae
12 changed files with 2190 additions and 7 deletions
+89
View File
@@ -165,6 +165,65 @@ bash hooks/tests/prod-write-guard.test.sh hooks/prod-write-guard.sh
- 「卡點二」2 條任一紅 ⇒ 「談論它」又被當成「執行它」(同款第八次),
或是剝了內文之後連真的部署都放行了
### A16 — 沒人會叫的事會自己叫:35 條
```
bash hooks/tests/overdue-nag.test.sh
```
**該看到**`通過 35 條,失敗 0 條`。**全程離線**——資料走 `--fixture`、時鐘走 `--now`
網路用 `ISEP_NOTIFY_OFFLINE=1` 關掉。**不打 Gitea、不打實例、不留任何測試票。**
**它在守什麼**`inkstone/ISEP#93`2026-08-27 實查):一張票掛著等 leo 三天了、
一個 milestone 逾期了、一根棒子躺著沒人接——**沒有任何東西為此叫過一聲**。
當天有五個 milestone 逾期(08-24 三個、08-26 兩個),一聲都沒有。
**失敗**
- A 群任一紅 ⇒ 撈不出票上點名的那五個逾期 milestone,或訊息不是白話的(票上驗收 1)
- ⑨⑩ 紅 ⇒ 沒東西可報時**安靜結束**(票上驗收 2)。**安靜跟壞掉長得一模一樣**
- ⑪ 紅 ⇒ 撈不到資料時把「我沒查到」講成「沒有事情逾期」——那是最貴的一種假情報
- **C 群任何一條紅 ⇒ 誤報**,這比漏報嚴重:每次開場都被念一串不相干的東西,
人就學會跳過它,那時真的有事也叫不動他
- D 群紅 ⇒ **發不出去卻靜默**。「送出成功」跟「送到了」是兩件事
- ㉖㉗㉘㉙ 紅 ⇒ 這個 session 的閘是哪一版變回「假設」。㉘ 特別重要:
**閘說會擋就不准從 python 這條路繞過去**——繞得過的閘等於不存在
- ㉛ 紅 ⇒ 探針把 `/tmp/.prod-write-ok` 燒掉了(那是總管單次用完即丟的授權)
- ㉟ 紅 ⇒ 每條 subagent 開工都吵 leo 一次
> 🔴 **跑測試前先確認 `CLAUDE_CODE_CHILD_SESSION` 沒有殘留**:這支閘刻意放行子 session,
> 在一條 subagent 裡跑,F 群會全綠而且是**假綠**。測試檔自己會把它清掉。
### A17 — 壓 wiki 不准弄丟東西:25 條
```
bash hooks/tests/wiki-compress.test.sh
```
**該看到**`通過 25 條,失敗 0 條`。**全程離線**,只在 `mktemp` 目錄裡動檔案,
**不碰任何真的 wiki**
**它在守什麼**`inkstone/ISEP#89`):`mistakes.md` 是「做新功能前讀一遍」等級的必讀檔,
實測 **7,681 行 / 260 條**——**沒有人真的每次都從頭讀。讀不完的必讀檔,等於沒有。**
**失敗**
- ①② 紅 ⇒ 切條切錯了,後面三件全部失準。② 特別重要:`mistakes.md` 裡真的有
**寫在 code fence 裡的 `##`**(第 534 行那種),把它當成一條就會拿不存在的東西去對帳
- ⑤⑥ 紅 ⇒ 弄丟了東西卻沒被發現(票上驗收 2)
- **⑦ 紅最嚴重**:那是**反向測**——真的弄丟一條時 `verify` 抓不到。
**一個抓不到問題的對帳表,比沒有對帳表更糟**,因為它會被當成證據
- ⑧⑨⑩ 紅 ⇒ 壓完查不到、或查得更慢(票上驗收 1)
- ⑪–⑮ 紅 ⇒ 壓縮沒有票號、沒有交付紀錄(票上驗收 3:不准順手做完沒人知道)
- **⑱⑲㉑ 任一紅 ⇒ 誤攔**,這比漏擋嚴重:一般編輯被擋、非 wiki 的檔被擋、
新建檔案被擋,人就學會繞過它
- ㉒ 紅 ⇒ 擋不只一次 ⇒ 會卡死
- ㉕ 紅 ⇒ 都在門檻內還要講一次話,那行提示下次就沒人看了
📌 **真跡跑過**:拿**現在的 `mistakes.md` 複本**實壓一次(不動真的 wiki)——
`7,681 行 → 1,199 行``260 條一條都沒少``verify` 逐條對帳)、
`80 個查詢命中率 80/80,定位成本 2,956 行 → 131 行,快 22.5 倍``bench`)。
指令:
```
cp <某個 wiki>/mistakes.md /tmp/w/ && python3 scripts/wiki-compress apply /tmp/w/mistakes.md --ticket inkstone/ISEP#89
python3 scripts/wiki-compress verify /tmp/w/mistakes.md.before-compress /tmp/w/mistakes.md /tmp/w/mistakes-archive-*.md
python3 scripts/wiki-compress bench /tmp/w/mistakes.md.before-compress /tmp/w/mistakes.md /tmp/w/mistakes-archive-*.md
```
### A5 — 開票前的搜尋是跨 repo 的
```
python3 scripts/ticket where 標籤 模組化
@@ -362,6 +421,9 @@ B2(信標那行)/B3(setup 輸出)/B4(閘的訊息)三個畫面
| **A15 pr-verdict --dry-run** | 總管 | ✅(2026-08-28 |
| **A14 回覆自己說出拖了多久** | 總管 | ✅ 20/202026-08-28inkstone/ISEP#63 |
| **A15 發通知不等於部署** | 總管 | ✅ 37/372026-08-28inkstone/ISEP#63 |
| **A16 沒人會叫的事會自己叫** | 總管 | ✅ 35/352026-08-28inkstone/ISEP#93 |
| **A16b Telegram 真的送達** | **leo** | ⛔ **驗不了——通道本身斷了**(見下) |
| **A17 壓 wiki 不准弄丟東西** | 總管 | ✅ 25/25+真跡實壓(2026-08-28inkstone/ISEP#89 |
| A5 搜尋跨 repo | 總管 | ✅ |
| A6 標籤對齊+冪等 | 總管 | ✅ 14 repo,第二次 0/0 |
| **A9 人閘警察管路** | 總管 | ✅ 14/142026-08-26 |
@@ -373,3 +435,30 @@ B2(信標那行)/B3(setup 輸出)/B4(閘的訊息)三個畫面
| **B1B5 雲端** | **leo** | 還沒跑(機器碰不到 Cloud environment |
**A8 與 B 全綠之前,這個 sprint 的里程碑不准關。**
---
## ⛔ 已知斷點:`notify_leo` 這條 Telegram 通道現在是斷的(2026-08-28 實測)
`inkstone/ISEP#93` 的驗收第 4 條是「**Telegram 真的收得到**(不是『送出成功』,
是 leo 手機上真的出現)」。**這一格現在過不了,而且原因不在本次的程式碼。**
實測(`scripts/isep-notify`,閘判定 `pass`、UA 帶對之後):
```
POST …/webhooks/named/leo/notify_leo/trigger
→ HTTP 404 {"error":"找不到 workflow \"notify_leo\",請先執行 acr push"}
```
**那台線上實例上沒有 `notify_leo` 這支工作流。** 不是閘擋的、不是網路、不是金鑰
(這條路本來就不需要金鑰)。頂層 wiki `agent-memory.md` 記過同一件事發生過一次
2026-08-10,KV 整批換新時定義消失,後來補推回去)——**看起來又斷了一次**。
修法要有人把 `mira` repo 的 `workflows/notify-leo.yaml` 重新 push 上那台實例。
**那是動線上實例,subagent 不能做**`prod-write-guard`),要總管或 leo。
📌 **這條斷了不代表 `#93` 沒交付**:本次的設計前提就是「**不假設發得出去**」——
實測那兩次的退路都走通了,訊息貼回了 `inkstone/ISEP#93`
`#issuecomment-5182``#issuecomment-5184`),並把原文印在眼前。
**發不出去而靜默,等於沒做;發不出去但講出來並留下退路,就是這支的規格。**
通道修好之後不必改任何程式碼,重跑 `python3 scripts/isep-nag --notify` 即可補驗這一格。
+12 -5
View File
@@ -1,4 +1,4 @@
# 52 支閘,白話盤點表
# 55 支閘,白話盤點表
> 回應 `inkstone/InkStoneCo#40`:「如果加入了,我應該可以白話文看到 hooks 的內容?」
> 這份表就是那個「白話文」——不用點開任何 `.sh` 檔,一行看懂一支閘在管什麼。
@@ -7,16 +7,14 @@
## 一句話結論
`hooks/` 底下有 **53 個 `.sh` 檔**`hooks.json` 實際掛上 **68 條註冊**(同一支閘常被多種情境同時掛上);
`hooks/` 底下有 **53 個 `.sh` 檔**`hooks.json` 實際掛上 **68 條註冊**(同一支閘常被多種情境同時掛上);
`hooks/` 底下有 **55 個 `.sh` 檔**`hooks.json` 實際掛上 **71 條註冊**(同一支閘常被多種情境同時掛上);
其中 **3 支檔案存在但沒被掛上**(2 支是待人填的空範本、1 支是刻意留著沒開的止血帶,見下面「未生效」表)。
下面按「你會在什麼時候撞到它」分組,一支一行。
> 🔴 **這兩個數字上一版是錯的(2026-08-26 實際數過才發現)**:本頁原本寫「43 個檔、53 條註冊」,
> 而當時真實是 **45 個檔、55 條註冊**——中間有兩支閘進來時沒有回頭改這裡。
> 現在的寫法是實際數出來的:
> `ls hooks/*.sh | wc -l` 53`grep -c '"command":' hooks/hooks.json` 68
> `ls hooks/*.sh | wc -l` 53`grep -c '"command":' hooks/hooks.json` 68。
> `ls hooks/*.sh | wc -l` 55`grep -c '"command":' hooks/hooks.json` 71
> ⚠️ **冒號不能省**`grep -c '"command"'`(沒冒號)會連 `"type": "command"` 一起數到,回 **120**。
> 本頁 2026-08-27 之前寫的是沒冒號那版——**照著它跑會拿到一個跟本頁不符的數字**。
> **一份會偷偷過期的盤點表,跟沒有盤點表差不多**——見本頁最後「怎麼跟實況對帳」那段。
@@ -41,6 +39,12 @@
> 📌 **`0.7.0``inkstone/ISEP#30`2026-08-27)進來一支**`mainline-idle-guard.sh`F 組,Stop)。
> 48→**49** 支、59→**60** 條,兩個數字都是加完之後當場數出來的(指令同上)。
>
> 📌 **`inkstone/ISEP#93` `#89`2026-08-28)進來兩支**
> `overdue-nag-guard.sh`E 組,SessionStart)與 `wiki-size-guard.sh`E 組 B 組,兩個事件)。
> 53→**55** 支、68→**71** 條——**兩個數字都是在這棵樹上當場數出來的**,不是拿上一版加減推的
> (本頁自己上面就記著「不能靠加減推算、只能實數」)。順手拿掉一組重複貼上的行
> (「53 個 `.sh` 檔…」與那條 `grep` 指令各被貼了兩次)。
> 📌 **`0.10.0``inkstone/ISEP#81`2026-08-28)進來一支**`pr-verdict-guard.sh`F 組,Stop)。
> 51→**52** 支、64→**65** 條,兩個數字都是加完之後當場數出來的(指令同上)。
> 順手改掉一個過期的數字:描述欄長期寫「27 支腳本」,實數是 **34**
@@ -90,6 +94,7 @@
| `arcrun-intent-guard.sh` | AI 寫的 Arcrun workflow 語法不對就擋下,而且**直接把正確寫法回貼給它**(不是只罵它錯,是教它怎麼改)。 | 🛑 擋(教學型) |
| `subagent-first-guard.sh` | 這個對話**從頭到尾都沒有派過任何 subagent**,AI 卻要自己動手改程式碼,就先擋一次,逼它想一想「這件事能不能交給別人做」。 | 🛑 擋 |
| `mistake-needs-ticket-guard.sh` | AI 想往 `mistakes.md`(教訓紀錄)新增一條「機制可以防止」的教訓,卻沒附對應票號就擋下——沒有票號的教訓沒有人會回頭處理。 | 🛑 擋 |
| `wiki-size-guard.sh` | AI 想**一口氣砍掉 wiki 檔一大半內容**(淨縮水超過 800 字且超過原本 45%)就擋一次——那不是一次編輯,那是一次壓縮,而**壓縮會弄丟東西,弄丟的當下沒有人會發現**。出路是走 `scripts/wiki-compress`:它逼你附票號、把壓掉了什麼寫進 `.compress-log.md`,並用內文雜湊**逐條對帳**證明沒弄丟。只是改字、加字、小修一律不碰;真要手改就在內容裡放 `wiki-compress-ok` 留痕。 | 🛑 擋(至多攔一次) |
| `pending-changes-retired.sh` | AI 想寫東西進已經廢除的 `pending-changes.md` 檔案就擋下——這個檔案已停用,規格變更一律改開 Gitea 票。 | 🛑 擋 |
> `kbdb-api-wall-guard.sh` 在這裡也重複掛了一次(見 A 組)——它同時守著「下指令」跟「寫檔案」兩種情境,詳見下方「重複掛載」一節。
@@ -129,6 +134,8 @@
|---|---|---|
| `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 注入) |
| `overdue-nag-guard.sh` | 對話一開始就去 Gitea 撈**沒有人會叫的事**:逾期的 milestone、掛著等你的票(標「等了幾天」)、標著「有人在做」卻好幾天沒動的票;撈完用白話講出來,**有事就發 Telegram 給你**。沒東西可報時它會說「查過了,沒有」——**安靜跟壞掉長得一模一樣**。發不出去時不會靜默:它會先問這個 session 的 `prod-write-guard` 會不會擋(舊版把「發通知」誤認成「部署」),擋就改貼回票上並把原文印在眼前。只在開 session 時跑一次,**不輪詢、不掛排程**。 | 📝 記錄(context 注入 Telegram |
| `wiki-size-guard.sh` | 對話一開始講出「哪幾個 wiki 檔已經沒有人讀得完了」(預設超過 1200 行就點名)。**讀不完的必讀檔,跟沒有那個檔的差別只在於它讓人以為有。** 另一半掛在寫檔上,見 B 組。 | 📝 記錄(context 注入) |
| `skill-deploy-drift-guard.sh` | 如果「全機真正在用的 skill」跟「repo 裡版控的正本」內容對不上,就在開場講出來——避免用著一份沒人知道已經跟正本分家的舊拷貝。 | 📝 記錄 |
## F. AI 想結束這一輪、要收工的時候(Stop)