Commit Graph

128 Commits

Author SHA1 Message Date
claude-code e575786be3 merge main(定版 v0.13.0),衝突只有 plugin.json 一行
版本號取 main 的 0.13.0(我不定版)。description 的腳本數在合併後的樹上重數:
  main 那個「40」= `ls scripts | wc -l`(37 個檔+3 個子目錄)
  合併後同一個算法 = 42(多的兩個是本 PR 的 debt-worklist 與 test-debt-worklist.sh)
  ⇒ 40 → 42。閘的兩個數字沒變(hooks/*.sh = 54、hooks.json "command": = 71,本 PR 沒新增 hook)
2026-08-28 00:55:30 +00:00
claude-code 45adc10a52 併進 main(v0.12.0)並接上「主線唯一答案」(inkstone/ISEP#83)
衝突只有 docs/TESTING.md 一處,兩邊都留:
  main 的 A16(gate-ok)/A17(未推警察)/A18(信標)原樣保留,
  我那節改編號成 A19——main 上 A16、A17 已經被 #90#99 各佔一次,不動別人的。

接上 inkstone/ISEP#82 帶進來的 hooks/lib/mainline.py(不自己養第二套判斷):
  · 有標主線 ⇒ 只排除「屬於主線那一條」的票;掛在**別條** milestone 的仍算債
    (那條線現在沒有人在推,正是它躺著的原因)
  · 沒標主線 ⇒ 退回保守做法:掛 open milestone 的一律不領,並在清單上照實說沒標
  · 別條 milestone 已逾期 ⇒ +15「答應過人家的日期已經過了」

測試 40 → 47 條(+7 條全給主線這段),並把 ISEP_COUNTDOWN_STATE_DIR 也隔離:
不然這支測試的結果會取決於「這台機器現在標了哪條主線」。

盤點數字在合併後的樹上重數:
  hooks/*.sh = 54、hooks.json "command": = 71(本 PR 沒新增 hook,兩個都沒變)
  scripts 一層檔案 = 39(本 PR +2)⇒ plugin.json description 37→39
  版本號沒動(0.12.0,總管統一定版)
2026-08-28 00:51:06 +00:00
claude-code 87678a8a87 Merge pull request '定版 v0.13.0(現在的主線是哪一個,有唯一答案)' (#102) from chore/v0.13.0 into main v0.13.0 2026-08-28 00:49:13 +00:00
claude-code 62c2f3e664 定版 v0.13.0(.claude-plugin/plugin.json) 2026-08-28 00:48:59 +00:00
claude-code 4014676813 Merge pull request '現在的主線是哪一個,有唯一答案(inkstone/ISEP#82)' (#99) from feat/mainline-milestone into main 2026-08-28 00:43:55 +00:00
claude-code 7871921741 把主線接進派工與注入(inkstone/ISEP#82)
- hooks.json     mainline-focus-guard 掛 Agent+Task(派工的兩個入口都要守);
                 SessionStart 多一條 `scripts/mainline refresh`(0.8s,不輪詢)
- hooks-inventory 54 支/71 條(當場數的)+補人話一列;順手修掉上一版重複的那一行,
                 並把「指向空氣」那格的假警報寫清楚(countdown-milestone-refresh 在 scripts/)
- TESTING        A16(24 條離線)+ A17(要網路的那半怎麼驗)
- plugin.json    描述數字對齊;版本不動,待總管定版
2026-08-28 00:40:05 +00:00
Claude Code 21971ea83e 現在的主線是哪一個,變成一個查得到的事實(inkstone/ISEP#82)
14 個 open milestone 同時亮著,其中「Mira 現代化」同名活在 5 個 repo,
所以 SOP 說的「那個 active milestone」在現場沒有指涉對象。

- hooks/lib/mainline.py        主線的唯一存放處(一個檔放得下一條),從不打網路
- scripts/mainline             show/list/set/clear/refresh/adopt/has
- hooks/mainline-focus-guard.sh 派了不在主線上的票 ⇒ 攔一次,問補收還是跳線
- hooks/lib/countdown.py       ⏱ 那一行的主線改讀「被標定的」,蓋過「期限最近」的猜測
- hooks/countdown-guard.sh     同一個注入點加第二行 🎯(ISEP#63 那半不動)
- 測試 24 條(離線)+ countdown 原有 20 條仍全綠
2026-08-28 00:39:50 +00:00
claude-code dbd7c8c874 Merge pull request '定版 v0.12.0(雲端的閘真的能用)' (#100) from chore/v0.12.0 into main v0.12.0 2026-08-28 00:39:06 +00:00
claude-code f9327de068 定版 v0.12.0(.claude-plugin/plugin.json) 2026-08-28 00:38:13 +00:00
claude-code 67f5f0fa5e 舊票每天有固定管道被撈出來(inkstone/ISEP#83)
leo 2026-08-27:「現在每天都在追新的,所以要用 Routine 去消化舊的,可以有多個條件」

兩條線各用各的判準(他當場訂正的那件事):
  緊急線=掛 open milestone 的,用「距離目標的遠近」排
  還債線=沒掛的那 188 張,用多條件排(事故/擋人/承諾/票齡/估工)← 本次做的

新增 scripts/debt-worklist(list/claim/forget):
  · 撈全 org 15 個 repo 的 open issue + open PR,逐一列出當作「我查了哪些」的證明
  · 扣掉掛 open milestone 的(緊急線在管)、等 leo 的(Human/human-exec/s/stage,
    那些要催不是要領,催辦是 inkstone/ISEP#93)、已領走的
  · 每張票旁邊印出「為什麼排這裡」的分項,排錯可以指著某一項說它給太多
  · 成包只認強訊號:互相指著、或 Gitea 原生相依(母子票)。
    第一版用單向引用成包,190 張裡 121 張黏成一坨——一包沒有人領得動
  · 空清單會明講「查詢跑過了,結果是零」,不默默結束
  · claim 之後那些票不再出現在清單上,並在隔天的清單頂端交代它們關了沒

hooks/session-start-recall.sh 加一段(不打網路,只看狀態檔日期):
  今天還沒撈過就說一句,並給出要跑的那一行。開 session 自動去撈=輪詢,紅線擋著。

測試 scripts/test-debt-worklist.sh:40 條全離線,票上四條驗收條件各一組。
真實資料唯讀實跑過:229 張 → 可領 188 張/185 包,領走兩張後隔天清單少掉它們。
2026-08-28 00:38:02 +00:00
claude-code 9b0afec0b2 Merge pull request '雲端的閘:修好三個穩定重現的接線缺陷,並診斷第四個(inkstone/ISEP#90)' (#96) from fix/cloud-wiring-isep90 into main 2026-08-28 00:37:30 +00:00
claude-code b7b83bf243 併進 main(#97 pr-verdict),並在合併後的樹上重數盤點數字(inkstone/ISEP#90)
數字全部在合併後的樹上重數,沒有用相加推的:
· hooks/*.sh = 53、hooks.json 註冊 = 68 —— 與 main 已宣告的一致,不用動
· scripts/ 頂層 = **39**(git 追蹤的),plugin.json 原本寫 38 ⇒ 改成 39
  (本分支新增 scripts/gate-ok 一支)

🔴 這裡不能用 `ls scripts/ | wc -l`:它在這台機器回 40,因為多了一個**沒進版控的
`__pycache__/`**。那個數字會隨「有沒有人跑過 python」而變 ⇒ 用 git 追蹤的數目才穩定。

docs/hooks-inventory.md 標題也順手改:它寫「52 支閘」而自己第 10 行寫「53 個 .sh 檔」
——同一頁自相矛盾,標題是漏改的那半。

**version 一個字沒動**(0.11.0),由總管統一收斂。

合併後全部重跑(CLAUDE_CODE_CHILD_SESSION 有設與清掉各跑一次,結果相同):
pr-verdict-guard 52/52、countdown-guard 20/20、prod-write-guard 37/37、
gate-ok 17/17、unpushed-police 10/10、isep-presence-beacon 14/14、
main-and-prod-push 10/10+13/13、stage-before-prod 16/16+13/13、
sdd-guard 8/8、reply-identity 11/11、dispatch-format 33/33、
search-is-not-proof 31/31、mainline-idle 61/61、factory-idle 33/33、
ticket-api-bypass 24/24、ticket-where-seen 17/17、baton-handback 10/10、
comment-carries-task 22/22、github-contact 14/14、kbdb-api-wall 10/10、
release-tag 8/8、版本一致性 
2026-08-28 00:31:06 +00:00
claude-code 45b7b81075 Merge remote-tracking branch 'gitea/main' into fix/cloud-wiring-isep90 2026-08-28 00:29:10 +00:00
claude-code bebbbd2c11 Merge pull request 'PR 一定要有結論(inkstone/ISEP#81)' (#97) from feat/pr-verdict-v2 into main v0.11.0 2026-08-28 00:28:19 +00:00
claude-code 4322deb23a 補上 stat -f 診斷的最後一層:連離開碼都不可靠(inkstone/ISEP#90 ④)
總管自己驗這一格量到 exit=0,我量到 exit=1。**兩個都是真的**,
而分歧本身就是最後一塊拼圖:

**GNU 的 `-f` 是 `--file-system`,布林旗標、不接格式字串**
⇒ `%m` 不是格式,它被當成**另一個檔名運算元**
⇒ 離開碼取決於「cwd 裡有沒有一個叫 `%m` 的檔」:

    A. 沒有(一般情況)  → exit 1 ⇒ `||` 會跑   ⇒ 正確的秒數接在垃圾後面
    B. 剛好有            → exit 0 ⇒ `||` 不會跑 ⇒ 整包連一個數字都沒有

兩種情況閘的結果一樣:MT 都不是純數字、戳記都作廢。(實測 GNU coreutils 9.4,兩種都重現過)

🔴 教訓比原本寫的更尖銳:舊寫法的 `||` fallback 救不了,
**不是因為它沒跑,而是因為「跑不跑」根本不由這支腳本決定**——
它由「cwd 裡有沒有某個檔名」決定。
一個行為取決於 cwd 有沒有某個檔的判斷式,不管跑不跑都是壞的。
⇒ 所以修法不能只是「把順序反過來」,**每一步都要驗它是不是純數字**
(lib/mtime.sh 本來就是這樣寫的,現在把理由寫進去了)。

gate-ok 測試補第 ⑰ 條守這一層:cwd 裡有一個叫 `%m` 的檔時,file_mtime 仍要回純數字。
16 → 17 條,TESTING.md 的 A16 一併更新。
2026-08-28 00:27:51 +00:00
claude-code eb7c3656be PR 一定要有結論(inkstone/ISEP#81)(scripts/pr-verdict) 2026-08-28 00:27:42 +00:00
claude-code 16c03e46c1 PR 一定要有結論(inkstone/ISEP#81)(hooks/tests/pr-verdict-guard.test.sh) 2026-08-28 00:27:40 +00:00
claude-code 99abdf725b PR 一定要有結論(inkstone/ISEP#81)(hooks/pr-verdict-guard.sh) 2026-08-28 00:27:39 +00:00
claude-code accedb5898 PR 一定要有結論(inkstone/ISEP#81)(hooks/hooks.json) 2026-08-28 00:27:37 +00:00
claude-code 77a2032858 PR 一定要有結論(inkstone/ISEP#81)(docs/hooks-inventory.md) 2026-08-28 00:27:34 +00:00
claude-code be09353330 PR 一定要有結論(inkstone/ISEP#81)(docs/TESTING.md) 2026-08-28 00:27:32 +00:00
claude-code 81368f451f PR 一定要有結論(inkstone/ISEP#81)(.claude-plugin/plugin.json) 2026-08-28 00:27:30 +00:00
claude-code 3a1c3d4969 Merge remote-tracking branch 'origin/main' into fix/cloud-wiring-isep90
# Conflicts:
#	docs/TESTING.md
2026-08-28 00:25:04 +00:00
claude-code 617315e3dd 把四個雲端接線缺陷的診斷寫下來(inkstone/ISEP#90)
docs/governance/cloud-wiring.md:四個缺陷分成兩種病——
①④ 是「閘的判準寫的是地端才成立的假設」,②③ 是「同一個東西有兩份,雲端跑到舊的」。
每一格都附實測證據與修法,並標明還沒關掉的兩格住在哪張票(ISEP#67/真身 repo)。

TESTING.md 補 A14/A15/A16 三格:怎麼跑、該看到什麼、什麼算失敗。
三支閘被擋下的訊息也一併指向 scripts/gate-ok(原本的手打法保留當等價寫法)。
2026-08-28 00:14:01 +00:00
claude-code 3e83a4b4f3 修好「總管可以放行」那道門——它在 Linux(=每個雲端 session)上是焊死的(inkstone/ISEP#90 ④)
三支閘(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 跑同一份:②⑤(門打得開)紅、⑥⑦⑧(門沒變寬)全綠
⇒ 這支補的正是既有測試從來沒驗過的那半邊。
2026-08-28 00:11:42 +00:00
claude-code a9e30fc6e7 Merge pull request '每一則回覆都自己說出拖了多久(inkstone/ISEP#63)' (#94) from feat/countdown-stamp into main v0.10.0 2026-08-28 00:10:10 +00:00
claude-code adb7009b67 信標多報三件雲端會壞掉的事:版本落差/舊複本遮蔽正門/退役機制的殘骸(inkstone/ISEP#90 ②③)
信標原本只證明「有一份 plugin 載入了」,不證明「載入的是哪一份」——
2026-08-27 雲端載的是 0.3.9、main 是 0.9.0,中間差 7 個 release,而它照樣是綠的。
那個看不見的落差的實際後果:0.3.9 裡還活著兩支 v0.9.0 已整支刪掉的 hook,
於是 .claude/pending-verification/ 被清掉之後又長回來。

三格(都只報告、不擋、拿不到答案就閉嘴):
· 版本落差:匿名讀 ISEP main 的 plugin.json(D20 判準下屬於「讀」),快取 6 小時、
  逾時 6 秒。落後就講清楚差幾版與怎麼修,並指向 inkstone/ISEP#67。
· 舊複本遮蔽正門:plugin 的 scripts/* 在專案樹(含雲端的 $TOP/InkStoneCo/)裡
  有同名但**內容不同**的複本就點名。實例=InkStoneCo/scripts/ticket 的取 token
  邏輯還停在「只認名叫 gitea 的 remote」,而雲端的 Gitea 叫 origin
  ⇒ 一律死在「拿不到 gitea token」⇒ 人被逼去繞過正門直接打 API,
  而那正是 ticket-api-bypass-guard.sh 在防的事。
· 退役機制的殘骸:.claude/ 底下的目錄,若 plugin 的 hooks/scripts 一個字都沒提到
  ⇒ 產生它的東西已經不在這一份裡了。判準是「plugin 現在還認不認得它」,
  **不是關鍵字黑名單**(leo 2026-08-17 已證明那條路 8 次誤攔、0 次正確攔截)。

訊息改由 hooks/lib/beacon_report.py 序列化:三段報告含引號與換行,
用 shell 內插拼 JSON 一個引號就會讓整行信標消失——而那正是「零閘狀態」的長相。
fail-open:python 掛掉、檔案不見、網路不通,一律退回原本那一行乾淨的信標。

測試 hooks/tests/isep-presence-beacon.test.sh:14/14(全離線)。
第 ⑪ 條守的是寫這支時真的犯過的 bug(內層迴圈變數跟外層的 d 撞名,
第二個殘骸之後全部被靜靜跳過)——把那個 bug 放回去,該條立刻轉紅。
2026-08-28 00:05:15 +00:00
Leo 17a74d7d3a 每一則回覆都自己說出拖了多久(inkstone/ISEP#63 → comment 5106)
leo 2026-08-27:「前面說過每個回覆要戴上已經花了總時長,這為什麼沒出現?」
              「這應該寫在 ISEP,隨時看自己拖了多久」
重點在後面那句:不是要總管記得戴,是要它長在機器上。
總管當時答「我沒做,現在開始戴」——而那正是這條規則第一次失效的方式。

一支閘掛兩個事件,是同一件事的兩半(票上點名的失效模式就在這裡):
  UserPromptSubmit → 注入算好的那一行(模型不必自己算,也算不準)
  Stop            → 查核這一回合的回覆裡到底有沒有那一行,沒有就擋一次
只做前半=又一個會被忽略的提醒;只做後半=罰它做一件拿不到資料的事。

判準不是關鍵字黑名單,是「那個被要求的輸出元素在不在」——
whitelist-of-one:要求一個機器產生的標記在場,不是猜哪些字不該在場。
換講法照樣要帶標記,多寫什麼都不會觸發。

時長從這段對話的第一則訊息算起,理由寫在 hooks/lib/countdown.py 檔頭:
「任務」在機器上沒有起點,而 CLAUDE.md 規則三點七「一段對話=一個 release」
剛好讓對話起點就是這個交付的起點——這個數字沒有人要維護,也不會說謊。
resume 取較早的那個:接關不是重新開始。

連帶修好票上的兩個卡點(prod-write-guard):
  卡點一 發一則 Telegram 跟部署工作流在閘眼裡一模一樣 ⇒ 判準改看打的是哪一個
        named webhook(路徑形狀+名字),放行範圍只有 notify_leo 一個名字
  卡點二 連「把卡點寫進票裡」都被同一支閘擋(同款第八次)⇒ 剝掉內文再判,
        起始行保留,所以真的在部署的寫法照樣擋
        (修這支的過程又撞了一次同款——那就是這一格最好的證據)

測試:countdown 20/20、prod-write-guard 29→37/37,全程離線
     (時鐘定住、狀態走環境變數、不打網路)。
實測:起點 06:28 台北、現在 08:03 ⇒ 期望「已過 1 小時 35 分」,實得同一字串。

plugin.json 0.9.0 → 0.10.0(版本沒動=沒有人吃得到)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 00:03:38 +00:00
claude-code 352d7a0b81 未推警察改用「遠端有沒有這顆 commit」判斷,不再用「有沒有 upstream」(inkstone/ISEP#90 ①)
雲端 session 開出來的工作分支天生沒有 upstream,內容卻等於遠端 main
(薄殼 HEAD 081c547 = GitHub 遠端 main 081c547,同一顆)
⇒ 每個雲端 session、每次收工都被攔一次,08-27 一個 session 五次全是誤報。

三件:
· 判準改成問遠端(git ls-remote,快取 120 秒)。本機只答得準「有」,
  答「沒有」時才打網路——實測本 session 的 origin/main 落後遠端 4 天。
· 三態:遠端有/遠端沒有/問不到。**問不到一律不報**,不拿離線當罪證。
· 散落分支的基準也一起換成遠端實際那顆——基準過期會把已經在遠端 main 上的
  分支整批報成散落(包含雲端 session 自己那條)。

順手補雲端那格接線:`$CLAUDE_PROJECT_DIR` 在雲端是薄殼根,真身在 `$TOP/InkStoneCo/`。
舊的掃描清單四個路徑一個都不存在 ⇒ 真身有東西沒推,這支閘一輩子不會知道。

測試 hooks/tests/unpushed-police.test.sh:10/10(全離線,本機 bare repo 當遠端)。
拿舊版跑同一份:A 群 5 條全紅、B 群 4 條全綠 ⇒ 測試有鑑別力,不是改到寬鬆。
2026-08-28 00:00:36 +00:00
claude-code e0ac81b2e3 Merge pull request '補上「用錯的路去證明一件事」那一格的閘(inkstone/ISEP#30 → comment 4879)' (#80) from feat/gate-search-is-not-proof into main v0.9.0 2026-08-27 13:41:17 +00:00
Leo 7de1ad6be6 補上「用錯的路去證明一件事」那一格的閘(inkstone/ISEP#30 → comment 4879)
擋的是**證據的出處**,不是措辭。

判準(兩個條件同時成立才響):
  ① 要送出去的那份東西(票上的留言/派工單)裡,貼了一個值
     ——entry id 或 `kb://` 來源位址——而它這個 session 只在
     `kbdb_search` 的回應裡出現過
  ② 這個 session 從沒用產品檢索路徑(graph/wiki 內容/query)拿過那筆

為什麼不比對措辭:驗收條件第 4 條寫死不准,而 leo 2026-08-17 已經證偽過
文字層——那天 8 次誤攔、0 次正確攔截,且方向穩定:紅線寫得越細,命中
關鍵字的機率越高 ⇒ 那些閘在懲罰謹慎。值跟動作一樣有限且可枚舉,措辭不是。

🔴 `kbdb_search` 一點都沒有變難用:查 wiki、找 record_id、看某筆在不在,
全部照放。它只在「把搜尋輸出貼出去當產品檢索壞掉的證據」那一刻才響。

實測 31 條,A 群(不該擋)11 條、B 群(該擋)5 條、C 群訊息 6 條、
D 群登記處 9 條。真跡重演=inkstone/Arcrun#167 comment 4865 那則退回,
原文照貼會被擋;走過一次真路徑之後同一份留言就放行。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 21:38:17 +08:00
claude-code a001b10947 Merge pull request '戳記要證明「看過」,不是「跑過」(inkstone/ISEP#72)' (#79) from fix/stamp-proves-seen-not-run into main v0.8.0 2026-08-27 13:24:28 +00:00
Leo ba444d1210 戳記要證明「看過」,不是「跑過」(inkstone/ISEP#72 → comment 4873)
2026-08-27 實犯:`ticket where … >/dev/null 2>&1` 之後直接 `ticket new`——
戳記寫成功了,命中的 72 張一眼沒看,於是開出 arcrun-rag#147,
而第一名 arcrun-rag#104 講的是同一件事,已經開了 13 天。

⇒ 舊戳記證明的是「這個程序被執行過」,而「有沒有看」在 stdout 那一端,
  閘本來完全碰不到。

做法:把 stdout 那一端變成機械事實,不是文字判斷。
- `fd_is_devnull(fd)`:fstat 問得出來的事實。刻意只認 /dev/null 這一種
  (寫進檔案讀得回來、pipe 有下游,只有 /dev/null 物理上找不回來)。
- `where` 把 `shown` 記進戳記;順便替前 3 名補內文摘要——今天的實害正是
  「光看標題看不出是同一條線」(#104 的標題完全沒提 ingest)。
- `new` 的閘一之二:命中 >0 且 shown 為假就擋,並把那份被丟掉的清單
  交到眼前;只要 stderr 不是 /dev/null,這一次的擋就記成「看過了」,
  重下一模一樣的指令就會過——成本落在「看」,不落在「寫」。
- 刻意排在閘二**之前**:否則第一次就帶 `--not-a-comment "理由"` 的人
  永遠看不到候選清單,理由是閉著眼睛寫的。
- 舊格式戳記(沒有 shown 欄位)當成沒看過(fail-closed)。

不該擋的(測試覆蓋):沒命中就不吵、看過了就不吵、`--not-a-comment`
既有欄位照舊、不判斷理由寫得好不好(leo 2026-08-17 已證偽文字層判準)。

測試 17/17:scripts/test-ticket-where-seen-guard.sh(離線,不打真實 Gitea、
不留測試票;離開碼 2=擋、1=放行走到網路才炸)。docs/TESTING.md 新增 A13。
plugin.json 升到 0.8.0——版本沒動=沒有人吃得到。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 21:17:35 +08:00
claude-code a4eb17df48 Merge pull request '補上「有動作但主線沒動」那一格的閘(inkstone/ISEP#30)' (#78) from feat/gate-catches-recon-without-output into main v0.7.0 2026-08-27 12:32:17 +00:00
Leo 35e927ef55 補上「有動作、不宣告」那一格:主線閒置警察(inkstone/ISEP#30)
空手警察判「有沒有動作」、稼動率警察判「有沒有那句話」,兩支中間留了一格:
**有動作、就是不宣告下一步** ⇒ 兩支都放行。2026-08-27 實際發生的就是這一格。

新閘 hooks/mainline-idle-guard.sh(Stop)判的是「有沒有推進」,不是「有沒有動作」:
- 乾回合 = 這回合 ≥3 個 tool call,且沒有任何推進證據
- 推進證據五種(任一成立就歸零):派工/產出/寫進外部系統/Bash 寫入白名單/
  **工作區狀態變了**(HEAD 或未提交變更的指紋,heredoc·sed 改檔也吃得到)
- 連續 4 個乾回合擋一次,擋完歸零且**門檻加倍**(4→8→16)

一個字都不讀(守票上「不准文字層判準」那條紅線);③④ 的字面比對只用來放行,
永遠不用來擋——白名單漏一項是少放行一次,黑名單漏一項是誤攔一次。

門檻 4 是量出來的,不是拍腦袋:重放 6 份真 transcript、1965 個真實回合,
門檻 3 響 4 次、門檻 4 響 1 次(0.05%);門檻 3 多出來那三次都落在
「leo 連問問題、我逐題查證回答」的段落——那正是票上第 3 條驗收要保護的情境。

測試 hooks/tests/mainline-idle-guard.test.sh:61 條,A 群 15 條全是「不該擋」。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 20:28:55 +08:00
claude-code 736a601589 Merge pull request '盤點表補回漏掉的兩列人話+補上驗得出這種漏的指令(inkstone/ISEP#75)' (#76) from docs/inventory-missing-rows into main v0.6.1 2026-08-27 10:39:59 +00:00
claude-code 1b64f3cc4e Merge pull request '跑測試不再偽造「有一筆推 main 在等你裁」(inkstone/ISEP#59)' (#77) from fix/tests-leave-no-forged-pending into main 2026-08-27 10:38:48 +00:00
Leo fa2f76177e docs(inventory): 反向那半補上真正驗得到幽靈的那一格(總管複驗退回)
總管複驗指出反向 comm 有 3 個輸出。實跑重現了,但逐個查過之後:
**三個都不是幽靈,三個都不該刪。**

  claim-verify-police.sh / subagent-claim-worksheet.sh
    → 全部 5 處都在內文,講的是「已於 ISEP#60 移除」。表格列(第一欄)
      命中數 = 0。刪掉它們等於刪掉「G 組為什麼是空的」這段歷史。
  github-arm.sh
    → 它存在,在 scripts/ 不在 hooks/;出現在 github-contact-guard 那一列的
      描述裡,是告訴人「怎麼解鎖」的逃生口。刪掉那一列就不再說得出怎麼過閘。

成因是那組 grep 拿掉了 `^\| \`` 錨點。有錨點=「這一行的第一欄就是這支閘」=
表格真的列了它;沒錨點=全頁所有反引號。**0 次正確攔截、3 次誤攔。**

但「頁面上出現根本不存在的檔名」確實該被抓到,只是不能靠文字判斷。
本次補上第三格 C:頁面提到的每個 *.sh,要嘛現在存在,要嘛 git 歷史裡存在過。
**「現在沒有但曾經有」=歷史;「從來沒有過」=幽靈。** 它問的是 git 不是用字。

順帶:標頭區那個 43 是 2026-08-20 快照段落裡的歷史數字,不是現況宣稱,
但原文用現在式讀起來像現況 ⇒ 加上「這一段每個數字都是那天的」標記。

實測(含兩個反向對照,證明新那格不是橡皮圖章):
  現況              A1[] A2[] C[]                48 檔 / 48 列
  表格種一支假閘    A2 與 C 都叫
  散文種一支假閘    A1/A2 抓不到、C 抓到          ← 只有 C 守得住這種
18 個測試檔 0 失敗;claude plugin validate 通過。

inkstone/ISEP#75(總管複驗 comment 4822)
2026-08-27 18:35:49 +08:00
Leo 950c3e1919 測試不再偽造「有一筆推 main 在等總管裁」(inkstone/ISEP#59)
現象(實測,不是推論):跑完 main-and-prod-push-guard 那三支測試之後

    $ git status --short
     M hooks/lib/__pycache__/strip_heredoc.cpython-314.pyc
     M pending-main-push/unnamed--ISEP.md
    ?? pending-main-push/unnamed--A.md

成因:這支閘擋下推 main 的同時,會把那次請求寫成
`<hooks 的上一層>/pending-main-push/<誰>--<repo>.md`,而三支測試的測資本來
就全是推 main。於是每跑一次測試,工作區就多/改幾筆**偽造的待裁決**。

兩個後果,後者比較貴:
  ① `git add -A` 很容易把它們帶進 commit(08-27 那次真的帶進去了,事後才拔掉)
  ② 總管的迴圈讀那個目錄,讀到的每一筆都該是真的在等他裁——
     測試每跑一次就偽造一筆 ⇒ **下一筆真的請求會混在雜訊裡**。
     這跟「永遠在響的警報」是同一個病。

改法(產品程式碼一行都沒動):
- 新增 hooks/tests/lib/hook-sandbox.sh:把整個 hooks/ 複製到暫存區再跑複本。
  閘算 pending 目錄的位置靠的是 `$0` 的上一層 ⇒ 請求寫進暫存區。
  **刻意不在閘上開一個「寫去哪」的環境變數**——那種開關同時是一條把紀錄關掉的路。
- 三支測試改測沙盒複本,並各補兩條斷言:
  ① repo 的 pending-main-push 一個位元都沒動
  ② 沙盒裡**真的有**留下請求(只驗 ① 的話,把留紀錄的功能整個關掉也會綠)
- pending-main-push/ 不再進版控(它是本機狀態不是原始碼),只留一份 README 說明規約;
  既有的兩筆與那顆被追蹤的 .pyc 一併 `git rm --cached`,檔案留在硬碟上不刪。

實測:
- 三支各自 10/10、19/19、13/13(原本 8/17/11,各多兩條新斷言)
- 把 `H` 改回真跡重跑 ⇒ 新斷言兩條都紅(11/13)+工作區又髒
  ⇒ 這兩條斷言真的抓得到它要抓的東西
- 全套 16 支測試檔跑完 0 失敗,`git status` 沒有多出任何一行
- `claude plugin validate .` ✔ Validation passed

沒有升版:這次只動測試與版控範圍,沒有任何閘的行為改變,
不需要靠新版本號送到任何人手上;請跟著總管下一個 release 一起出去。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:33:44 +08:00
Leo a671948fa5 docs(inventory): 補回表格漏掉的兩列人話,並補上驗得出這種漏的那組指令
標頭寫 48 支,下面的表格只列得出 46 支——缺的是
`isep-presence-beacon.sh`(E 組 SessionStart)與
`milestone-due-guard.sh`(A 組 PreToolUse/Bash)這兩列。
不是本次合併造成的,`origin/main` 上數同一個指令也是這兩支。

為什麼這個漏會活這麼久:本頁「落差偵測」只有一組 comm,比的是
`hooks.json`(驗有沒有註冊),驗不了「有沒有寫進這張人話表」。
所以本次同時補上第二組 comm(fs 檔名 vs 表格列出的閘名),
並把「怎麼跟實況對帳」第 1 條指到那一組。

順手修掉同一頁另外兩處自己會騙人的地方:
- 標頭的複驗指令 `grep -c '"command"'` 會連 `"type": "command"` 一起數,
  實跑回 118 不是 59;正確寫法要帶冒號。
- 「其餘 41 支」是 43 支閘那一版數的。改成拿掉數字而不是改成 46——
  改成 46 等於宣稱逐支重讀過源碼,而這次沒有。

版本 0.6.0 → 0.6.1(v0.6.0 已出 release,不升版沒有人吃得到這頁)。

實測:48 檔 vs 48 列,兩個方向的 comm 都空;
18 個測試檔全綠(0 失敗);claude plugin validate 通過。

inkstone/ISEP#59(comment 4779 第二件)
2026-08-27 18:24:20 +08:00
claude-code 7795cd705a Merge pull request '第二波:棒子三格+移除自造待驗單,版本收斂 0.6.0(inkstone/ISEP#58 #62)' (#74) from integrate/isep59-wave2 into main v0.6.0 2026-08-27 10:13:17 +00:00
Leo 577c736b74 merge: 移除自造的待驗單機制,改由 Gitea 原生三格承接(inkstone/ISEP#62)
順序刻意是先 #58 後 #62:新機制(comment-carries-task-guard/baton-handback-guard/
scripts/ticket 的 subtask)先到位,舊機制才拆,main 上不存在「舊的拆了、新的沒到」的真空。

衝突只有 .claude-plugin/plugin.json:
- version 依 inkstone/ISEP#59 comment 4763 的裁決收斂成 0.6.0(四張 PR 各自宣告的作廢)
- description 的數字不採信任何一邊,改成併完後當場數出來的 48 支/59 條

一併把 docs/hooks-inventory.md 與 README.md 的同一組數字改成實數結果
(原本各寫 58/57,都是用加減兜出來的)。
2026-08-27 18:02:24 +08:00
Leo 7e1b762cbe merge: 棒子不會掉——子票相依+tag+指派三格全用 Gitea 原生欄位(inkstone/ISEP#58)
衝突只有 docs/hooks-inventory.md 的 A 組表格,解法照 ISEP#59 comment 4763 指定的
「兩列都留」:保留 main 上 #73 更新過的 ticket-api-bypass 敘述與 reply-identity-guard,
再加上本分支新增的 comment-carries-task-guard。標頭數字留到 #62 併完後一次實數。
2026-08-27 17:56:11 +08:00
claude-code e32957e72b Merge pull request '封「新增 Gitea 東西」的每一道側門,不是只有開票(closes inkstone/ISEP#72)' (#73) from fix/gitea-write-search-guard into main 2026-08-27 09:50:21 +00:00
claude-code e7e60fff89 Merge pull request 'dispatch-format-guard 修兩處:合格範例被自己擋、禮貌收尾不該算違規(closes #65)' (#70) from fix/dispatch-guard-parens-and-wiring into main 2026-08-27 09:49:13 +00:00
Leo 23e4472715 封「新增 Gitea 東西」的每一道側門,不是只有開票(inkstone/ISEP#72)
leo 2026-08-27:「開票不查是否有現成的,這是什麼問題?」「不只開票前,
所有新增 gitea 的東西,都要搜尋」。

v1 的 ticket-api-bypass-guard 只認裸字 `grep -qw POST`(大小寫敏感),
只管 issues 端點。2026-08-27 總管一天開 13 張票,v1 一次都沒攔到——
用的是 `urllib.request.Request(url, data=...)`(靠傳 data= 隱式變 POST,
指令裡從頭到尾沒有 "POST" 三個字)和 `requests.post(...)`(小寫)。

本次改動:
- 寫入訊號從單一裸字換成一組結構性訊號(curl 資料類旗標/`.post(`/
  `method=post`/urllib 隱式 POST),且大小寫不敏感,同時保留 v1 的裸字比對
- 端點擴大到 milestones/labels/pulls(含 org 層級的 labels),不再只管 issues
- 用「resource/數字」特徵先放行帶 ID 的既有資源子路徑(貼留言、改標籤、
  合併 PR 等),避免貪婪比對把 `/issues/5/labels` 誤判成頂層 `/labels` 端點
- 擋下 issues 新增時,順手用標題猜關鍵字查一次跨 repo 搜尋,把可能撞到的
  舊票(例如 Arcrun#100)直接列進擋下的訊息裡

測試:scripts/test-ticket-api-bypass-guard.sh 24/24 通過(v1 的 13 條 + 本次
新增 11 條,含一條打真實 Gitea 網路重演 Arcrun#100 重複主題偵測)。

plugin.json 0.5.0 → 0.5.1;version-consistency 會在合併打 tag 那一刻才對齊
(release-tag-guard.sh 的既定分工:打 tag 是總管驗過整個 milestone 之後的事)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 16:19:06 +08:00
Leo b1830053e5 dispatch-format-guard 修兩處:規則自己的合格範例會被自己擋,禮貌收尾不該算違規
inkstone/ISEP#65:leo 發現「寫完 ISEP#30 那條規則之後,同一天總管又犯了 8 次」,
派來查「現行閘為什麼抓不到」。查證結果分兩層:

━━ 真正在跑的閘其實是 dispatch-format-guard.sh,不是 no-ticket-no-dispatch.sh ━━
no-ticket-no-dispatch.sh 的殼驗證早就是已知行為(只驗有沒有一行【工單】),
但 ISEP#30 已經為此新增了 dispatch-format-guard.sh 做內容判定,測試 19/19 通過。
問題是它有兩個沒被那 19 條測資蓋到的洞:

1. **regex 洞(本體 bug)**:頂層 CLAUDE.md 規定的合格格式帶全形括號——
   「【工單】owner/repo#N(→ comment M)」。_COMMENT_RE 只吃「→ comment M」
   本體,兩側括號沒被算進去,殘留括號讓 _REF_RE 比不過,於是**規則自己定義
   的合格範例會被自己的閘擋下**(半形括號 `(...)` 同樣會中)。用今天派我這張
   票的那份派工單原句實測,改之前 exit=2「票號形狀不對」。
2. **零容忍過頭**:純禮貌收尾(「謝謝」)跟「記得先讀 CLAUDE.md」這種真內容
   一樣被當「派工單不只有票號」擋下。ISEP#65 test 4 明講這四種不該擋
   (只有票號/空行/「謝謝」/括號包住的 comment 格式),優先做成低誤鎖。

判斷:不改 no-ticket-no-dispatch.sh、不另立第三支閘——dispatch-format-guard.sh
已經是「另立一支」的正確位置,這兩個洞在它自己的地盤上補。

修法:
- _COMMENT_RE 兩側括號(全形/半形)都設可選
- 新增 _COURTESY_CLOSERS 白名單(謝謝/多謝/感謝/辛苦了…)+ _is_courtesy_closer(),
  只在圍欄外生效,判準仍是「整行清乾淨標點後完全相等」不是「包含」——
  白名單不是黑名單,猜漏頂多誤鎖一次,不會反過來放走真內容(③g 測資證明)

測試:hooks/tests/dispatch-format-guard.test.sh 19→33 條,全過。新增:
- ③b/③c 全形/半形括號格式(規則自己的例句)
- ③d/③e/③f ISEP#65 test 4 的三種不該擋
- ③g 白名單邊界(禮貌詞混真內容裡照樣算數,防止白名單被誤用成漏洞)
- ⑮b–⑮i:leo 點名的「寫完規則後又犯的 8 次」當回歸樣本,內容是從
  ISEP#60/#61/#64、Arcrun#142/#144/#165、arcrun-rag#104、InkStoneCo#102
  的真實票內文摘錄(見 hooks/tests/fixtures/README.md 記載來歷),
  不是想像出來的例子;8 種形狀全部驗證會被擋
reply-identity.test.sh 11/11 仍全過(共用 dispatch_parse.py 沒有回歸)

升版 0.5.0 → 0.5.1(改完不升版沒人吃得到;check-version-consistency.sh
在本分支照慣例是紅的,tag 於 merge 時打)。

另查:.shell-payload/ 整個被 gitignore(scripts/vendor-to-shell.py 產物),
不是 git 分發的一部分——「這支閘會不會被吃到」取決於消費端有沒有重跑
plugin update/vendor-to-shell.py,不是這個 repo 委交的內容缺漏,故不在
本票改動範圍內,僅記錄供總管排查用。

未動 no-ticket-no-dispatch.sh(判斷見上,職責保持不重疊,兩支閘互斥見
dispatch_parse.py 的 has_ticket_marker 分岔)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:25:17 +08:00
Leo 770a0ee824 移除自造的待驗單機制,改由 Gitea 原生三格承接(inkstone/ISEP#60)
subagent-claim-worksheet.sh(SubagentStop 產待驗單)與 claim-verify-police.sh
(Stop 攔收工)這一套整組移除。它們守的東西違反 D58(不要硬做平台不支援的機制)
——「待驗單」只是一張沒有狀態、沒有持有人的 markdown,必然退化成雜訊。

改由 inkstone/ISEP#59/PR #58 的三個 Gitea 原生欄位承接同樣的情境:
子票相依(存在嗎/做完了嗎)、s/* tag(卡在哪)、指派(誰該動)。

- hooks/hooks.json:移除 Stop/SubagentStop 兩條註冊
- hooks/lib/path-resolve.sh:拿掉已刪檔案的註解引用
- docs/hooks-inventory.md、README.md:更新閘數量(48→46 檔、59→57 條註冊)
- docs/governance/sdd-gitea-governance.md:E12/E14 標記現況,新增 §8.4 說明
  移交對象;PR #58 未 merge 前這兩條實質仍是待建,誠實標記
- .claude-plugin/plugin.json:0.5.0 → 0.6.0(版本沒動=plugin update 是 no-op)

未動 docs/governance/DIVERGENCE-v0.5.0-to-v0.6.0.md——那是 2026-08-20 的
時間點快照(意見書),修改它等於竄改歷史記錄,不在本票範圍。

未動 InkStoneCo/.claude/pending-verification/{done,done-20260826,verified}/
三個歸檔目錄——它們是 InkStoneCo repo 裡的已提交檔案,跨 repo 且需要
InkStoneCo 自己的 git 流程處理,超出本票(ISEP repo)範圍,留給總管判斷。

測試:scripts/test-*.sh 全數 6 支通過;hooks/tests/*.test.sh 與 pristine
origin/main 基準比對,失敗特徵完全一致(環境既有問題,非本次改動引入);
claude plugin validate . 通過。check-version-consistency.sh 目前會報不一致
(0.6.0 vs tag v0.5.0)——這是預期的,等總管收斂 release 打 v0.6.0 tag 時解決。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 12:38:31 +08:00
Leo 9d20ef925e subtask 指派了人就一定要寫下一步——不然新子票一開出來就是「棒子沒有下一步」
實跑 ticket mine 時自己抓到:剛用 subtask 開的 #143 顯示
「 沒有人寫下一步」——工具自己造出了它要消滅的那個狀態。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 12:15:20 +08:00
Leo b46bc5e44c dogfood 抓到自己的漏:交棒標記只認第一行,不認「內文提過」
實跑自己那則交件報告時,閘沒有擋——因為報告裡**引用了一段 ticket mine 的輸出**
(那段有 🏃),整則就被豁免掉了,而它裡面真的有「等雲端那半出貨」。
⇒ 同 inkstone/InkStoneCo#23 那個病:複述關鍵字被當成本人。
ticket handback 寫的交棒留言 🏃 一定在第一行,引用別人的輸出則不會 ⇒ 錨在第一行。

回歸測試 +2(20/20 → 22/22)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 12:13:27 +08:00