Commit Graph

87 Commits

Author SHA1 Message Date
Leo 2876cb295d 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 17:59:43 +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
Leo 0b6af15f1a 補:實測探針票的去處,以及 close/* 七類沒有一類裝得下它
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 12:08:48 +08:00
Leo e474b4cb6f 棒子不會掉:子票相依+tag+指派三格,全部用 Gitea 原生欄位
leo 2026-08-27:「子票相依是 gitea 原有機制,改 tag 和指定也是,這些全部都要」

08-26 掉的那一次(arcrun-rag#136 comment 4267「等雲端那半出貨才驗得了」躺了
14 小時)三格全缺:沒有子票、tag 沒動、沒有指派給任何人。根因不是誰忘了,
是那件事沒有落在任何一個「撈一次就看得到」的欄位上。

實測(Gitea 1.26.4):子票還開著時關母票 → HTTP 412
"cannot close this issue or pull request because it still has open dependencies"
⇒ 相依是平台保證的硬擋,不是提醒。跨 repo 也成立(201)。

- scripts/ticket 新增三個動詞:subtask(長子票+掛相依)/
  handback(指派+改 tag+寫下一步,一個動作)/mine(撈一次看棒子在誰手上)
- hooks/comment-carries-task-guard.sh:留言帶未完成的未來式卻沒開子票 → 擋一次
- hooks/baton-handback-guard.sh:一條線收工,三格缺哪一格當場說出來(提醒不擋)
- docs/governance §16:三個維度/粒度(傾向多開票)/既有票只從今天起適用/
  與 s/* 的關係/實測輸出
- 測試 20+10 全綠,用 fixture 跑不打網路、不在票池留測試票

📌 踩到並記進閘的檔頭:hook 裡比對中文一律用 python3 的 re,不要用 grep 的字元類
(等[^。]{0,20}出貨 在真實 hook 呼叫路徑下對「等雲端那半出貨」不匹配,閘靜默失效)

票:inkstone/ISEP#30(comment 4334/4335/4346/4347)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 12:05:53 +08:00
Leo 8e28041ba4 merge: 派工單只剩票號——閘從驗「有沒有票號」改成驗「是不是只有票號」(ISEP#30)
leo 2026-08-27:「這些話票上都沒有,你根本沒照規則做事,你的 hook 讓你這樣搞?」
四條追加:output parser 分點規定格式/散文警察抓不到/描述寫進票留歷史/subagent 回覆要表明身份。

總管複驗過才併(自己跑的):
- dispatch-format-guard.test.sh  19/19
- reply-identity.test.sh         11/11
- 兩支都涵蓋「該擋」與「不該擋」兩側;訊息點得出違反哪一條規則,不是籠統的「格式不對」

版本 0.3.9 → 0.5.0(產物按版本號分資料夾,不升版新閘不會被載入)。

🔴 仍是 report 不是 deliver 的一格:「新 session 真的會叫它」要併版後開新 session 才驗得了。
v0.5.0
2026-08-27 11:55:24 +08:00
Leo 3bc7f3c5f5 派工單只剩票號——閘從驗「有沒有票號」改成驗「是不是只有票號」
leo 2026-08-27(inkstone/ISEP#30 comment 4322/4325/4327):
「這些話票上都沒有,你根本沒照規則做事,你的 hook 讓你這樣搞?」
「你用一個 output parser 把你給 subagent 的指令規範,分作幾點,每一點規定格式,
  照這種散文寫法根本無法迭代」「警察也不能抓」
「交件方式不需要寫,定義在原則裡⋯⋯每次都一樣提取出來變成共通規定」
「(那些 session 事實)這些為什麼不寫到票裡?」「subagent 回覆時要表明身份」

病根:no-ticket-no-dispatch.sh 驗的是「有沒有一行【工單】owner/repo#N」,
而規則的原文是「派工單只寫票號」。⇒ 把 40 行任務全寫在 prompt 裡、票號補一行,
閘照樣放行。2026-08-27 一天內這樣做了 5 次,每次票上都沒有那份任務。
規則存在,閘只驗了它的殼——同款第 N 次(history-first/KBDB-first/stage-first)。

新增 hooks/dispatch-format-guard.sh(PreToolUse Task|Agent),兩件事:
- 擋:【工單】以外還有實質內容就 exit 2,並指出那些內容該搬去哪
  (每次都一樣 → 共通規定;這次才知道 → 寫進那張票。
   判準「這句話換一張票還成立嗎?」)
- 注入:合規的派工自動把共通規定送給收工方(交件方式、不准 push main、
  org 是 inkstone…)——這是「派工單只剩票號」能成立的前提,
  leo 的驗收條件之一就是「收工方沒讀派工單也知道要貼回原票」

判準是結構不是文字(leo 2026-08-17 那條檢驗):問的是「這一行是不是【工單】欄位」
——在不在,不是寫什麼。hooks/lib/dispatch_parse.py 全檔零個「命中某個詞就違規」的比對。
⇒ 也因此不需要語意判官:免費、瞬間、每次結果一樣。

新增 hooks/reply-identity-guard.sh(PreToolUse Bash)+ scripts/ticket 內建檢查:
票上每一則留言第一行要有【身份】(總管/subagent/leo)。貼留言有兩條路,兩條都封
——ticket-api-bypass-guard 是刻意放行「對既有票留言」的,只封正門等於沒封。
實害:多條線並行時總管寫的診斷被當成 subagent 的結論,而其中一則是錯的。

規約寫成文件:docs/governance/dispatch-and-reply-format.md
(§2 那段就是被注入的那份共通規定本體——只有一份,改那裡等於改所有派工)

實測(離線、不打網路、不花錢):
  hooks/tests/dispatch-format-guard.test.sh   19/19
  hooks/tests/reply-identity.test.sh          11/11
測資裡的 B⑨ 是真跡:產生 ISEP#30 這條線的那一次派工,一字未改。
另 4 份 leo 點名的違規派工拿不回來了——它們住在 prompt 裡,agent 一停就沒了,
這件事本身就是這條規則的證據(見 hooks/tests/fixtures/README.md,不用想像的例子替補)。

升版 0.4.0 → 0.5.0(產物按版本號分資料夾,不升版新閘不會被載入)。
tag 照慣例打在 merge commit 上,所以這條分支上 check-version-consistency.sh 是紅的。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 11:35:03 +08:00
Leo 8e7e265589 merge: 閘掛在收工後,而 leo 是在被問到的當下被打擾——補上那一刻的攔截(InkStoneCo#55)
leo 2026-08-26:「今天已經好幾次問我,為什麼 hooks 沒有攔下來?」

總管複驗過才併:
- 掛點:PreToolUse / matcher AskUserQuestion(自己從 hooks.json 解出來的)
- 封動作不封文字:整支 261 行零個判擋用的正則,決定 exit 2 的只有判官 verdict;
  判官掛掉/回空/不在 PATH 一律 fail-open
- 測試自己跑過 14/14(含「樹要不要捲」那種真人閘必須放行的案例)
- 版本 0.3.9 → 0.4.0:第一版忘了升版,而產物是按版本號分資料夾的
  ⇒ 不升版=新閘永遠不會被載入。退回補後已驗產物資料夾長得出來。

🔴 仍有一格是 report 不是 deliver:「新 session 真的會叫它」驗不到——
AskUserQuestion 官方明載 subagent 不可用,它撞不到自己這道閘。
那一格要 leo 開新 session 才驗得了。
v0.4.0
2026-08-26 22:43:34 +08:00
claude-code e0557334bc 閘掛在「收工後」,而 leo 是在「被問到」的當下被打擾——補上那一刻的攔截
leo 2026-08-26:「今天已經好幾次問我,為什麼 hooks 沒有攔下來?」

實查:總管問 leo 走的動作是 AskUserQuestion,而 hooks.json 裡它出現 0 次,
沒有任何 matcher。判準其實早就寫好了——self-drive-police / self-drive-judge
用的就是同一套四題公式——但那兩支只掛在 Stop 與 SubagentStop,
是回合結束後才跑的。問題早就送到他眼前了,事後再反問「你查過了嗎」,
成本已經轉嫁出去。判準對了,時機錯了。

新增 hooks/ask-user-question-guard.sh(PreToolUse / AskUserQuestion):
- 觸發條件是那個動作本身,不是任何句型或關鍵字——全檔零個判擋用的正則,
  換句話說閃不過去,講得謹慎也不會被多罰(leo 2026-08-17 文字層封路的檢驗)
- 進來之後用四題公式的 haiku 判官分「該問 / 不該問」,命中任一題一律放行
- 同一個問題只擋一次(雜湊戳記):判官誤判時重送即過,
  leo 該收到的問題不會因為一支閘而永遠送不到
- 判官掛掉/沒網路/claude 不在 PATH 一律 fail-open,壞掉等於它不存在

版本 0.3.9 → 0.4.0。這不是儀式,是傳輸機制本身:產物按版本號分資料夾
(~/.claude/plugins/cache/inkstone/isep/<版本>/),版本沒動就不會長出新資料夾,
這支閘一個 session 都載入不到。docs/TESTING.md 開頭那段講的就是這件事,
而第一版我漏了——總管複驗時量出來的。

假設(沒有前例可循,先裁再記):跳 0.4.0 而不是 0.3.10。理由是這一版第一次
掛上 AskUserQuestion 這個事件面,是新能力不是修補;而且 0.3.10 在
plugin 快取目錄的 ls 裡會排到 0.3.1 旁邊,肉眼不好認。錯了打回,改號很便宜。

順手修掉自己寫出來的一個坑:訊息原本用沒加引號的 heredoc,
反引號被當命令執行,wiki 路徑與豁免指令兩行變成空白(閘照擋,只看離開碼看不出來)。
已收成 mistakes.md 一條,並由 ⑩b 這條測試守著。

實測:
- hooks/tests/ask-user-question-guard.test.sh      14/14(離線,不花錢)
- hooks/tests/ask-user-question-guard.live.test.sh 9/9 連跑三次(真的叫 haiku)
  A 群 5 條真人閘(花錢/不可逆/跨專案結構/品味方向/物理人閘)全部放行,誤攔 0
  B 群 4 條純技術路徑選擇全部擋下
- 既有 7 支測試與改動前逐條對照,結果完全相同(沒有被我弄壞)

順手對帳:plugin.json 與 marketplace.json 的描述寫「43 支機械閘、53 條註冊」,
實際數過是 46/56(含本次新增這支)。docs/hooks-inventory.md 一併更正。

【工單】inkstone/InkStoneCo#55
2026-08-26 22:36:08 +08:00
claude-code d4547bfec3 Merge pull request '信標自己講「這一份是誰」——vendor 還是 plugin 快取' (#55) from fix/beacon-says-which-copy into main v0.3.9 2026-08-23 13:03:37 +00:00
Leo 2f43ecc346 信標自己講「這一份是誰」——vendor 還是 plugin 快取
leo 的雲端驗收整整卡了一輪在這個問題上:同一台機器可能有兩份 ISEP,
兩份都會印信標,版本號一樣時分不出誰在說話。而「閘從哪一份走」正是決定
「另一份能不能拆」的唯一判準。

總管上一版設計的判準(叫雲端跑 env | grep CLAUDE_PLUGIN_ROOT)也是錯的:
那個變數是 hook 呼叫當下才注入的,在 Bash 工具的 env 裡本來就看不到
⇒ 空輸出不代表沒載入,那個探針從一開始就答不了這題。

改法:讓路徑自己講。快取在 plugins/cache/ 底下,vendor 的在 repo 的 .claude/ 底下。

三向實測:
  /root/.claude/plugins/cache/inkstone/isep/0.3.9  → 來源:plugin(marketplace 裝的)
  /home/user/inkstoneco/.claude/isep               → 來源:vendor(repo 裡的複製本)
  其他路徑                                          → 來源不明(不假裝知道)

inkstone/InkStoneCo#57
2026-08-23 21:02:45 +08:00
claude-code 1b95388a0f Merge pull request '身為總管,我要這道閘擋的是「宣告」而不是「提到宣告」,我才不會一舉報 bug 就被它擋下' (#54) from fix/factory-idle-guard-blocks-actions-not-mentions into main v0.3.8 2026-08-23 09:58:41 +00:00
Leo e3d05df341 再修兩類:主詞是 leo 的下一步、票號放寬把閘變鈍——都是真 transcript 量出來的
交付警察擋回來是對的:前一顆只驗了「我自己造的假 transcript」。
改用本機一條 2068 行的真 session(26 個真實回合終止點)重驗,當場多找到兩個問題:

① 主詞是「你」的下一步,被當成我的宣告。
   舊閘在 26 個真實回合裡擋了 2 次,兩次咬的都是我在交代 leo 該做什麼:
     「下一步還是那一個動作:你把 feat/... 併進 main」
     「## 你下一步(兩招,先便宜的)」
   ⇒ 這是全新的第四類誤攔,我原本一向都沒列到。
   修法是主詞檢查(誰要動手),只掛在「下一步」這條 alternative 上;
   用 finditer 逐個檢查前 8 字有沒有第二人稱,蓋得到「你的下一步」這種
   單字 lookbehind 蓋不到的變體。「你點頭我就做」主詞本來就是我,不受影響。

② 票號從「宣告句附近」放寬成「整段」,把閘變鈍了。
   真數據:26 個真實回合有 20 個是靠「文中某處剛好有票號」放行的——
   而報告幾乎一定會提到票號 ⇒ 這道閘在實務上等於不會響。
   當初放寬是因為票號常寫在行內 code 裡,剝掉就找不到。
   ⇒ 改成**等長**替換(蓋成同樣長度的哨兵而非刪除),位移就能對回原文,
     locality 與「行內 code 裡的票號也算數」兩件同時成立。
   收緊後的判定分佈:21 no-declaration / 3 dispatched / 2 ticket-referenced
   (原本是 20 ticket-referenced / 3 no-declaration / 3 dispatched)。

真 transcript 實測:舊閘擋 2 次(兩次都是誤攔)→ 新閘擋 0 次。
測試 33 向(+6):舊版 21/33 → 新版 33/33。
版號 0.3.7 → 0.3.8。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 17:54:17 +08:00
Leo a594decb78 稼動率警察改成擋「宣告」,不擋「提到宣告」
2026-08-23 雲端驗收連續三次被這道閘誤攔,三次都不是宣告意圖:
① 否認自己有下一步 ②引用閘自己的訊息 ③貼閘自己的正則原始碼舉報 bug。
而訊息教人走的「選項③:說明它在等什麼」,程式碼裡根本沒有那條分支
——唯一走得通的路是不寫那三個字,正是同一則訊息明文禁止的動作。

四個真兇,沒有一個是「例外沒列夠」:
(a) DECL 會匹配裸的「下一步」三個字(每一節都可選 ⇒ 退化成關鍵字)
    ⇒ 收緊:每一條 alternative 都必須接到動作動詞才算命中
(b) 只剝 > 引言與長「」,不認 code fence 與行內 code ⇒ 引用被當成主張
    ⇒ 引用性標記整段換成哨兵(不是刪掉):內層宣告消失、外層句構留著
      ——刪掉正是 08-17 漏掉「回『規劃』我就派人」的原因,兩個方向一起修
(c) 取 blocks_text[-1],但那則文字後面可能還有 tool_use ⇒ 宣告其實兌現了
    ⇒ 只看「最後一個動作之後」的文字;收尾在動作上就不觸發
(d) 訊息承諾的出路只有兩條真的存在
    ⇒ 出路③ 給一個機械形式 ⏸ 等:<在等什麼>(白名單標記,要刻意寫,留痕)

方向刻意與「再加幾個關鍵字例外」相反——例外清單會越加越長、越長越誤攔。
守 leo 的封路哲學:紅線寫得越細,命中關鍵字的機率越高 ⇒ 那些閘在懲罰謹慎。

順手:擋下與放行都留痕(InkStoneCo#48:只記擋下的話分母未知);
log 目錄不在時安靜跳過,不再噴 redirect 錯誤到 stderr。

測試 hooks/tests/factory-idle-guard.test.sh 27 向,誤攔與漏攔兩個方向都測:
舊版 18/27(9 敗)→ 新版 27/27。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 17:42:22 +08:00
claude-code 5789f0a917 Merge pull request '信標改數「真的被註冊的閘」——它一直多報一支樣板' (#53) from fix/beacon-counts-registered-gates into main v0.3.6 2026-08-23 08:52:30 +00:00
Leo b1f399f8b9 信標改數「真的被註冊的閘」——它一直多報一支樣板
leo 的雲端驗收(2026-08-23)抓到:信標說 44 支,`ls hooks/ | wc -l` 是 49,對不上。
追下去是 `ls "$ROOT"/hooks/*.sh | wc -l`:
把 `pre-write-guard.template.sh`(樣板,不是閘)與兩支沒掛註冊的輔助檔一起算進去。

🔴 這個數字是 leo 判斷「這個 session 到底有沒有閘」的唯一介面——**多報就是假綠**。

改成數 `hooks.json` 裡註冊過的唯一 `.sh`;hooks.json 讀不到才退回檔案數(且排除樣板)。

實測兩向:
  正向(真的 plugin 根目錄)      → 42(與 hooks.json 註冊數一致)
  反向(沒有 hooks.json 的假根)  → 2(三個檔裡有一個是 .template.sh,沒算進去)

查過歷史:本檔自 daa1674 建立以來只有那一版,沒有別的分支修過這段。

inkstone/InkStoneCo#57
2026-08-23 16:52:01 +08:00
claude-code a446ad6d1d Merge pull request '推 main 的戳記改綁「push 真正的目標 repo」,不再綁 hook 自己的 cwd' (#52) from fix/push-guard-cross-repo-stamp into main v0.3.5 2026-08-23 07:31:01 +00:00
Leo 41c56acd32 推 main 的戳記改綁「push 真正的目標 repo」,不再綁 hook 自己的 cwd
跨 repo 交辦時(總管站在 A repo,要推 B repo 的 main)main-and-prod-push-guard
的戳記機制永遠對不上:HERE 讀的是 hook 自己的 cwd(=session 的真身,不會變),
WANT 是總管替目標 repo(B)寫進戳記的路徑——兩者結構性地不可能相等,不是
判斷錯,是這個情境在舊模型裡根本不存在(inkstone/ISEP#30 comment 3949,
脈絡 inkstone/InkStoneCo#57,2026-08-21 實撞)。

新增 hooks/lib/push_target_dir.py:純 tokenize(不執行任何指令)解析指令裡
`cd <path> && git push` 或 `git -C <path> push` 真正會落地的目錄,對多層 cd
鏈與子殼(`(cd A && ...); git push` 這種子殼 cd 不能外洩出去)都做了範圍化——
這條範圍化是防穿透的關鍵,不是順手:沒有它,`(cd A && true); git push`
會被誤判成推向 A,讓替 A 開的舊戳記錯誤地放行推到殼外真正的目標。解不出來
一律退回舊行為(hook 自己的 cwd),維持 fail-closed 方向不變。

順手修掉補測時自己抓到的另一個洞:`(git push origin HEAD:main)`——單純加一層
括號——舊版目的地判斷完全偵測不到,整段直接放行,跟戳記無關。成因是截斷
refspec 尾巴的 sed 只認 `;`/`&`/`|` 三種字元,沒算到 `)`;補上即可,git 的
refspec 語法本來就不允許出現 `)`,這裡截斷永遠安全。

綁 repo+單次用完即丟兩條 2026-08-11/12 用血換來的性質完全沒有鬆動:只是把
「現在人在哪個 repo」問得更準,比對邏輯一個字沒動。

實測:
- hooks/tests/main-and-prod-push-guard.test.sh 舊有 8 向:8/8
- scripts/test-main-and-prod-push-guard.sh 舊有 11 向:11/11
- 新增 hooks/tests/main-and-prod-push-guard-cross-repo.test.sh 17 向
  (跨 repo 正向/反向不准鬆/git -C/子殼範圍化/括號洞/單次用完即丟/
  900 秒逾時/空戳記/既有行為零回歸):17/17

本輪只驗證,未拿去放行任何真實推送;plugin.json 隨慣例 bump 0.3.4 -> 0.3.5
並重跑 vendor-to-shell.py(.shell-payload 為 gitignore 產物,不入版控)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 14:18:28 +08:00
claude-code 2eb9b2aaaa Merge pull request '身為總管,我要閘擋的是動作不是字面,我才不會一晚被自己的閘擋六次' (#51) from fix/push-guard-target-not-substring into main v0.3.4 2026-08-20 17:30:46 +00:00
Leo 67dae3b814 推送閘改成判目標,不判整條指令裡有沒有那個字
一個晚上誤攔六次,全都不是在推預設分支:
  ① checkout -b 建新分支時把預設分支寫在後面,再推那條新分支
  ② gh pr create 指定 base——根本不是 git push
  ③ 推 tag(refs/tags/…)
  ④ 推 feature 分支(帶 -u)
  ⑤ 它擋住了我用來**測試它自己**的那條指令
  ⑥ 它擋住了這一筆的 commit——因為 message 裡引用了那幾個字

leo 2026-08-17 早就講過這個形狀:文字層封路必敗,
「紅線寫得越細,命中關鍵字的機率越高 ⇒ 那些閘在懲罰謹慎」。
舊版掃整條指令字串,正是文字層。

改成解析 push 的目標 refspec:
  旗標跳過/第一個非旗標=remote/a:b 取 b/refs/tags/* 不算分支
  一個 refspec 都沒給,才退回看當前分支

八向實測(hooks/tests/main-and-prod-push-guard.test.sh,8/8):
  五種該放行的(今晚誤攔的原形狀,含分支名帶 domain 那種)全過
  三種該擋的全擋

中途自己抓到一個 bug:tag 被跳過後目標清單變空 → 退回猜當前分支
⇒ 當前分支剛好叫預設名時誤擋。改成看到 refspec 就不退回猜測。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:29:55 +08:00
claude-code 43c328d26f Merge pull request 'feat/milestone-must-have-due' (#50) from feat/milestone-must-have-due into main v0.3.3 2026-08-20 17:26:08 +00:00
claude-code bcb736ed19 Merge pull request 'fix/worksheet-dedup-by-content' (#49) from fix/worksheet-dedup-by-content into main 2026-08-20 17:26:06 +00:00
Leo 6772ca67d3 每個里程碑都要有真的期限,9999 也擋
leo 2026-08-21:「以後所有的 milestone 限制時間」「你根本沒有時間概念,浪費一整天」

實查七個 open milestone:六個期限是 9999-01-01、一個空白。
9999 比空白更糟——盤點時每一格看起來都有值,
於是沒有人發現這裡從來沒有時間壓力。七個已全部改成真日期。

新增 hooks/milestone-due-guard.sh,四向實測:
  無 due_on → exit 2
  due_on 帶 9999 → exit 2
  真期限 → exit 0
  只是讀 milestone → exit 0

規範補 M4.8(怎麼定期限、過期只對帳不自動關)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:25:12 +08:00
Leo 1b5551274a 待驗工作單改用宣稱內容去重,驗過的不再冒出來
stamp 原本雜湊「交件路徑」⇒ 同樣的宣稱每回合生一個新檔名,
而且不知道總管已經驗過了。

實際發作(2026-08-21 一個 session 內):同兩條 sdd-guard 宣稱連生四張單
1c97d461/fcb285dc/256de849/394b97ae——驗掉一張下一回合又冒一張,
Stop 閘於是變成永遠過不去。閘在懲罰有照做的人。

改成雜湊宣稱內容本身,並在寫檔前檢查 verified/ 底下有沒有同名。

雙向實測:
  兩次不同 transcript、同樣宣稱 → 只生 1 個檔
  移進 verified/ 後再跑         → SKIP:already-verified,沒再冒出來

過程中兩個自己的錯,記下來免得下次重犯:
  ① 先猜了變數名 blocked/ok/nogo,實際是 ok_hits/ng_hits
     ——猜錯的話 _claims 永遠是空的、悄悄退回舊行為,不會報錯
  ② 測試資料先寫成 role:assistant,再寫成 role:user 都不觸發
     ——它要的是 <task-notification> 裡的 <result>
     前兩次「0 個檔」我差點當成去重成功

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:18:16 +08:00
claude-code 1920d4cb06 Merge pull request '身為 leo,我要雲端 clone 下來就有閘,我才不用先處理憑證' (#48) from fix/b4-real-probe into main v0.3.2 2026-08-20 17:11:52 +00:00
Leo 47ed778cc4 改走「直接複製進薄殼 repo」,並修掉一支會偷跑指令的閘
leo 2026-08-21:「你應該把 Plugin 直接裝進 Github repo,從本地直接複製就好了」

為什麼這條對:雲端 session 是 fresh clone 薄殼 repo,而 setup script 讀不到
環境變數。走 marketplace 就得同時處理憑證、repo 可見性、環境快取三件事——
今天這三件各失敗過一次。複製進 repo 之後,clone 下來就有,沒有任何前置條件。

新增 scripts/vendor-to-shell.py:
  把 hooks/skills/commands/scripts/.claude-plugin 整份複製到 .claude/isep/,
  並把 54 條 hook 註冊改寫成薄殼裡的絕對路徑。
  保留 CLAUDE_PLUGIN_ROOT 這個變數名(44 支閘內部靠它定位自己的 lib/),
  只是把它指到複製過來的那份。

冒煙測試(54 條註冊全跑一遍,找路徑壞掉的):
  第一輪 4 條壞 → 3 條是 log 目錄不存在(已補建 .claude/hooks/)
                  1 條在真身也一樣壞 ⇒ 不是複製造成的
  第二輪 0 條壞

順手修掉那支既有 bug:wiki-first-search.sh
  python3 -c 用雙引號,註解裡的反引號被 shell 當指令替換
  ⇒ 這支閘每次觸發都在偷跑 bge-m3 與 head changelog.md。
  改成全形引號後實測靜默 exit 0。
  (crude grep 掃出 6 支疑似,但冒煙測試證明只有這一支真的中——
    再一次:證據勝過掃描。)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 00:51:00 +08:00
Leo 03d9782f22 查了官方文件才發現:setup script 根本讀不到環境變數,而 exit 1 會鎖死 session
leo 問「寫以前為什麼不查」——沒有藉口,我假設了兩件事都沒查,兩件都是錯的。

① setup script 讀不到 Environment variables
   官方原文:Each session copies the environment's values once, at startup,
   into ordinary environment variables
   而 setup script 是 before Claude Code launches 跑的 ⇒ 注入在它之後。
   ⇒ 把 token 放進 Environment variables 再要 setup script 讀,永遠讀不到。
   證據吻合:leo 的變數設對了、值也跟本機同一把(長度 40、頭尾一致),
   而腳本回報找不到。

② exit 非零會讓整個 session 開不起來
   官方原文:Exit zero: if the script exits non-zero, the session fails to start.
   前一版為了大聲失敗用 exit 1 ⇒ 直接造成 Session initialization failed。
   ⇒ 現在一律 exit 0,失敗寫進 /tmp/.isep-setup-report。

本機三向實測:
  無變數(雲端真實情況)→ exit 0,印出說明,不擋 session
  有變數(未來平台若改行為)→ 設 git 認證並驗證
  ls-remote 加 timeout 45(本機曾掛住近 4 分鐘)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 00:39:48 +08:00
Leo 36d8e05a32 認證驗證加 timeout:掛住比失敗更糟
2026-08-21 本機隔離測試:git ls-remote 掛住近 4 分鐘不回。
setup 卡在那裡看起來像還在跑,不會有人知道它壞了。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 00:34:34 +08:00
Leo 135637291c B4 的探針我自己沒撞過,實撞後發現它根本不會擋
v0.3.1 我把 B4 從 git tag 換成「寫 __GITEA_TOKEN__ 進 /tmp/x.md」,
說它會被 credential-only-guard 擋下。今天實撞:exit 0,閘完全沒反應。

原因:那支閘刻意豁免 .md/docs//wiki/(文件本來就要能談論這些字串)。
它只管會被執行的產物:*workflow*/.yaml/.yml/installer/worker.js/wrangler。

改成 /tmp/wf.yaml 後三向實測:
  違規 workflow.yaml 帶佔位符       → exit 2 credential 鐵律攔截
  同檔用 {{credential.gitea_token}} → exit 0(正確放行)
  .md 談論同一個字串                 → exit 0(正確豁免)
薄殼指標端同樣實測:真身在+違規 → exit 2。

這是同一個病的第四次:修假綠的那一刀,自己又是沒撞過就寫。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 00:33:44 +08:00
claude-code d3061585e8 Merge pull request '身為 leo,我要雲端 env 檔能直接產在桌面,我才不用去翻隱藏目錄' (#46) from feat/cloud-env-outdir into main 2026-08-20 15:13:38 +00:00
Leo 9099c3f533 make-cloud-env.sh:輸出路徑可指定,且不再對使用者指定的目錄動權限
leo 要產在桌面。兩個改動:
- OUT_DIR 可被環境變數覆寫(OUT_DIR=~/Desktop bash scripts/make-cloud-env.sh)
- chmod 700 只在「這個目錄是我們自己造的預設位置」時才做
  原本無條件對 $OUT_DIR chmod 700,一旦 OUT_DIR 指到既有目錄
  (例如 ~/Desktop)就會改動使用者目錄的權限。產出檔本身仍是 600。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 23:13:25 +08:00
Leo 5bceb03478 v0.3.1
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v0.3.1
2026-08-20 23:03:05 +08:00
claude-code 4e73b8b03d Merge pull request '身為 leo,我要雲端驗收步驟在閘死掉時真的變紅,我才不會再被三個綠燈騙一次' (#45) from fix/testing-b-section-discriminating into main 2026-08-20 15:03:04 +00:00
Leo 291787eaaa TESTING.md B 段整段換掉——舊版在閘全滅時會回綠
2026-08-20 雲端實證:B2/B3/B4 三步在 plugin 完全缺席時**全部通過**。
一個在閘死掉時也會給出正確答案的測試,不是測試。

三處各自為什麼假:
- B2「跑 claude plugin list」→ 要它自己報自己,沒有獨立證據
- B3「Skills (9) / Hooks (5)」→ 那組數字剛好是薄殼自己的 .claude/ 產生的
- B4「git tag -a v9.9.9」→ git tag 在三支閘的白名單裡

新版三步都改成有鑑別力的:
- B2 找 v0.3.0 新增的載入信標(那支腳本住在 plugin 裡,沒載入就不可能發聲)
- B3 看 setup script 的自我驗證輸出(v0.3.0 新增)
- B4 改用金鑰佔位符觸發 credential-only-guard(不在任何白名單上)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 23:02:47 +08:00
claude-code 3a951210a5 雲端零閘的兩個真因:setup 不自驗+沒有 release 撐版本號 (#44) v0.3.0 2026-08-20 14:59:55 +00:00
Leo daa1674a20 雲端零閘的兩個真因:setup 從不驗證自己+沒有任何 release 撐版本號
2026-08-20 雲端 session 的閘全滅,而三個驗證步驟全部回綠。
今晚在隔離 HOME(GIT_CONFIG_NOSYSTEM=1)重現,把兩件事分開了:

① setup script 的寫法是對的
   裸環境失敗、加了 url.insteadOf 就成功 —— x-access-token 這個使用者名稱
   Gitea 也接受。所以先前我對 leo 說「URL 重寫沒作用到 marketplace 這條路徑」
   是錯的,這裡更正。
   (前兩次測試之所以誤導,是因為 /etc/gitconfig 的 macOS keychain helper
   還在幫忙 —— 「隔離 HOME」並沒有隔離系統層設定。同一個病第三次。)

② 真正的缺陷是這支腳本從不驗證自己
   設完就結束。token 沒生效也不出聲 ⇒ setup log 一片綠、
   session 開起來才發現 marketplace 拉不下來,而那時已經沒有任何線索。
   本次加兩道自我驗證:git 認證通不通、marketplace 有沒有就位,
   任一不通就 exit 1 並印出該查什麼。

③ 新增 isep-presence-beacon.sh(信標,不是閘)
   SessionStart 報「ISEP v幾 已載入、幾支閘」。
   它的全部意義是鑑別力:這行住在 plugin 裡,所以看得到就一定載入了,
   看不到就是零閘。不像 git tag(在三支閘的白名單裡,閘死了照樣過)。

④ plugin.json 0.0.0 → 0.3.0
   查清楚了:0.0.0 不是漂移,是誠實 —— ISEP 一個 tag 都沒有,從沒發過 release。
   而這正是「雲端拿不到更新」的另一半:claude plugin update 比對版本號,
   沒有 release 就永遠沒有新號碼可比。所以這一刀的收工是真的打 tag 發版。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 22:59:21 +08:00
claude-code c48495d911 Merge pull request 'fix(hooks): sdd-guard.sh 修「解析失敗仍照擋、且訊息洩漏 /nonexistent」' (#42) from fix/sdd-guard-path-resolution into main
sdd-guard 路徑解析修好+ADR 訂正(InkStoneCo#22)
2026-08-20 13:09:10 +00:00
Leo 87186585d6 fix(hooks): sdd-guard.sh 修「解析失敗仍照擋、且訊息洩漏 /nonexistent」(InkStoneCo#22)
症狀(總管 2026-08-12 實撞):寫暫存腳本進 scratchpad
(/private/tmp/.../scratchpad/foo.py)被 sdd-guard.sh 攔下,訊息印出字面的
「/nonexistent/3-specs/ 下找不到任何 SDD」。

兩個洞:
- 洞 A:scratchpad 不在任何 git repo 裡,卻被當成「repo 裡的 code 變動」誤判
  需要 SDD。改成先問 path_in_git_worktree()(見 hooks/lib/path-resolve.sh):
  不在任何 git repo 裡 → SDD 天生管不到,直接放行,不必先猜專案根。
  這個檢查放在 $_root 的 case 分岔之前、對兩邊都適用——第一版只放進「專案外」
  分支,被本次新增的 hooks/tests/sdd-guard.test.sh 抓到一個不對稱漏洞(cwd 剛好
  等於 scratchpad 祖先目錄時會漏判),改成統一檢查後修掉。
- 洞 B:舊版用內部 sentinel `/nonexistent/3-specs` 重用既有的擋下路徑,但這個
  假路徑被直接印進使用者看到的訊息。改用 RESOLVED 旗標記解析成不成功,訊息
  改用人話描述原因,不洩漏假路徑。

fail-closed / fail-open 的判準(票上明確要求回答,不能各憑運氣):
真的落在某個 git repo 裡、但那個 repo 沒有 3-specs(或沒有 active SDD)→
仍然 fail-closed(擋)。理由:這道閘存在的目的就是防止「沒有 SDD 卻能動
code」,把「判斷不出來」直接放行,等於把環境跑歪(cwd 被切走、
$CLAUDE_PROJECT_DIR 沒設)悄悄變成「這道閘關掉了、且沒人知道」——silent
bypass 的代價遠高於多打一次確認。#22 紅線亦明寫「不要把閘改成解析失敗就
放行」。

順手修的殘留 cwd 依賴:SPECS_DIR 的預設值原本是相對路徑
「system-dev/docs/3-specs」,專案內迴圈找不到時會被拿去跟 hook 執行當下的
cwd 兜;改成絕對路徑 $_root/system-dev/docs/3-specs。

同時修 ADR-0001(ISEP 自建 wiki):標題與內文原本會讓人誤解成「ISEP plugin
裝到哪個 repo,就會在那裡自建一份 wiki」,但實際查證(marketplace.json 只宣告
hooks/commands/skills、README 明文排除 wiki/docs、hooks 一律用
${CLAUDE_PLUGIN_ROOT} 讀自己不是寫別處)並非如此——那份 wiki 只是 ISEP 這個
repo自己的開發歷史,跟裝 plugin 無關。唯一真的會在某 repo 建 wiki 的
scripts/install.sh 是 system-dev-template 的獨立安裝器殘留,要手動執行,
作用對象是 cwd 不是「plugin 裝到的地方」——這多半是誤解的真正來源,已在
ADR 的「常見誤解」段說明。

驗證:
- 造出 08-12 原始事故情境(cwd=InkStoneCo、CLAUDE_PROJECT_DIR 未設、寫
  scratchpad),修前擋(印 /nonexistent)、修後放行——實測輸出見票留言。
- 造出「真的在 git repo 裡但沒有 3-specs」情境,修後仍擋、訊息不含
  /nonexistent。
- 新增 hooks/tests/sdd-guard.test.sh:8 案例全過(洞 A/洞 B/fail-open
  陷阱/單一活性違反/恰好一份 active/改文件放行)。
- 既有六套 scripts/test-*.sh 全過,無退步。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 21:03:14 +08:00
claude-code e6d183d038 Merge pull request '產生雲端 env 設定給 leo 貼(InkStoneCo#14)' (#41) from feat/cloud-env-generator into main
雲端 env 產生器
2026-08-20 12:45:50 +00:00
Leo f855d82573 產生雲端 env 設定,不要 leo 自己拼湊(InkStoneCo#14)
leo 2026-08-20:「應該是你會幫我產生正確的 env 我去貼⋯⋯不然就是我要自己拼湊」
「這些值你都有,你可以只寫名字然後 build 一個檔案給我」

總管原本的說法是「你複製設定給我、值遮掉」——那是錯的,兩個理由:
① 把拼湊的工作丟回給 leo ② 讓值經過對話本身就是洩漏面。

scripts/make-cloud-env.sh:
- 腳本裡只寫**變數名字**(NEEDED 陣列,要加變數就加在那裡)
- 值在執行當下才從既有 .env 讀(credentials-map 記的六個位置都找)
- 產物寫到 ~/.claude/cloud-env/<時間>.txt,權限 600,**刻意不在任何 repo 底下**
- 只印路徑不印值;找不到值的變數會標出來而不是靜默留空
- 產物最後一行就是刪除指令

docs/TESTING.md 補 B0 段:先跑產生器,再貼。

實跑過:1 個變數、0 個找不到、產物 45 行、權限 600、不在任何 repo。
2026-08-20 20:45:02 +08:00
claude-code 17de121017 Merge pull request 'fix(hooks): 補上 leo21c-write-guard.sh 第二、三次誤攔的修法(反向移植)' (#40) from fix/one-copy-per-gate into main
補兩個空殼閘(InkStoneCo#57):ISEP 成為完整超集
2026-08-20 12:06:51 +00:00
Leo c1d80756d7 審核補件:第二個空殼閘(irreversible_dispatch_check.py)+既有測試套
總管複驗 PR 時發現 subagent 只補了一個空殼,還有第二個同款的:

  arcrun-intent-guard.sh      → exec 不存在的 .py ⇒ 擋掉全部(吵,PR 已修)
  irreversible-dispatch-guard.sh → 同款,但寫法是
      python3 <不存在> 2>/dev/null || echo '{"verdict":"OK"}'
    ⇒ **靜默放行全部**(危險,本 commit 補)

實測同一份派工單「驗過了就把舊分支刪掉」:
  InkStoneCo 版(有 .py)exit=2 擋 / ISEP 版(缺 .py)exit=0 放行
補完後:不可逆派工 exit=2、正常派工 exit=0、合規工作流 exit=0。

系統性掃描 ISEP 全部 hooks 引用的同目錄檔案:補完後 0 個缺檔(InkStoneCo 本來就是 0)。
順帶把 InkStoneCo 的 hooks/tests/ 四支既有測試一併帶過來。

🔴 這件事很重要:若先併 InkStoneCo#64(刪掉 .claude/hooks/),
   這台機器會失去那支唯一還能用的副本——變成真的沒有那道閘。
2026-08-20 20:06:23 +08:00
Leo b790c3a78d fix(hooks): 補回 arcrun_intent_guard.py 主體——這支閘自 ISEP 建立以來就是空殼
發現於 inkstone/InkStoneCo#57/inkstone/ISEP#32:要把 InkStoneCo 本機
`.claude/hooks/arcrun-intent-guard.sh`(連同它同目錄的 arcrun_intent_guard.py)
移除、改吃 ISEP 這份之前,實測 ISEP 版才發現 `hooks/arcrun-intent-guard.sh`
呼叫的 `arcrun_intent_guard.py` 從未進過 ISEP 的 git 歷史(`git log --all` 0
命中)——`exec python3 "$DIR/arcrun_intent_guard.py"` 找不到檔案,
python3 直接噴 OS 層錯誤、exit 2,**每一次 Write/Edit/MultiEdit 都被無條件擋下**,
不是「規則判定違規才擋」,是檔案不存在導致的硬當機。

這比 kbdb_cmd_check.py 那次(PR #39)更嚴重:那次是「該擋的沒擋」(靜默放行),
這次是「不管寫什麼都擋」——方向相反,但同一個病根:hook 的 shell 外殼進了
git,Python 主體沒有。

修法:把 InkStoneCo 本機那份(唯一存在的正本)原樣搬進來。這支腳本本來就設計
成離開 InkStoneCo 語境會優雅放行(`rules_path` 不存在 → return 0),只是
之前連跑到那行都做不到。

實測 3 種情境:
- ISEP 語境(沒有 intent-rules.json):exit 0(修好前是 exit 2,任何寫入都被當機式擋下)
- InkStoneCo 語境、合法內容:exit 0
- InkStoneCo 語境、真違規(`ON_FAILURE` 這種不存在的邊):exit 2(行為不變,沒有變寬鬆)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 19:49:34 +08:00
Leo 1dc97f0bd6 fix(hooks): 補上 leo21c-write-guard.sh 第二、三次誤攔的修法(InkStoneCo 反向移植)
InkStoneCo/.claude/hooks/leo21c-write-guard.sh 2026-08-20 當天已修過兩次
誤攔(bc8a529/5e62d28),但沒人把修法帶回 ISEP——ISEP 那份還是上線當天
的舊版,會誤擋「git commit 訊息提到 leo21c」「Gitea PATCH 內文提到
leo21c」這類完全碰不到 CF 帳號的動作。

發現於 inkstone/InkStoneCo#57/inkstone/ISEP#32「同一件事只留一份」:
要把 InkStoneCo 本機的閘收斂成只剩 ISEP 這份之前,先確認 ISEP 沒有落後
——結果這支剛好是唯一反向的(其餘 4 支已修過的閘 ISEP 都領先)。

判準改成只認「這個指令會不會真的寫到那台」:
① 寫入請求打到 *.leo21c.workers.dev(POST/PUT/PATCH/DELETE/--data/-d/trigger)
② 用它的 CF 帳號 id 跑 wrangler/acr
其餘一律放行,不管文字裡提到什麼。

實測 6/6(4 該放行/2 該擋,含這次要修的兩個誤攔案例):見 PR 說明。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 19:46:24 +08:00