fix(skillset): 技能組稽核修正與第九支 hook #52
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jsc-hooks",
|
||||
"version": "0.3.1",
|
||||
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、註解範圍守門、繁中編碼守門、部署後強制重啟",
|
||||
"version": "0.3.2",
|
||||
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、註解範圍守門、繁中編碼守門、部署後強制重啟、寫入與提交閘門",
|
||||
"skills": "./skills",
|
||||
"author": {
|
||||
"name": "JSC"
|
||||
@@ -18,6 +18,8 @@
|
||||
"requires": {
|
||||
"jsc-cli": ">=0.2.1",
|
||||
"jsc-gitea": ">=0.1.7",
|
||||
"jsc-git": ">=0.1.1",
|
||||
"jsc-meta": ">=0.2.3",
|
||||
"jsc-review": ">=0.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
{
|
||||
"name": "jsc-hooks",
|
||||
"version": "0.3.1",
|
||||
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、註解範圍守門、繁中編碼守門、部署後強制重啟",
|
||||
"version": "0.3.2",
|
||||
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、註解範圍守門、繁中編碼守門、部署後強制重啟、寫入與提交閘門",
|
||||
"skills": "./skills",
|
||||
"jsc": {
|
||||
"requires": {
|
||||
"jsc-cli": ">=0.2.1",
|
||||
"jsc-gitea": ">=0.1.7",
|
||||
"jsc-git": ">=0.1.1",
|
||||
"jsc-meta": ">=0.2.3",
|
||||
"jsc-review": ">=0.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# jsc-hooks — 給 AI 助理的指引
|
||||
|
||||
本 repo 是 jsc 技能組的 `hooks` domain(跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、部署後強制重啟閘門、註解範圍檢查、繁中與編碼檢查),可同時被 Claude Code / Codex / Copilot / Antigravity / Kiro 使用。
|
||||
本 repo 是 jsc 技能組的 `hooks` domain(跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、部署後強制重啟閘門、註解範圍檢查、繁中與編碼檢查、寫入與提交閘門),可同時被 Claude Code / Codex / Copilot / Antigravity / Kiro 使用。
|
||||
|
||||
## 規則
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
4. 所有 hook 只放在 `jsc-hooks`;gitea 操作一律經由 `jsc-gitea` 的 `tools/gitea.sh`;問使用者一律依 `jsc-ask:ask` 的決策樹規則。
|
||||
5. 註解範圍規則正文的唯一來源:`jsc-review` 的 `references/comment-scope.md`。本存取庫只放 `hooks/comment-scope.sh` 的判定實作,不留規則清單副本,接線腳本要用規則文字時一律取腳本的實際輸出。`comment-scope.sh` 有 `prompt`、無參數逐檔掃描、`sweep` 掃整個 git 工作區三種模式;掃描時機每個 CLI 都不同(claude 逐檔即時、codex 每輪結束、kiro 每輪提示送出時、copilot 與 antigravity 只有工作階段結束時),談覆蓋範圍時一律據實分開講,不得寫成五支一樣。
|
||||
6. 所有非程式碼輸出一律繁體中文、UTF-8、無亂碼、無簡體字:程式碼註解、commit 訊息、PR 描述、wiki 頁、對使用者的回報、README 與各種文件都算。規則正文的唯一來源同樣是 `plugins/meta` 的 `references/ste100.md`,本存取庫只放 `hooks/lang-guard.sh` 的判定實作與 `hooks/simplified.txt` 的機檢字表。那份字表是本存取庫的單一真實來源,刻意排除繁體也在用的字(后、台、干、只、里、面、制、志),增刪前先確認不會製造誤報。`lang-guard.sh` 的三種模式與掃描時機跟 `comment-scope.sh` 一致,但它掃整個檔案而不只掃註解行,`.md` 與純文字檔也照掃。
|
||||
7. 主 agent 不需要處理細節的流程,一律建立 sub agent 處理。
|
||||
7. `hooks/write-guard.sh` 的三種模式只有 claude 接得上(其餘四支 CLI 沒有 PreToolUse),談覆蓋範圍時據實講,不得暗示每支 CLI 都擋得住。它只讀 `sdlc-gate.sh` 的階段鎖與 `skill-usage.sh` 的技能紀錄,不自己寫狀態檔;提交訊息的簡繁與編碼判定一律轉呼叫 `hooks/lang-guard.sh`,本檔不留第二份樣式。
|
||||
8. 主 agent 不需要處理細節的流程,一律建立 sub agent 處理。
|
||||
|
||||
## 呼叫慣例
|
||||
|
||||
|
||||
@@ -24,16 +24,17 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
|
||||
| --- | --- | --- |
|
||||
| `hooks/ste100-guard.sh` | UserPromptSubmit | 注入 STE100 繁體中文輸出規則(hook > prompt 強制層) |
|
||||
| `hooks/session-timer.sh` | SessionStart / Stop / SessionEnd | 記錄工作階段起訖。子指令:`start` 記起始時間(已有紀錄就不動,給 claude 這種每階段有自己 session id 的 CLI)、`restart` 一律覆寫起始時間(給接不到 session id 的 kiro,不覆寫會把上一階段算進來)、`mark` 更新最後活動時間、`report` 供 `jsc-log:worklog` 取花費時間。`start` 與 `restart` 判定為新工作階段時,另外呼叫 `restart-gate.sh clear` 放下部署後的重啟閘門——新工作階段代表 CLI 行程是新起的,新版一定已經載入。清除的範圍只有跑到這支腳本的那一支 CLI 自己那一份狀態檔,別支沒重啟就繼續被擋 |
|
||||
| `hooks/version-guard.sh` | PreToolUse(Skill) | 技能使用前的版本前置檢查:本機**實際載入**版本落後遠端發佈版本就以 exit 2 擋下該次呼叫並提示更新指令(更新指令依當前 CLI 給)。只擋落後這一種情況:超前放行(開發技能組時本機本來就會超前),讀不到本機版本、推導不出站台、查不到遠端版本也一律放行。遠端版本快取在 `$JSC_HOME/version-cache/{CLI 代號}/{domain}`,一支 CLI 一份;舊路徑 `$JSC_HOME/version-cache/{domain}` 會在第一次讀取時複製到新路徑。逃生門 `JSC_VERSION_GUARD=off`。豁免 `jsc-cli:deploy`、`jsc-hooks:hooks-install`、`jsc-cli:models`、`jsc-meta:*` |
|
||||
| `hooks/restart-gate.sh` | PreToolUse(Skill) | 部署後強制重啟閘門:`$JSC_HOME/restart-required.d/{CLI 代號}` 一支 CLI 一份,當前 CLI 那份存在時以 exit 2 擋下 jsc 技能呼叫,並印出要重新啟動哪一支 CLI;別支 CLI 那幾份不影響這一支。狀態檔由 `jsc-cli:deploy` 在 install 或 update 收尾時經 `restart-gate.sh require {install|update} [{domain}...]` 寫入當前 CLI 那一份,在下一個工作階段開始時由 `session-timer.sh` 呼叫 `restart-gate.sh clear` 只清除那一份。判定看檔案在不在:狀態檔讀不到、CLI 代號取不到、技能名取不到都放行(理由與 `version-guard.sh` 一致,只擋確定違規)。舊格式的單一檔案 `$JSC_HOME/restart-required` 存在時一律擋,`clear` 會一併刪掉它(過渡相容,詳見下面「部署後重啟狀態檔」)。豁免 `jsc-cli:deploy`、`jsc-hooks:hooks-install`、`jsc-gitea:wiki`、`jsc-log:worklog`、`jsc-log:learn`、`jsc-meta:*`、`jsc-ask:ask`、`jsc-git:pr`、`jsc-git:commit`——部署後還要寫得完技能組異動報告與工作日誌,整批擋下去兩條規則會互相打死。清單認技能名不認呼叫鏈,後三支是為了讓前六支走得完才補進來的:`deploy` 要問模式、報告寫完要開 PR。另有唯讀子指令 `report`,一支 CLI 一行印出每一份狀態檔的內容,看得出還有哪幾支沒重啟。逃生門 `JSC_RESTART_GATE=off` |
|
||||
| `hooks/version-guard.sh` | PreToolUse(Skill) | 技能使用前的版本前置檢查:本機**實際載入**版本落後遠端發佈版本就以 exit 2 擋下該次呼叫並提示更新指令(更新指令依當前 CLI 給)。只擋落後這一種情況:超前放行(開發技能組時本機本來就會超前),讀不到本機版本、推導不出站台、查不到遠端版本也一律放行。遠端版本快取在 `$JSC_HOME/version-cache/{CLI 代號}/{domain}`,一支 CLI 一份;舊路徑 `$JSC_HOME/version-cache/{domain}` 會在第一次讀取時複製到新路徑。逃生門 `JSC_VERSION_GUARD=off`。豁免 `jsc-cli:deploy`、`jsc-hooks:hooks-install`、`jsc-hooks:repair`、`jsc-cli:models`、`jsc-meta:*`、`jsc-ask:ask`、`jsc-gitea:wiki`——清單的唯一來源是 `hooks/version-guard.sh` 的檔頭,那裡一項一個理由 |
|
||||
| `hooks/restart-gate.sh` | PreToolUse(Skill) | 部署後強制重啟閘門:`$JSC_HOME/restart-required.d/{CLI 代號}` 一支 CLI 一份,當前 CLI 那份存在時以 exit 2 擋下 jsc 技能呼叫,並印出要重新啟動哪一支 CLI;別支 CLI 那幾份不影響這一支。狀態檔由 `jsc-cli:deploy` 在 install 或 update 收尾時經 `restart-gate.sh require {install|update} [{domain}...]` 寫入當前 CLI 那一份,在下一個工作階段開始時由 `session-timer.sh` 呼叫 `restart-gate.sh clear` 只清除那一份。判定看檔案在不在:狀態檔讀不到、CLI 代號取不到、技能名取不到都放行(理由與 `version-guard.sh` 一致,只擋確定違規)。舊格式的單一檔案 `$JSC_HOME/restart-required` 存在時一律擋,`clear` 會一併刪掉它(過渡相容,詳見下面「部署後重啟狀態檔」)。豁免 `jsc-cli:deploy`、`jsc-hooks:hooks-install`、`jsc-hooks:repair`、`jsc-gitea:wiki`、`jsc-log:worklog`、`jsc-log:learn`、`jsc-meta:*`、`jsc-ask:ask`、`jsc-git:pr`、`jsc-git:commit`——部署後還要寫得完技能組異動報告與工作日誌,hook 壞掉也要修得回來,整批擋下去這些規則會互相打死。清單認技能名不認呼叫鏈,後三支是為了讓前七支走得完才補進來的:`deploy` 要問模式、報告寫完要開 PR。另有唯讀子指令 `report`,一支 CLI 一行印出每一份狀態檔的內容,看得出還有哪幾支沒重啟。逃生門 `JSC_RESTART_GATE=off` |
|
||||
| `hooks/skill-usage.sh` | PostToolUse(Skill) | 記錄技能使用與呼叫鏈到 `$JSC_HOME/usage/*.jsonl`,供 `jsc-log:stats` 統計 |
|
||||
| `hooks/comment-scope.sh` | UserPromptSubmit、PostToolUse(Write、Edit、MultiEdit)、codex `notify`、kiro `userPromptSubmit`、`tools/jsc-wrap.sh` 收尾 | 程式碼註解不得夾帶文件相關資訊與審查流程痕跡,共三種模式。`prompt`:在每次提示注入規則摘要(禁止項與白名單各一行),五個 CLI 都接得到。無參數:寫檔後的逐檔掃描,從 stdin JSON 取 `file_path`(或環境變數 `JSC_CHANGED_FILE`),只有 claude 的 PostToolUse 接得上。`sweep [dir]`:掃整個 git 工作區這次改過的所有檔案,給沒有 post-tool hook 的四個 CLI 用,找不到 git 就安靜 exit 0。掃描時機每個 CLI 不同——claude 逐檔即時(PostToolUse)、codex 每輪結束(`notify`)、kiro 每輪提示送出時(`userPromptSubmit`,掃的是上一輪寫的檔)、copilot 與 antigravity 只有工作階段結束時由 `tools/jsc-wrap.sh` 收尾掃一次。兩種掃描模式都只看 `git diff HEAD` 的新增行、不翻舊帳,命中就把警告與最多三行證據送到 stderr 並以 exit 2 交回模型就地修正(不擋寫入,檔案已經寫好了)。markdown、純文字、資料檔與二進位檔一律跳過。只實作可用樣式判定的項目,專案代號、客戶名稱這類判不出來的交給 `/jsc-review:code-review`。規則正文的唯一來源在 `jsc-review` 的 `references/comment-scope.md`,本存取庫不留副本。逃生門 `JSC_COMMENT_SCOPE=off` |
|
||||
| `hooks/lang-guard.sh` | UserPromptSubmit、PostToolUse(Write、Edit、MultiEdit)、codex `notify`、kiro `userPromptSubmit`、`tools/jsc-wrap.sh` 收尾 | 所有非程式碼輸出一律繁體中文、UTF-8、無亂碼、無簡體字,共三種模式。`prompt`:在每次提示注入規則摘要(適用範圍與自我檢查各一行),五個 CLI 都接得到。無參數:寫檔後的逐檔掃描,從 stdin JSON 取 `file_path`(或環境變數 `JSC_CHANGED_FILE`),只有 claude 的 PostToolUse 接得上。`sweep [dir]`:掃整個 git 工作區這次改過的所有檔案,給沒有 post-tool hook 的四個 CLI 用,找不到 git 就安靜 exit 0。接線位置與掃描時機跟 `comment-scope.sh` 完全一樣,見下面那張表。偵測三項:簡體字(字表在 `hooks/simplified.txt`,讀不到就安靜跳過這一項)、亂碼(U+FFFD 替代字元與雙重編碼殘骸)、非 UTF-8 編碼(用 `iconv` 判定,沒有 `iconv` 就跳過)。三項都掃整個檔案、不只掃註解行,`.md` 與純文字檔照掃——那些正是「非程式碼輸出」的主場,這兩點跟 `comment-scope.sh` 刻意不同。掃描深度仍只看 `git diff HEAD` 的新增行、不翻舊帳,命中就把警告與最多三行證據送到 stderr 並以 exit 2 交回模型就地修正(不擋寫入)。二進位檔(只認 NUL 位元組)與 `*.lock`、`*.min.js`、`*.map` 這類產生檔跳過;`hooks/simplified.txt`、`hooks/ste100-guard.sh`、`hooks/lang-guard.sh` 也跳過,那三份檔案裡的簡體字與亂碼樣本是被討論的對象,不是被使用。規則正文的唯一來源在 `jsc-meta` 的 `references/ste100.md`。逃生門 `JSC_LANG_GUARD=off` |
|
||||
| `hooks/sdlc-gate.sh` | UserPromptSubmit、PreToolUse(Skill) | SDLC 階段能力標籤閘門與模型鎖:`lock {stage}` 由 jsc-sdlc 階段技能呼叫,從可驗證來源讀出模型 id,比對該階段必要標籤(`$JSC_HOME/model-tags.tsv`),不符就拒絕上鎖;來源優先序為 transcript、hook stdin JSON、Codex 本機 session 記錄,最後才接受 `JSC_MODEL` 人工覆寫,且回報會標明人工覆寫;`check` 在模型不符時以 exit 2 擋下該輪提示(其他 hook 一律 exit 0,此處是刻意例外);`report` 印出階段、必要標籤、模型 id、模型來源與判定結果;`unlock` 為逃生門。另含工作包 PR 閘門:`wp-lock {owner}/{repo} {index} [{工作包代號}]` 記下一筆未結清的工作包 PR、`wp-unlock {owner}/{repo} {index}` 結清那一筆(檔案不存在也算成功)、`wp-claim {owner}/{repo} {工作包代號} [{PR 編號}] [{分析頁頁名}]` 記下這個存取庫目前領取哪一包、`wp-unclaim {owner}/{repo}` 交回、`wp-report` 印出所有未結清、`wp-check {prompt|skill}` 為 hook 模式。狀態檔一個工作包一支,在 `$JSC_HOME/wp/{owner}-{repo}-{index}.pr`,**刻意不綁 session**——PR 沒合併時換一個工作階段照樣要擋;一個工作包一支鎖檔是為了讓好幾個互不相依的工作包能同時記在案,不會互相覆蓋掉對方的鎖。`wp-check prompt` 只注入提醒、絕不擋提示(擋了連「去修那支 PR」的對話都送不出去);`wp-check skill` 在有未結清 PR 時以 exit 2 擋下 `plan`、`analyze`、`maintain`,但一律放行 `implement`(結清 PR 正是 implement 的步驟,擋它會鎖死流程)——這一層是整個存取庫共用的粗粒度提醒,「某個候選工作包能不能挑」的細粒度判斷在 `jsc-sdlc/tools/wp-gate.sh check-deps`,不是這裡。另外會比對歸屬:未結清的 PR 不屬於目前領取的工作包時,`prompt` 多注入一行「那幾支交給領取它的工作階段」,`skill` 在擋下 `plan`、`analyze`、`maintain` 時一併點名,`implement` 仍放行但收到同一則提醒。逃生門 `JSC_WP_GATE=off`。這道閘門只讀檔案、不打網路,PR 的真實合併狀態由 `jsc-sdlc/tools/wp-gate.sh` 查證 |
|
||||
| `hooks/sdlc-gate.sh` | UserPromptSubmit、PreToolUse(Skill) | SDLC 階段能力標籤閘門與模型鎖:`lock {stage}` 由 jsc-sdlc 階段技能呼叫,從可驗證來源讀出模型 id,比對該階段必要標籤(`$JSC_HOME/model-tags.tsv`),不符就拒絕上鎖;來源優先序為 transcript、hook stdin JSON、Codex 本機 session 記錄,最後才接受 `JSC_MODEL` 人工覆寫,且回報會標明人工覆寫;`check` 在模型不符時以 exit 2 擋下該輪提示(其他 hook 一律 exit 0,此處是刻意例外);`report` 印出階段、必要標籤、模型 id、模型來源與判定結果;`unlock` 為逃生門。另含工作包 PR 閘門:`wp-lock {owner}/{repo} {index} [{工作包代號}]` 記下一筆未結清的工作包 PR、`wp-unlock {owner}/{repo} {index}` 結清那一筆(檔案不存在也算成功)、`wp-claim {owner}/{repo} {工作包代號} [{PR 編號}] [{分析頁頁名}]` 記下這個存取庫目前領取哪一包、`wp-unclaim {owner}/{repo}` 交回、`wp-report` 印出所有未結清、`wp-check {prompt|skill}` 為 hook 模式。狀態檔一個工作包一支,在 `$JSC_HOME/wp/{owner}-{repo}-{index}.pr`,**刻意不綁 session**——PR 沒合併時換一個工作階段照樣要擋;一個工作包一支鎖檔是為了讓好幾個互不相依的工作包能同時記在案,不會互相覆蓋掉對方的鎖。`wp-check prompt` 只注入提醒、絕不擋提示(擋了連「去修那支 PR」的對話都送不出去);`wp-check skill` 在有未結清 PR 時以 exit 2 擋下 `analyze` 與 `maintain`,但一律放行 `implement`(結清 PR 正是 implement 的步驟,擋它會鎖死流程),也放行 `plan`,只注入提醒(plan 是純邏輯階段、不碰程式碼,而這道閘門只知道「有 PR 未合併」、判不出跟新計畫有沒有關聯;放棄的是在製品上限,`analyze` 與 `maintain` 兩道仍在,上限晚一個階段才生效)——這一層是整個存取庫共用的粗粒度提醒,「某個候選工作包能不能挑」的細粒度判斷在 `jsc-sdlc/tools/wp-gate.sh check-deps`,不是這裡。另外會比對歸屬:未結清的 PR 不屬於目前領取的工作包時,`prompt` 多注入一行「那幾支交給領取它的工作階段」,`skill` 在擋下 `analyze`、`maintain` 時一併點名,`plan` 與 `implement` 仍放行但收到同一則提醒。逃生門 `JSC_WP_GATE=off`。這道閘門只讀檔案、不打網路,PR 的真實合併狀態由 `jsc-sdlc/tools/wp-gate.sh` 查證 |
|
||||
| `hooks/write-guard.sh` | PreToolUse(Write、Edit、MultiEdit)、PreToolUse(Bash) | 寫入與提交閘門,共三種擋人模式,只有 claude 有 PreToolUse,其餘四支 CLI 一條都接不上;另有一個不接 hook 的 `release` 解除模式。`stage`:`sdlc-gate.sh` 的階段鎖鎖在 `plan` 或 `analyze` 時,以 exit 2 擋下 `Write`、`Edit`、`MultiEdit`——那兩個階段的產出是計畫頁與分析頁,不是檔案。階段鎖狀態檔沿用 `sdlc-gate.sh` 那一份,這裡只讀不寫。`review`:目前技能是 `jsc-review:code-review` 或 `jsc-review:api-doc` 時擋下寫入,那兩支只回報發現、不改程式碼。技能名先讀環境變數,取不到才讀 `skill-usage.sh` 記下的那一份;沒有「技能結束」事件可讀,所以紀錄超過 `JSC_WRITE_GUARD_TTL` 秒就當那支技能早已跑完。`jsc-review:comment-cleanup` **刻意不擋**:它本來就要改檔,只是限定僅註解行,而精確判定要解析工具參數裡整份新內容再逐語言判斷哪幾行是註解,判錯會擋掉合法的清理,代價比漏擋大,所以那條界線留給技能內文與後續審查。`commit`:擋下「同一道指令把全部變更一次加進索引再提交」,也擋下含簡體字、亂碼或非 UTF-8 編碼的提交訊息(判定整段轉呼叫 `lang-guard.sh`,字表仍是 `hooks/simplified.txt`,這裡不留第二份樣式)。跨兩次工具呼叫的 `git add -A` 不擋:那要記跨呼叫狀態,而被擋下的人沒有辦法讓那個狀態自己消失,閘門會把解除自己的路徑一起鎖掉。`release`:刪掉 `review` 模式認人用的那份紀錄,一律 exit 0,由 `jsc-review:code-review` 與 `jsc-review:api-doc` 在收尾時各呼叫一次。有這個模式是因為那份紀錄記的是「最近一次載入的技能」不是「還在跑的技能」——稽核收尾後呼叫端本來就要動手改,那時紀錄仍寫著稽核技能,TTL 內每一次寫入都被擋,解除路徑只剩逃生門或空等;閘門不得把解除自己的路徑一起鎖掉。逃生門 `JSC_WRITE_GUARD=off`(`release` 不受它影響,清紀錄擋不到任何人) |
|
||||
|
||||
Claude 由 `hooks/hooks.json` 自動接線八支 hook;其他 CLI 用 `hooks-install` 技能接線、改裝包裝啟動器,或降級為規則檔。寫進使用者設定的長期命令一律指向 `$JSC_HOME/current/jsc-hooks`,不指向帶版號的 plugin 快取目錄,也不指向開發存取庫。
|
||||
Claude 由 `hooks/hooks.json` 自動接線九支 hook;其他 CLI 用 `hooks-install` 技能接線、改裝包裝啟動器,或降級為規則檔。寫進使用者設定的長期命令一律指向 `$JSC_HOME/current/jsc-hooks`,不指向帶版號的 plugin 快取目錄,也不指向開發存取庫。
|
||||
|
||||
> 覆蓋範圍要據實看待:只有 claude 同時有 PreToolUse、PostToolUse 與 UserPromptSubmit,八支 hook 全接得上,回報 `wired`。codex、copilot、antigravity、kiro 都沒有 pre-tool hook,接不上 `version-guard.sh` 的版本前置檢查,也接不上 `restart-gate.sh` 的部署後重啟閘門,SDLC 模型鎖也只剩技能步驟檢查,這四個 CLI 一律回報 `degraded`,靠 `/jsc-cli:deploy` 定期更新。重啟閘門在這四個 CLI 上一次技能呼叫都擋不下來:那一支自己那份狀態檔照樣寫、下一個工作階段開始照樣清,只是中間沒有判定點,重啟得靠 `/jsc-cli:deploy` 收尾的提示自己動手。codex 另外沒有工作階段開始事件,計時改由 `tools/jsc-wrap.sh` 的 `codex` 別名在啟動當下開始;沒走別名啟動時,時間從第一輪回應算起。
|
||||
> 覆蓋範圍要據實看待:只有 claude 同時有 PreToolUse、PostToolUse 與 UserPromptSubmit,九支 hook 全接得上,回報 `wired`。codex、copilot、antigravity、kiro 都沒有 pre-tool hook,接不上 `version-guard.sh` 的版本前置檢查,接不上 `restart-gate.sh` 的部署後重啟閘門,也接不上 `write-guard.sh` 的三種模式,SDLC 模型鎖也只剩技能步驟檢查,這四個 CLI 一律回報 `degraded`,靠 `/jsc-cli:deploy` 定期更新。重啟閘門在這四個 CLI 上一次技能呼叫都擋不下來:那一支自己那份狀態檔照樣寫、下一個工作階段開始照樣清,只是中間沒有判定點,重啟得靠 `/jsc-cli:deploy` 收尾的提示自己動手。codex 另外沒有工作階段開始事件,計時改由 `tools/jsc-wrap.sh` 的 `codex` 別名在啟動當下開始;沒走別名啟動時,時間從第一輪回應算起。
|
||||
|
||||
> `comment-scope.sh` 與 `lang-guard.sh` 五個 CLI 都掃得到,接的是同一批位置,但時機不同,不能當成五支一樣:
|
||||
|
||||
@@ -92,6 +93,12 @@ Claude 由 `hooks/hooks.json` 自動接線八支 hook;其他 CLI 用 `hooks-in
|
||||
> 代表這台機器無法做版本檢查,跟「全部最新」是兩件事。查遠端版本走與 hook 同一份快取
|
||||
> 與同一個 `JSC_VERSION_TTL`,但快取依 CLI 分開,一次部署不會為同一支 CLI 的每個 domain 重複打一輪網路。
|
||||
> `jsc-cli:deploy` 用它決定要不要把「更新」設成推薦選項。
|
||||
>
|
||||
> `version-guard.sh recommend` 是同一份比對的結論版,只印一行 `recommend<TAB>update|none|unverifiable`,
|
||||
> 永遠 exit 0。判定規則寫在腳本檔頭:任一 plugin 落後就 `update`;查不到本機註冊檔、
|
||||
> 一列 domain 都沒有、或每一列都查詢失敗都是 `unverifiable`;其餘是 `none`。查詢失敗那幾列
|
||||
> 不計入——查不到不等於最新。證據表要另外看就再呼叫一次 `report`,兩者刻意不混印,
|
||||
> 呼叫端取第二欄的解析才不會被表格內容打亂。
|
||||
|
||||
### 狀態檔盤點
|
||||
|
||||
@@ -114,8 +121,8 @@ Claude 由 `hooks/hooks.json` 自動接線八支 hook;其他 CLI 用 `hooks-in
|
||||
| --- | --- |
|
||||
| `tools/jsc-wrap.sh` | 沒有完整 hook 系統的 CLI 的包裝啟動器:匯出 `JSC_CLI`、`JSC_SESSION_ID`,前後接 `session-timer.sh`,結束時自動跑 `scan-logs.sh` 回填,再依序跑一次 `comment-scope.sh sweep` 與 `lang-guard.sh sweep` 掃整個 git 工作區的註解範圍與繁中編碼(copilot 與 antigravity 沒有任何逐輪事件,整個工作階段只有這裡掃得到)。兩次收尾掃描一律不影響結束碼:包裝器原樣回傳 CLI 自己的結束碼,`sweep` 命中只把警告印到 stderr。`JSC_CLI` 存 CLI 代號,實際執行的是對應的執行檔(antigravity 是 agy、kiro 是 kiro-cli) |
|
||||
| `tools/scan-logs.sh` | 離線回填:解析 copilot、antigravity、codex 的原生日誌,把技能用量與階段界線補進 `$JSC_HOME`,重掃不重複 |
|
||||
| `tools/report-error.sh` | 失敗回報流程:把一筆 hook 或工具異常寫成 wiki 的 `ERROR_{HASH}`,並在 `ERROR_CONTENTS` 附上一列索引。wiki 位置由 `jsc-gitea` 的 `gitea.sh wiki-repo ERROR` 解析,解析不出來就安靜降級。由操作者手動執行,或由 `hooks-install` 在 `wire-cli.sh` 回報 `status=failed` 時執行;**不接在失敗的 hook 上自動觸發**(hook 一律安靜 exit 0,自我回報會疊出迴圈) |
|
||||
| `tools/wire-cli.sh` | 單一 CLI 的 hook 生命週期,共三個用法。`{cli}` 是接線:先建立或更新 `$JSC_HOME/current/jsc-hooks` 指向目前這版 plugin,接著把對應的設定編輯、包裝別名安裝、hook 檔建立成穩定路徑,皆以 `<!-- jsc-hooks -->`(或 `# jsc-hooks`)標記整段重寫,重跑等同先移除再重裝;寫完每個檔案會重讀驗證位置正確才回報成功(codex 的 `notify` 必須是根層鍵、kiro 的 JSON 必須成對且 `on`、`run` 在最上層),也會確認寫入路徑能解到既有腳本。檔案系統不能建立 symlink 時,會明確回報並退回目前根目錄,不會靜默寫出壞路徑。`status=wired\|degraded\|skipped\|failed` 回報接線結果。`purge {cli}` 是移除:把該 CLI 的**所有** hook 清掉,含非 jsc 的第三方項目,動到的檔案先原樣備份到 `$JSC_HOME/backup/hooks/{cli}/{yyyyMMdd_HHmmss}/`,備份失敗就不移除;移除標記段落時會先去掉標記行前後空白,所以縮排或尾端補空白的 jsc 區塊一樣會移除;移除後重讀驗證,驗不過自動還原備份,以 `status=purged\|skipped\|failed` 回報。`smoke {cli}` 是執行期冒煙測試:八支 hook 的每個接線模式各跑一次,非零退出即為錯誤,另外用一份暫時的 `$JSC_HOME` 狀態檔把工作包歸屬的五條判定路徑與重啟閘門的十二條判定與清除路徑各跑一次並比對結束碼,驗的是判定結果本身,不只是腳本跑得完(例外有三個:`sdlc-gate.sh check` 的 exit 2 是階段鎖的設計行為,`comment-scope.sh` 與 `lang-guard.sh` 掃描模式的 exit 2 是掃到違規的設計行為——`sweep` 在髒工作區本來就會回 2,不算 hook 壞掉),以 `status=ok\|failed` 回報。`status {cli}` 是唯讀盤點:只讀設定檔判斷標記段落在不在,不寫檔也不執行 hook,每個接線點印一行 `item<TAB>{項目}<TAB>{路徑}<TAB>{present\|missing}`,也會把帶版號快取路徑、開發存取庫路徑與不存在的腳本列為缺項;`status claude` 讀 Claude Code 實際載入的 `installed_plugins.json`,不再檢查目前腳本旁邊那份 `hooks.json`。體檢類技能(`/jsc-cli:doctor`)只能用這個子命令,另外三個都會動到環境 |
|
||||
| `tools/report-error.sh` | 失敗回報流程:把一筆 hook 或工具異常寫成 wiki 的 `ERROR_{HASH}`,並在 `ERROR_CONTENTS` 附上一列索引。目錄頁一律先讀回舊頁再附加新列、整頁寫回,不整頁覆蓋:只有 `wiki-get` 回 4(頁面真的不存在)才用範本建新頁,回 7(金鑰失效)或 8(其他 API 失敗)代表舊內容未知,放棄目錄頁寫入並以 exit 4 回報,免得拿範本蓋掉所有既有列。wiki 位置由 `jsc-gitea` 的 `gitea.sh wiki-repo ERROR` 解析,解析不出來就安靜降級。由操作者手動執行,或由 `hooks-install` 在 `wire-cli.sh` 回報 `status=failed` 時執行;**不接在失敗的 hook 上自動觸發**(hook 一律安靜 exit 0,自我回報會疊出迴圈) |
|
||||
| `tools/wire-cli.sh` | 單一 CLI 的 hook 生命週期,共四個用法。`{cli}` 是接線:先建立或更新 `$JSC_HOME/current/jsc-hooks` 指向目前這版 plugin,接著把對應的設定編輯、包裝別名安裝、hook 檔建立成穩定路徑,皆以 `<!-- jsc-hooks -->`(或 `# jsc-hooks`)標記整段重寫,重跑等同先移除再重裝;寫完每個檔案會重讀驗證位置正確才回報成功(codex 的 `notify` 必須是根層鍵、kiro 的 JSON 必須成對且 `on`、`run` 在最上層),也會確認寫入路徑能解到既有腳本。檔案系統不能建立 symlink 時,會明確回報並退回目前根目錄,不會靜默寫出壞路徑。`status=wired\|degraded\|skipped\|failed` 回報接線結果。`purge {cli}` 是移除:把該 CLI 的**所有** hook 清掉,含非 jsc 的第三方項目,動到的檔案先原樣備份到 `$JSC_HOME/backup/hooks/{cli}/{yyyyMMdd_HHmmss}/`,備份失敗就不移除;移除標記段落時會先去掉標記行前後空白,所以縮排或尾端補空白的 jsc 區塊一樣會移除;移除後重讀驗證,驗不過自動還原備份,以 `status=purged\|skipped\|failed` 回報。`smoke {cli}` 是執行期冒煙測試:九支 hook 的每個接線模式各跑一次,非零退出即為錯誤,另外用一份暫時的 `$JSC_HOME` 狀態檔把工作包歸屬、部署後重啟閘門與寫入提交閘門的每條判定路徑各跑一次並比對結束碼,驗的是判定結果本身,不只是腳本跑得完(例外有四個:`sdlc-gate.sh check` 的 exit 2 是階段鎖的設計行為,`comment-scope.sh`、`lang-guard.sh` 掃描模式與 `write-guard.sh` 三種模式的 exit 2 是命中違規的設計行為——`sweep` 在髒工作區本來就會回 2,`write-guard.sh` 在機器剛好鎖在 `plan` 階段時也會回 2,都不算 hook 壞掉),以 `status=ok\|failed` 回報。**結果行數由腳本自己數、自己斷言**:`status=` 之後緊接一行 `lines<TAB>{數量}`,那是其後 `[jsc]` 結果行的實際條數,與腳本內逐類宣告的預期條數比對,不符就回非零。判定路徑增減時只改腳本裡的預期值,散文一律引用這一行,不另外抄一份數字。`status {cli}` 是唯讀盤點:只讀設定檔判斷標記段落在不在,不寫檔也不執行 hook,每個接線點印一行 `item<TAB>{項目}<TAB>{路徑}<TAB>{present\|missing}`,也會把帶版號快取路徑、開發存取庫路徑與不存在的腳本列為缺項;`status claude` 讀 Claude Code 實際載入的 `installed_plugins.json`,不再檢查目前腳本旁邊那份 `hooks.json`。體檢類技能(`/jsc-cli:doctor`)只能用這個子命令,另外三個都會動到環境;那道限制另有程式層把關,`JSC_READONLY=1` 之下只准 `status` 與 `smoke`,`purge` 與接線一律以 exit 6 拒絕並回報 `status=readonly`,環境不會被動到 |
|
||||
| `tools/scan-hook-errors.sh` | 掃 CLI 原生紀錄找 hook 的執行期錯誤(接線寫對、跑起來出錯)。只有 claude 有 hook 結果紀錄,掃 `~/.claude/projects/**/*.jsonl` 的 `hook_non_blocking_error` 與非空 `hookErrors`;codex、copilot、antigravity、kiro 沒有等價紀錄,一律回報 `unavailable` 並指向 `wire-cli.sh smoke {cli}`。每筆錯誤附加一行 JSON 到 `$JSC_HOME/errors/hooks.jsonl`,`jsc` 欄位標明是不是 jsc 自己的 hook(第三方 hook 的錯誤只回報,不由 jsc 修正);去重與 `scan-logs.sh` 同法,重掃只讀新增段落,以 `status=clean\|errors\|unavailable` 回報 |
|
||||
|
||||
## 失敗回報範本
|
||||
@@ -136,7 +143,7 @@ Claude 由 `hooks/hooks.json` 自動接線八支 hook;其他 CLI 用 `hooks-in
|
||||
|
||||
### `hooks-install`
|
||||
|
||||
把八支 hook 接線到所有已安裝的 CLI,每個 CLI 走四道關卡:先 `tools/wire-cli.sh purge {cli}` 備份後移除所有 hook(含非 jsc 的第三方項目,乾淨起跑才分得清後續失敗是誰的),再 `tools/wire-cli.sh {cli}` 接線(claude 由 `hooks.json` 自動接線,無需寫入;其他 CLI 的持久命令會寫成 `$JSC_HOME/current/jsc-hooks` 穩定路徑),接著 `tools/wire-cli.sh smoke {cli}` 驗執行期,最後 `tools/scan-hook-errors.sh --cli {cli}` 掃原生紀錄。codex、copilot、antigravity 由接線腳本裝上 `tools/jsc-wrap.sh` 包裝別名補上計時與用量回填(結束時自動跑 `tools/scan-logs.sh`),語言規則仍重寫到各自的規則檔(以 `<!-- jsc-hooks -->` 標記整段取代,等同先移除再重裝,不重複追加)。codex、copilot、antigravity、kiro 的 SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 SDLC 技能直接呼叫 `sdlc-gate.sh lock` 寫入;這四個 CLI 沒有 pre-tool hook,版本前置檢查與部署後重啟閘門都接不上(重啟閘門在那四支上一次技能呼叫都擋不下來,狀態檔照樣寫、下個工作階段照樣清);也沒有 post-tool hook,`comment-scope.sh` 接不到逐檔即時掃描,改用 `sweep` 掃整個 git 工作區——codex 每輪結束、kiro 每輪提示送出時、copilot 與 antigravity 只有工作階段結束時掃一次,腳本會在 `reason` 裡講明各自的時機,只有 claude 回報 `wired`,也只有 claude 掃得到執行期錯誤紀錄。任一關卡出錯(purge、接線、冒煙失敗,或掃到 `jsc=true` 的執行期錯誤)就先寫 `ERROR_{HASH}`,再交給 `repair` 技能接手並以 `develop` PR 收尾;此時允許中止剩下的安裝,但修正一定要開始。掃到 `jsc=false` 的第三方 hook 錯誤只回報,不轉修正。
|
||||
把九支 hook 接線到所有已安裝的 CLI,每個 CLI 走五道關卡:先 `tools/wire-cli.sh purge {cli}` 備份後移除所有 hook(含非 jsc 的第三方項目,乾淨起跑才分得清後續失敗是誰的),再 `tools/wire-cli.sh {cli}` 接線(claude 由 `hooks.json` 自動接線,無需寫入;其他 CLI 的持久命令會寫成 `$JSC_HOME/current/jsc-hooks` 穩定路徑),接著 `tools/wire-cli.sh status {cli}` 唯讀盤點接線結果,再 `tools/wire-cli.sh smoke {cli}` 驗執行期,最後 `tools/scan-hook-errors.sh --cli {cli}` 掃原生紀錄。第一支 CLI 的管線單獨跑完(`$JSC_HOME/current/jsc-hooks` 連結由它統一更新),其餘各 CLI 的管線才並行。codex、copilot、antigravity 由接線腳本裝上 `tools/jsc-wrap.sh` 包裝別名補上計時與用量回填(結束時自動跑 `tools/scan-logs.sh`),語言規則仍重寫到各自的規則檔(以 `<!-- jsc-hooks -->` 標記整段取代,等同先移除再重裝,不重複追加)。codex、copilot、antigravity、kiro 的 SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 SDLC 技能直接呼叫 `sdlc-gate.sh lock` 寫入;這四個 CLI 沒有 pre-tool hook,版本前置檢查、部署後重啟閘門與 `write-guard.sh` 的三種模式都接不上(重啟閘門在那四支上一次技能呼叫都擋不下來,狀態檔照樣寫、下個工作階段照樣清);也沒有 post-tool hook,`comment-scope.sh` 接不到逐檔即時掃描,改用 `sweep` 掃整個 git 工作區——codex 每輪結束、kiro 每輪提示送出時、copilot 與 antigravity 只有工作階段結束時掃一次,腳本會在 `reason` 裡講明各自的時機,只有 claude 回報 `wired`,也只有 claude 掃得到執行期錯誤紀錄。任一關卡出錯(purge、接線、冒煙失敗,或掃到 `jsc=true` 的執行期錯誤)就先寫 `ERROR_{HASH}`,再交給 `repair` 技能接手並以 `develop` PR 收尾;此時允許中止剩下的安裝,但修正一定要開始。掃到 `jsc=false` 的第三方 hook 錯誤只回報,不轉修正。
|
||||
|
||||
### `repair`
|
||||
|
||||
@@ -158,7 +165,11 @@ Claude 由 `hooks/hooks.json` 自動接線八支 hook;其他 CLI 用 `hooks-in
|
||||
| `JSC_RESTART_GATE` | 設 `off` 完全略過部署後重啟閘門(`restart-gate.sh` 一律放行) | 啟用閘門 |
|
||||
| `JSC_COMMENT_SCOPE` | 設 `off` 完全略過註解範圍檢查(`comment-scope.sh` 三種模式都直接結束) | 啟用檢查 |
|
||||
| `JSC_LANG_GUARD` | 設 `off` 完全略過繁中與編碼檢查(`lang-guard.sh` 三種模式都直接結束) | 啟用檢查 |
|
||||
| `JSC_WRITE_GUARD` | 設 `off` 完全略過寫入與提交閘門(`write-guard.sh` 三種模式都直接結束) | 啟用閘門 |
|
||||
| `JSC_WRITE_GUARD_TTL` | `write-guard.sh review` 判定「稽核技能還在跑」的時效秒數 | 預設 900 |
|
||||
| `JSC_READONLY` | 設 `1` 時 `tools/wire-cli.sh` 只准 `status` 與 `smoke`,`purge` 與接線一律拒絕並回 exit 6 | 四個用法都可執行 |
|
||||
| `JSC_CHANGED_FILE` | 非 Claude CLI 要掃描的檔案路徑,代替 stdin JSON 的 `file_path`,供 `comment-scope.sh` 與 `lang-guard.sh` 使用 | 安靜降級,不掃描 |
|
||||
| `JSC_TOOL_COMMAND` | 非 Claude CLI 要判定的 Bash 指令字串,代替 stdin JSON 的 `command`,供 `write-guard.sh commit` 使用 | 安靜降級,不判定 |
|
||||
| `JSC_CLI` / `JSC_SESSION_ID` / `JSC_SKILL` / `JSC_TOOL_NAME` | 非 Claude CLI 接線時由 `tools/jsc-wrap.sh` 或接線設定提供,代替 stdin JSON 的 `session_id`、`skill`、`tool_name`(`version-guard.sh` 也收沒有前綴的 `SKILL`、`TOOL_NAME`) | 安靜降級 |
|
||||
| `JSC_MODEL` | `sdlc-gate.sh` 找不到 transcript、hook stdin JSON 與 Codex 本機 session 記錄時的人工覆寫模型 id;回報會標明 `人工覆寫:JSC_MODEL` | 找不到可驗證模型來源時拒絕 `lock`,並列出已檢查來源與修復建議 |
|
||||
|
||||
|
||||
@@ -73,6 +73,28 @@
|
||||
"command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/sdlc-gate.sh\" wp-check skill'"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/write-guard.sh\" stage'"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/write-guard.sh\" review'"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/write-guard.sh\" commit'"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
# 缺資料時安靜降級,hook 預設 exit 0,不可中斷宿主 CLI。
|
||||
# 唯一例外:sdlc-gate.sh check 在「SDLC 階段鎖存在且模型不符」時會 exit 2 擋下該輪提示;
|
||||
# 其餘情況(無鎖、資料不足無法判定)仍照舊 exit 0。
|
||||
#
|
||||
# 結束碼:不適用。本檔是被 source 的共用函式庫,不是可執行入口,內部一次 exit 都沒有。
|
||||
# 載入成功回 0(最後一行是函式定義);拿 `sh lib.sh` 直接跑也只是定義完函式回 0,不做事。
|
||||
# 呼叫端真正要防的是「載入失敗」:POSIX sh 找不到這個檔時,`.` 會讓整支腳本就地結束並回 2。
|
||||
# 接在 PreToolUse 的 hook 遇到這一下,等於無聲擋掉每一次工具呼叫,而且腳本自己的放行路徑
|
||||
# 一條都跑不到。要安靜降級的呼叫端請寫 `. "$HERE/lib.sh" 2>/dev/null || true`
|
||||
# (comment-scope.sh 就是這樣接);其餘直接載入的腳本,各自檔頭都標了這一條。
|
||||
|
||||
JSC_HOME="${JSC_HOME:-$HOME/.jsc}"
|
||||
mkdir -p "$JSC_HOME/sessions" "$JSC_HOME/usage" 2>/dev/null || true
|
||||
|
||||
+16
-3
@@ -4,6 +4,17 @@
|
||||
# 技能組更新後,正在跑的 CLI 行程載入的還是舊版:SKILL.md、hook 腳本與 tools 都在啟動當下
|
||||
# 讀進記憶體。所以部署收尾要求重新啟動,這道閘門負責讓「還沒重啟就繼續用技能」擋在門外。
|
||||
#
|
||||
# 結束碼(hook 模式):0=放行 2=擋下該次技能呼叫,訊息走 stderr。
|
||||
# 安靜放行(exit 0)的情況:逃生門 JSC_RESTART_GATE=off、工具名取得到但不是 Skill、
|
||||
# 取不到技能名、技能名不是 jsc-{domain}:{name}、命中下方豁免清單那 10 支、取不到 CLI 代號、
|
||||
# 當前 CLI 那份狀態檔與舊格式狀態檔都不在。
|
||||
# 只有「當前 CLI 那份狀態檔存在」或「退回讀到的舊格式狀態檔存在」會 exit 2。
|
||||
# 結束碼(require):0=閘門已掛上 2=取不到 CLI 代號或寫不進狀態檔,兩種都等於沒掛上。
|
||||
# 結束碼(clear、report):0=永遠成功。clear 檔案不存在也算成功,report 一份都沒有就不印。
|
||||
# 結束碼(不認得的子命令):0=安靜放行,不中斷宿主 CLI。
|
||||
# 註:本檔以 `. "$HERE/lib.sh"` 載入共用函式,沒有接 `|| true`。lib.sh 讀不到時 sh 會就地
|
||||
# 結束並回 2,接在 PreToolUse 上就是無聲擋下每一次技能呼叫,上面那些放行路徑一條都跑不到。
|
||||
#
|
||||
# 用法:
|
||||
# restart-gate.sh hook 模式:當前 CLI 那份狀態檔存在就擋下該次技能
|
||||
# 呼叫(exit 2)。別支 CLI 那幾份不看。
|
||||
@@ -79,6 +90,8 @@
|
||||
# 豁免(這些技能永遠放行,改動前想清楚後果):
|
||||
# jsc-cli:deploy 部署入口本身,也是唯一能把技能組換成新版的路徑,擋了會死鎖
|
||||
# jsc-hooks:hooks-install 部署後要重新接線,擋了會讓部署做一半卡住
|
||||
# jsc-hooks:repair 接線或執行期出錯時唯一的修復路徑。修 hook 的技能被 hook 擋下,
|
||||
# 就沒有任何方法把 hook 修回來,閘門等於把解除自己的路徑一起鎖掉
|
||||
# jsc-gitea:wiki 寫技能組異動報告與工作日誌都要它落地,擋了報告寫不完
|
||||
# jsc-log:worklog 部署後還要寫得完工作日誌(R1)
|
||||
# jsc-log:learn 同上,教訓也要記得完
|
||||
@@ -90,7 +103,7 @@
|
||||
# 「先重啟」與「先寫完報告」會互相打死,使用者兩件事都做不完。
|
||||
#
|
||||
# 清單認的是技能名,不是呼叫鏈:豁免技能轉呼叫的下一層若不在清單上,那一層照樣會被擋。
|
||||
# 後三支(ask、pr、commit)就是為了這件事補進來的——它們自己不是收尾規則的主體,但前六支
|
||||
# 後三支(ask、pr、commit)就是為了這件事補進來的——它們自己不是收尾規則的主體,但前七支
|
||||
# 少了它們就走不完:deploy 問不出模式、報告寫完開不了 PR。version-guard.sh 當年把
|
||||
# jsc-ask:ask 與 jsc-gitea:wiki 放進豁免,也是同一個原因。
|
||||
# 還有巢狀呼叫走不下去時,先重新啟動;真的卡死才下 JSC_RESTART_GATE=off。
|
||||
@@ -187,7 +200,7 @@ esac
|
||||
|
||||
# 豁免清單(理由見檔頭)
|
||||
case "$skill" in
|
||||
jsc-cli:deploy|jsc-hooks:hooks-install|jsc-gitea:wiki|jsc-log:worklog|jsc-log:learn|jsc-meta:*|jsc-ask:ask|jsc-git:pr|jsc-git:commit)
|
||||
jsc-cli:deploy|jsc-hooks:hooks-install|jsc-hooks:repair|jsc-gitea:wiki|jsc-log:worklog|jsc-log:learn|jsc-meta:*|jsc-ask:ask|jsc-git:pr|jsc-git:commit)
|
||||
exit 0 ;;
|
||||
esac
|
||||
|
||||
@@ -227,5 +240,5 @@ printf '[jsc][重啟閘門][ERR]:技能組已更新%s,%s 還在跑舊版,
|
||||
"${info:+($info)}" "$bin" >&2
|
||||
printf '重新啟動:結束 %s 再重新開啟一次,狀態檔 %s 會在新工作階段開始時自動清除。\n' \
|
||||
"$bin" "$state" >&2
|
||||
printf '仍可使用:/jsc-cli:deploy、/jsc-hooks:hooks-install、/jsc-gitea:wiki、/jsc-log:worklog、/jsc-log:learn、/jsc-meta:*、/jsc-ask:ask、/jsc-git:pr、/jsc-git:commit(部署後的異動報告與工作日誌要寫得完) | 確定要略過閘門:JSC_RESTART_GATE=off\n' >&2
|
||||
printf '仍可使用:/jsc-cli:deploy、/jsc-hooks:hooks-install、/jsc-hooks:repair、/jsc-gitea:wiki、/jsc-log:worklog、/jsc-log:learn、/jsc-meta:*、/jsc-ask:ask、/jsc-git:pr、/jsc-git:commit(部署後的異動報告與工作日誌要寫得完,hook 壞掉也要修得回來) | 確定要略過閘門:JSC_RESTART_GATE=off\n' >&2
|
||||
exit 2
|
||||
|
||||
+41
-4
@@ -32,8 +32,30 @@
|
||||
# sdlc-gate.sh wp-report 印出 {owner}/{repo} {index} {上鎖時間} {工作包代號},每個未結清
|
||||
# 工作包各一行;沒有未結清就不印,exit 0。查無歸屬時第四欄留白。
|
||||
# sdlc-gate.sh wp-check prompt hook 模式(UserPromptSubmit):注入提醒,一律 exit 0。
|
||||
# sdlc-gate.sh wp-check skill hook 模式(PreToolUse,matcher Skill):命中別的階段技能時
|
||||
# exit 2 擋下該次呼叫;其餘 exit 0。
|
||||
# sdlc-gate.sh wp-check skill hook 模式(PreToolUse,matcher Skill):命中 analyze 或 maintain
|
||||
# 時 exit 2 擋下該次呼叫;plan 與 implement 只注入提醒後 exit 0,
|
||||
# 其餘技能一律 exit 0。
|
||||
#
|
||||
# 結束碼(一個子命令一列):
|
||||
# lock 0=通過並已上鎖 1=未通過,呼叫端必須停止流程(階段名不合法、讀不到該
|
||||
# 階段的必要標籤、判定不出目前模型、模型不在標籤表上、缺標籤、寫不進狀態檔)
|
||||
# unlock 0=永遠成功,狀態檔不存在也算
|
||||
# check 0=放行 2=擋下該輪提示。安靜放行:沒有階段鎖、舊格式狀態檔讀不出必要標籤、
|
||||
# 必要標籤是 any、判定不出目前模型(只提醒不擋)
|
||||
# report 0=永遠成功,無鎖就不印
|
||||
# wp-lock 0=已記下 2=存取庫不是 {owner}/{repo}、PR 編號不是數字、寫不進狀態檔
|
||||
# wp-unlock 0=已結清,狀態檔不存在也算 2=存取庫或 PR 編號格式錯誤
|
||||
# wp-claim 0=已記下 2=存取庫格式錯誤、工作包代號不帶數字、寫不進領取檔
|
||||
# wp-unclaim 0=已交回,領取檔不存在也算 2=存取庫格式錯誤
|
||||
# wp-report 0=永遠成功,沒有未結清就不印
|
||||
# wp-check prompt 0=永遠放行,只注入提醒
|
||||
# wp-check skill 0=放行 2=擋下該次技能呼叫。安靜放行:逃生門 JSC_WP_GATE=off、沒有未結清
|
||||
# 的工作包、取不到技能名、技能是 plan 或 implement、其餘不在名單上的技能。
|
||||
# 只有 analyze 與 maintain 會 exit 2
|
||||
# 不認得的子命令 0=安靜放行
|
||||
# 註:本檔以 `. "$HERE/lib.sh"` 載入共用函式,沒有接 `|| true`。lib.sh 讀不到時 sh 會就地結束
|
||||
# 並回 2;check 接在 UserPromptSubmit、wp-check skill 接在 PreToolUse,那一下都是無聲擋人,
|
||||
# 上面那些放行路徑一條都跑不到。
|
||||
#
|
||||
# 鎖檔一個工作包一支($JSC_HOME/wp/{owner}-{repo}-{index}.pr),不是整個存取庫共用一支:
|
||||
# SDLC 實作可能同時有好幾個互不相依的工作包平行進行,各自開各自的 PR。整庫共用一支鎖檔
|
||||
@@ -42,6 +64,15 @@
|
||||
# 跟「implement 挑下一個工作包能不能挑到某一包」是兩件事——後者的判斷依據是該包在分析頁
|
||||
# WBS 表的相依欄,走 jsc-sdlc/tools/wp-gate.sh check-deps,不靠這支鎖檔。
|
||||
#
|
||||
# --- plan 已從擋人名單移出,被放棄的保護寫在這裡 ---
|
||||
#
|
||||
# plan 原本跟 analyze、maintain 一起被擋。現在改成只提醒、照樣放行,放棄的是「在製品上限」:
|
||||
# 手上的工作包還沒結清就不准開新計畫。放棄的理由有兩個。一是 plan 是純邏輯階段,產出是計畫頁,
|
||||
# 不碰程式碼,開一份新計畫不會動到那支未結清的 PR。二是這道閘門手上只有「這個存取庫有 PR
|
||||
# 未合併」這一個事實,判不出新計畫跟那支 PR 有沒有關聯,擋下去多半是誤擋。
|
||||
# 代價要據實看待:沒有東西再擋住計畫越積越多,計畫的產出速度可以快過實作。
|
||||
# analyze 與 maintain 兩道仍在,上限只是晚一個階段才生效。
|
||||
#
|
||||
# --- 狀態檔格式(與 jsc-sdlc/tools/wp-gate.sh 對齊,兩邊都靠這段註解對格式) ---
|
||||
#
|
||||
# 兩種檔案都放在 $JSC_HOME/wp/ 底下,都是純文字 key=value,一行一欄位,順序不拘,
|
||||
@@ -428,11 +459,17 @@ WP_PENDING_EOF
|
||||
# 帶前綴(jsc-sdlc:plan)與裸名(plan)都要認。
|
||||
sname=${skill##*:}
|
||||
case "$sname" in
|
||||
plan|analyze|maintain)
|
||||
analyze|maintain)
|
||||
echo "[jsc][工作包閘門][ERR]:${brief} PR 尚未合併,禁止在此存取庫執行「${sname}」。${foreign:+其中 ${foreign}還不屬於領取中的工作包,更不該由這裡處理。}請先把該 PR 結清(合併或關閉),或執行 jsc-hooks/hooks/sdlc-gate.sh wp-unlock {owner}/{repo} {index} 解除;確定要整體放行請設 JSC_WP_GATE=off。本次技能呼叫已擋下。" >&2
|
||||
exit 2 ;;
|
||||
plan)
|
||||
# plan 只提醒不擋,理由見檔頭「plan 已從擋人名單移出」。放棄的是在製品上限,
|
||||
# 換來的是不再誤擋跟那支 PR 無關的新計畫;analyze 與 maintain 兩道仍在。
|
||||
echo "[jsc][工作包閘門]:${brief} PR 尚未合併。plan 是純邏輯階段、不碰程式碼,這裡只提醒不擋,但新計畫排進實作前請先把它結清。"
|
||||
[ -n "$foreign" ] && echo "[jsc][工作包閘門]:${foreign}不屬於這個存取庫領取中的工作包。那幾支交給領取它的工作階段結清:這裡不要改它的程式碼,也不要回它的留言。"
|
||||
exit 0 ;;
|
||||
implement)
|
||||
# implement 一律放行,連別包的 PR 未結清也放行:結清 PR 正是 implement 步驟 4 要做
|
||||
# implement 一律放行,連別包的 PR 未結清也放行:結清 PR 正是 implement 步驟 2 要做
|
||||
# 的事,擋掉就沒有任何路徑能解除這道鎖。領取檔不綁工作階段,擋下去連領取那一包的
|
||||
# 工作階段都會被自己的舊紀錄擋住,等於把流程鎖死。所以這裡只注入歸屬提醒。
|
||||
[ -n "$foreign" ] && echo "[jsc][工作包閘門]:${foreign}不屬於這個存取庫領取中的工作包。這裡只結清自己領取那一包的 PR${mine:+(${mine})};別包的 PR 不要改、留言也不要回,交給領取它的工作階段。"
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
# session-timer.sh mark # Stop/SessionEnd:更新最後活動時間
|
||||
# session-timer.sh report [sid] # 印出 {sid} {seconds};無紀錄印 0
|
||||
#
|
||||
# 結束碼:0=一律成功,四個子命令都走到最後那一行 exit 0,不認得的子命令也一樣(case 沒有
|
||||
# 相符分支就直接落到那一行)。這支接在 SessionStart 與 Stop/SessionEnd 上,本來就不擋人:
|
||||
# 狀態檔寫不進去只是少一筆計時,report 讀不到起始時間就印 0,都照樣 exit 0。
|
||||
# 唯一的非零來源是 lib.sh 載入失敗:本檔以 `. "$HERE/lib.sh"` 載入,沒有接 `|| true`,
|
||||
# 檔案不在時 sh 會就地結束並回 2。這兩個事件不擋工具呼叫,回 2 只會在宿主留下一筆 hook 錯誤。
|
||||
#
|
||||
# start 與 restart 的差別在「同一個 session id 會不會重複開始」:
|
||||
# start 給 Claude 這種每個工作階段都有自己 session id 的 CLI。續接同一階段時
|
||||
# SessionStart 會再觸發一次,覆寫起始時間會讓花費時間歸零。
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
# 產出:
|
||||
# $JSC_HOME/usage/skills.jsonl {ts,cli,session,skill}
|
||||
# $JSC_HOME/usage/chains.jsonl {ts,cli,session,from,to}(同 session 內前一技能 → 本技能)
|
||||
#
|
||||
# 結束碼:0=一律成功,只有這一種正常碼。取不到技能名(JSC_SKILL 與 stdin JSON 都沒有)就
|
||||
# 安靜降級,不寫任何紀錄直接 exit 0;寫得成紀錄也是 exit 0。這支接在 PostToolUse,
|
||||
# 技能已經跑完了,結束碼擋不掉任何事,所以連寫檔失敗都不回報。
|
||||
# 唯一的非零來源同 session-timer.sh:本檔以 `. "$HERE/lib.sh"` 載入,沒有接 `|| true`,
|
||||
# lib.sh 讀不到時 sh 會就地結束並回 2。
|
||||
HERE=$(dirname "$0"); . "$HERE/lib.sh"
|
||||
read_stdin
|
||||
skill="${JSC_SKILL:-$(json_str skill)}"
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
# Claude: UserPromptSubmit 的 stdout 會成為額外 context。
|
||||
# 其他 CLI: 由 hooks-install 以各自的規則檔(AGENTS.md 等)落地,本腳本仍可被 wrapper 呼叫。
|
||||
# 完整規則的唯一來源:jsc-meta 的 references/ste100.md。
|
||||
#
|
||||
# 結束碼:0=一律放行,而且只有這一種。本檔只把規則文字印到 stdout,不讀輸入、不碰檔案、
|
||||
# 不載入 lib.sh,所以沒有任何擋人路徑,也沒有會冒出非零碼的降級路徑。
|
||||
# UserPromptSubmit 的 stdout 會成為額外 context,這支的產出走的是 stdout,不是結束碼。
|
||||
echo "[jsc] 輸出規則:STE100 繁體中文,擬人台灣感。適用範圍是所有非程式碼輸出——程式碼註解、commit 訊息、PR 描述、wiki 頁、對使用者的回報、README 與各種文件都算。短句、一句一指令、主動語態、術語一致;台灣用語(預設、支援、相容、資訊);全形標點;去 AI 味(不用「總的來說」「首先/其次/最後」開場收尾套路、不諂媚);直接講重點。一律 UTF-8 無亂碼、無簡體字,檔案不加 BOM。完整規則見 jsc-meta 的 references/ste100.md。"
|
||||
echo "[jsc] 送出前自我檢查,命中任一項就先改再送出:1)簡體字(例:应、为、这、说、后、发);2)句尾用半形標點(. , ! ?),應為全形(。,!?);3)套路句(「總的來說」「綜上所述」「首先…其次…最後」);4)中文並列用半形「/」,應改頓號「、」;5)諂媚開場(「好問題」「當然可以」)。"
|
||||
exit 0
|
||||
|
||||
+62
-4
@@ -3,6 +3,17 @@
|
||||
#
|
||||
# 本機版本落後遠端發佈版本時擋下該次技能呼叫,並提示更新指令。
|
||||
#
|
||||
# 結束碼(hook 模式):0=放行 2=擋下該次技能呼叫,訊息走 stderr。
|
||||
# 安靜放行(exit 0)的情況要記清楚,這道閘門絕大多數時候走的是這幾條:逃生門
|
||||
# JSC_VERSION_GUARD=off、工具名取得到但不是 Skill、取不到技能名、技能名不是
|
||||
# jsc-{domain}:{name}、拆不出 domain、命中下方豁免清單那 7 支、讀不到本機實際載入版本、
|
||||
# 推導不出遠端站台、查不到遠端版本、本機版本等於或超前遠端。
|
||||
# 只有「本機落後遠端」這一條會 exit 2。
|
||||
# 結束碼(report、recommend):0=永遠成功,只讀不擋。結論看 stdout,不看結束碼。
|
||||
# 註:本檔以 `. "$HERE/lib.sh"` 載入共用函式,沒有接 `|| true`。lib.sh 讀不到時 sh 會就地
|
||||
# 結束並回 2,接在 PreToolUse 上就是無聲擋下每一次技能呼叫,上面那些放行路徑一條都跑不到
|
||||
# (write-guard.sh 踩過這個坑)。部署時要確認 hooks/lib.sh 跟這支腳本一起裝上。
|
||||
#
|
||||
# 輸入:stdin JSON(Claude 格式)或環境變數,兩者都收。
|
||||
# 工具名 JSC_TOOL_NAME、TOOL_NAME、stdin 的 tool_name
|
||||
# 技能名 JSC_SKILL、SKILL、stdin 的 skill
|
||||
@@ -30,6 +41,8 @@
|
||||
# deploy 卡在問不出模式那一步,跟直接擋 deploy 是同一種死鎖
|
||||
# jsc-gitea:wiki jsc-ask:ask 問完一定寫回 wiki 才算完成,理由同上一條,
|
||||
# 擋在這一步一樣是 deploy 做不完
|
||||
# jsc-hooks:repair hook 壞掉時唯一的修復路徑。修 hook 的技能被 hook 擋下,
|
||||
# 就沒有任何方法把 hook 修回來,跟直接擋 deploy 是同一種死鎖
|
||||
#
|
||||
# 逃生門:JSC_VERSION_GUARD=off 完全略過檢查(離線工作時用)。
|
||||
#
|
||||
@@ -37,13 +50,26 @@
|
||||
# 預設 600 秒內不重查(JSC_VERSION_TTL 可調)。hook 與 report 在同一支 CLI 內共用。
|
||||
# 舊路徑 $JSC_HOME/version-cache/{domain} 會在第一次讀取時複製到當前 CLI 的新路徑。
|
||||
#
|
||||
# 另有一個非 hook 的子指令:
|
||||
# 另有兩個非 hook 的子指令:
|
||||
# version-guard.sh report 把每個已安裝 jsc-* plugin 的版本比對印成 TSV,每行
|
||||
# 「{domain}<TAB>{本機}<TAB>{遠端}<TAB>{落後|最新|超前|查詢失敗}」,
|
||||
# 最後一行「behind<TAB>{落後個數}」。供 jsc-cli:deploy 判斷要不要
|
||||
# 把「更新」設成推薦選項。report 只讀不擋,永遠 exit 0。
|
||||
# 本機沒有 Claude 的 plugin 註冊檔時改印「noregistry<TAB>{路徑}」
|
||||
# 再接 behind 0:那代表這台機器無法做版本檢查,跟「全部最新」是兩件事。
|
||||
#
|
||||
# version-guard.sh recommend
|
||||
# 把 report 那張表收斂成一個結論,只印一行、只有這一種格式:
|
||||
# recommend<TAB>update|none|unverifiable
|
||||
# 判定規則(呼叫端不必自己再判一次):
|
||||
# update 任何一個 plugin 落後就是 update,一個就夠,不等多數
|
||||
# unverifiable 查不到註冊資訊(noregistry),或一列 domain 都沒有,
|
||||
# 或每一列都是查詢失敗——沒有任何一項查得到的證據
|
||||
# none 至少有一列查得到結果,而且沒有任何一項落後
|
||||
# 查詢失敗的那幾列不計入:查不到不等於最新,也不等於落後,只是沒有證據。
|
||||
# 輸出格式必須穩定,別的 domain 的技能直接讀第二欄;證據表要另外看的話
|
||||
# 再呼叫一次 report,這裡刻意不混印,免得 cut 取值被表格內容打亂。
|
||||
# recommend 只讀不擋,永遠 exit 0:判定結果只看那一行的第二欄。
|
||||
HERE=$(dirname "$0"); . "$HERE/lib.sh"
|
||||
|
||||
REG="$HOME/.claude/plugins/installed_plugins.json"
|
||||
@@ -165,7 +191,9 @@ ver_cmp() { # $1=版本 A $2=版本 B
|
||||
}
|
||||
|
||||
# ── report:一次比對所有已安裝的 jsc plugin(非 hook 模式,不讀 stdin)
|
||||
if [ "${1:-}" = "report" ]; then
|
||||
# 抽成函式是為了讓 recommend 讀同一份輸出。判定規則只寫在這一支腳本裡,recommend 直接解析
|
||||
# 這裡印出來的表;兩邊各實作一次比對邏輯就會漂移,結論與證據對不起來。
|
||||
do_report() {
|
||||
# 註冊檔不存在或讀不到就明講。這裡不能只印 behind 0:呼叫端會把它讀成「都是最新」,
|
||||
# 於是把「這台機器無法做版本檢查」誤報成「不用更新」。
|
||||
# 舊版用 `tr -d '\n' < "$REG" 2>/dev/null`,那個 2>/dev/null 只蓋住 tr 的 stderr,
|
||||
@@ -173,7 +201,7 @@ if [ "${1:-}" = "report" ]; then
|
||||
if [ ! -f "$REG" ] || [ ! -r "$REG" ]; then
|
||||
printf 'noregistry\t%s\n' "$REG"
|
||||
printf 'behind\t0\n'
|
||||
exit 0
|
||||
return 0
|
||||
fi
|
||||
ho=$(remote_host_owner)
|
||||
r_host=$(printf '%s' "$ho" | cut -d' ' -f1)
|
||||
@@ -197,6 +225,36 @@ if [ "${1:-}" = "report" ]; then
|
||||
printf '%s\t%s\t%s\t%s\n' "$d" "${lv:-?}" "${rv:-?}" "$st"
|
||||
done
|
||||
printf 'behind\t%s\n' "$behind"
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "report" ]; then
|
||||
do_report
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── recommend:把 report 那張表收斂成一個結論(非 hook 模式,不讀 stdin)
|
||||
# 規則的唯一來源就是這一段,jsc-cli:deploy 只讀第二欄,不再自己解那張表。
|
||||
if [ "${1:-}" = "recommend" ]; then
|
||||
rep=$(do_report)
|
||||
verdict=none
|
||||
if printf '%s\n' "$rep" | grep -q '^noregistry '; then
|
||||
# 沒有本機註冊檔,一項都比不了。這跟「全部最新」是兩件事,不能推薦 none。
|
||||
verdict=unverifiable
|
||||
else
|
||||
behind_n=$(printf '%s\n' "$rep" | sed -n 's/^behind //p' | head -n1)
|
||||
# 查得到結果的列:狀態欄是落後、最新或超前三種之一。查詢失敗那幾列不算證據。
|
||||
known=$(printf '%s\n' "$rep" | awk -F'\t' '$1 != "behind" && $1 != "noregistry" && ($4 == "落後" || $4 == "最新" || $4 == "超前")' | wc -l | tr -d ' ')
|
||||
if [ -n "$behind_n" ] && [ "$behind_n" -gt 0 ] 2>/dev/null; then
|
||||
verdict=update
|
||||
elif [ "${known:-0}" -gt 0 ] 2>/dev/null; then
|
||||
verdict=none
|
||||
else
|
||||
# 一列 domain 都沒有,或每一列都查詢失敗:兩種都是「沒有任何查得到的證據」。
|
||||
verdict=unverifiable
|
||||
fi
|
||||
fi
|
||||
printf 'recommend\t%s\n' "$verdict"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -225,7 +283,7 @@ domain=${domain%%:*}
|
||||
|
||||
# 豁免清單
|
||||
case "$skill" in
|
||||
jsc-cli:deploy|jsc-hooks:hooks-install|jsc-cli:models|jsc-meta:*|jsc-ask:ask|jsc-gitea:wiki) exit 0 ;;
|
||||
jsc-cli:deploy|jsc-hooks:hooks-install|jsc-hooks:repair|jsc-cli:models|jsc-meta:*|jsc-ask:ask|jsc-gitea:wiki) exit 0 ;;
|
||||
esac
|
||||
|
||||
# 更新指令依實際 CLI 給。印別的 CLI 的指令等於沒給指令,使用者照著打只會失敗。
|
||||
|
||||
Executable
+280
@@ -0,0 +1,280 @@
|
||||
#!/usr/bin/env sh
|
||||
# write-guard.sh — 寫入與提交的前置閘門(PreToolUse)。三種擋人模式接在兩個 matcher 上,
|
||||
# 另有一個給技能收尾呼叫的解除模式。
|
||||
#
|
||||
# 用法:
|
||||
# write-guard.sh stage matcher Write|Edit|MultiEdit:plan 與 analyze 階段鎖存在時擋下寫檔
|
||||
# write-guard.sh review matcher Write|Edit|MultiEdit:稽核類技能執行中擋下寫檔
|
||||
# write-guard.sh commit matcher Bash:擋下 git add -A 後的單次提交,以及含簡體字或亂碼的提交訊息
|
||||
# write-guard.sh release 不接 hook,由稽核技能收尾時自己呼叫:清掉 review 模式認人用的那份
|
||||
# 紀錄,一律 exit 0,紀錄本來就不存在也算成功
|
||||
#
|
||||
# 輸入相容(比照其他 hook,stdin JSON 與環境變數都收,缺欄位一律安靜降級 exit 0):
|
||||
# 工具名 JSC_TOOL_NAME、TOOL_NAME、stdin 的 tool_name
|
||||
# 技能名 JSC_SKILL、SKILL、stdin 的 skill
|
||||
# 指令 JSC_TOOL_COMMAND、stdin 的 command
|
||||
#
|
||||
# 結束碼:0=放行、解除完成、資料不足或不認得的模式;2=擋下,訊息走 stderr。
|
||||
# 逃生門:JSC_WRITE_GUARD=off,三種擋人模式全部略過(release 不受影響,清紀錄擋不到任何人)。
|
||||
# review 模式另有 JSC_WRITE_GUARD_TTL(預設 900 秒),見下方「稽核技能的時效」。
|
||||
#
|
||||
# 覆蓋範圍要據實看待:只有 claude 有 PreToolUse,這道閘門只在 claude 上擋得下來。
|
||||
# codex、copilot、antigravity、kiro 都沒有 pre-tool 事件,三種模式在那四支上一次都擋不到,
|
||||
# 規則只剩 SKILL.md 的散文,回報時不得暗示每支 CLI 都擋得住。
|
||||
#
|
||||
# --- stage 模式 ---
|
||||
#
|
||||
# 階段鎖狀態檔沿用 sdlc-gate.sh 那一份($JSC_HOME/sessions/{sid}.stage,單行
|
||||
# 「{階段}<TAB>{必要標籤}<TAB>{上鎖時的模型}」),這裡只讀不寫:判準與格式留在 sdlc-gate.sh,
|
||||
# 兩邊各存一份就會漂移。plan 與 analyze 是純邏輯階段,產出是 wiki 頁不是程式碼,所以那兩個
|
||||
# 階段鎖著時 Write、Edit、MultiEdit 一律擋下;implement 與 maintain 本來就要寫檔,放行。
|
||||
#
|
||||
# --- review 模式 ---
|
||||
#
|
||||
# 目前技能取自 skill-usage.sh 已經記下的那一份($JSC_HOME/sessions/{sid}.lastskill),
|
||||
# 環境變數餵得到技能名時優先用環境變數。code-review 與 api-doc 只回報發現、不改程式碼,
|
||||
# 執行中出現寫入就是越權,擋下。
|
||||
#
|
||||
# comment-cleanup 不擋:它本來就要改檔,只是限定「僅註解行」。要精確判定得先解析工具參數裡
|
||||
# 帶跳脫字元的整份新內容,再逐語言判斷哪幾行是註解——判錯就會擋掉合法的清理,代價比漏擋大。
|
||||
# 這一支因此只放行,寫入範圍由 SKILL.md 的散文與後續審查把關,不在這裡硬做。
|
||||
#
|
||||
# 稽核技能的時效:沒有「技能結束」事件可讀,只有「最近一次呼叫的技能」這個事實。不設界線的話,
|
||||
# 稽核技能跑完之後每一次寫檔都會被擋到下一支技能被呼叫為止。所以紀錄超過 JSC_WRITE_GUARD_TTL
|
||||
# 秒就當那支技能早已跑完,放行;取不到紀錄時間也放行。
|
||||
#
|
||||
# --- release 模式 ---
|
||||
#
|
||||
# 介面:write-guard.sh release,不吃其他參數、不接任何 hook 事件,由呼叫端自己執行。
|
||||
# 呼叫端是 jsc-review:code-review 與 jsc-review:api-doc,兩支在收尾(把發現清單交回呼叫端)
|
||||
# 那一步各呼叫一次。做的事只有一件:刪掉 $JSC_HOME/sessions/{sid}.lastskill。
|
||||
#
|
||||
# 為什麼要有這個模式:review 模式靠那份紀錄認人,而 skill-usage.sh 記的是「最近一次載入的
|
||||
# 技能」,不是「還在跑的技能」。code-review 的契約是只回報、修不修由呼叫端決定,稽核結束後
|
||||
# 呼叫端本來就要動手改——那一刻紀錄仍寫著 code-review,JSC_WRITE_GUARD_TTL 內每一次寫入
|
||||
# 都被擋,解除路徑只剩逃生門或空等。閘門不得把解除自己的路徑一起鎖掉,所以補一個由呼叫端
|
||||
# 自己按的解除鍵。
|
||||
# 只刪那一份紀錄,不碰階段鎖:階段鎖歸 sdlc-gate.sh unlock 管,兩件事混在一起會互相解除。
|
||||
#
|
||||
# --- commit 模式 ---
|
||||
#
|
||||
# 擋兩件事:
|
||||
# 1. 同一道指令裡同時有「git add -A(或 --all、.)」與「git commit」。那等於把所有待提交
|
||||
# 變更併成一次提交,型別與功能分組就消失了。跨兩次工具呼叫的同一組動作不擋——那要記
|
||||
# 跨呼叫狀態,而被擋下的人沒有辦法讓那個狀態自己消失,閘門會把解除自己的路徑一起鎖掉。
|
||||
# 2. 提交訊息含簡體字、亂碼或非 UTF-8 編碼。判定整段轉呼叫 lang-guard.sh(字表在
|
||||
# hooks/simplified.txt),這裡不抄第二份樣式;連帶地 JSC_LANG_GUARD=off 也會關掉這一項,
|
||||
# 因為那本來就是同一條規則。
|
||||
set -u
|
||||
|
||||
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
# lib.sh 讀不到就直接放行。這裡不能寫成「. lib.sh || true」:dash 的 `.` 找不到檔案時會結束
|
||||
# 整支 shell,後面的 || true 一次都跑不到,2>/dev/null 還把原因蓋掉,三種模式全部變成無訊息
|
||||
# 的 exit 2——而 PreToolUse 的 exit 2 正是「擋下」,等於每一次寫檔與提交都被無聲擋死。
|
||||
[ -r "$HERE/lib.sh" ] || exit 0
|
||||
. "$HERE/lib.sh"
|
||||
# lib.sh 沒載到時這個變數就沒人設,下面兩個模式都要用它組狀態檔路徑,補一份同樣的預設值。
|
||||
JSC_HOME="${JSC_HOME:-$HOME/.jsc}"
|
||||
|
||||
mode="${1:-}"
|
||||
case "$mode" in
|
||||
stage|review|commit|release) ;;
|
||||
*) exit 0 ;; # 不認得的模式一律安靜放行,不中斷宿主 CLI
|
||||
esac
|
||||
|
||||
read_stdin 2>/dev/null || STDIN_JSON=""
|
||||
|
||||
# ── release:稽核技能收尾時清掉「目前技能」紀錄,解除 review 模式的擋下
|
||||
#
|
||||
# 排在逃生門之前,也不受 JSC_WRITE_GUARD=off 影響:清一筆紀錄從來不會擋到任何人,
|
||||
# 而收尾呼叫失敗才是真的麻煩——閘門關著的機器上跑過一輪,紀錄留著,下次開啟就自鎖。
|
||||
if [ "$mode" = release ]; then
|
||||
rm -f "$JSC_HOME/sessions/$(session_id).lastskill" 2>/dev/null || true
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[ "${JSC_WRITE_GUARD:-on}" = "off" ] && exit 0
|
||||
|
||||
deny() { # $1=擋下的理由 $2=修法
|
||||
printf '[jsc][寫入閘門][ERR]:%s\n' "$1" >&2
|
||||
printf '%s\n' "$2" >&2
|
||||
printf '確定要略過這道閘門:JSC_WRITE_GUARD=off\n' >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
# 檔案的最後修改時間(epoch 秒)。GNU 與 BSD 的取法不同,三種都試過才放棄;
|
||||
# 取不到就不輸出,呼叫端當成無法判定並放行。
|
||||
file_mtime() { # $1=檔案
|
||||
_m=$(date -r "$1" +%s 2>/dev/null)
|
||||
[ -n "$_m" ] || _m=$(stat -c %Y "$1" 2>/dev/null)
|
||||
[ -n "$_m" ] || _m=$(stat -f %m "$1" 2>/dev/null)
|
||||
printf '%s' "$_m"
|
||||
}
|
||||
|
||||
# 只在寫檔類工具上判定。工具名取不到就當成沒有篩選條件,交給後面的狀態判定——
|
||||
# 接線的 matcher 已經先篩過一輪,這裡再擋一次只會把用環境變數餵資料的 CLI 全部放掉。
|
||||
write_tool_or_exit() {
|
||||
_t="${JSC_TOOL_NAME:-${TOOL_NAME:-$(json_str tool_name)}}"
|
||||
case "$_t" in
|
||||
""|Write|Edit|MultiEdit) return 0 ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── stage:plan 與 analyze 階段鎖存在時擋下寫檔
|
||||
if [ "$mode" = stage ]; then
|
||||
write_tool_or_exit
|
||||
state="$JSC_HOME/sessions/$(session_id).stage"
|
||||
[ -f "$state" ] && [ -r "$state" ] || exit 0
|
||||
stage=$(cut -f1 "$state" 2>/dev/null | head -n1)
|
||||
case "$stage" in
|
||||
plan|analyze) ;;
|
||||
*) exit 0 ;; # implement 與 maintain 本來就要寫檔;讀不出階段也放行
|
||||
esac
|
||||
deny "目前鎖在 SDLC「$stage」階段,這個階段只產出計畫或分析頁,不寫檔案。本次寫入已擋下。" \
|
||||
"改法:把結論寫進該階段的 wiki 頁;真的要動程式碼請先進入 implement 階段。
|
||||
解除階段鎖:jsc-hooks/hooks/sdlc-gate.sh unlock"
|
||||
fi
|
||||
|
||||
# ── review:稽核類技能執行中擋下寫檔
|
||||
if [ "$mode" = review ]; then
|
||||
write_tool_or_exit
|
||||
skill="${JSC_SKILL:-${SKILL:-$(json_str skill)}}"
|
||||
if [ -z "$skill" ]; then
|
||||
last="$JSC_HOME/sessions/$(session_id).lastskill"
|
||||
if [ -f "$last" ] && [ -r "$last" ]; then
|
||||
mt=$(file_mtime "$last")
|
||||
now=$(now_epoch)
|
||||
if [ -n "$mt" ] && [ -n "$now" ]; then
|
||||
age=$((now - mt))
|
||||
[ "$age" -lt "${JSC_WRITE_GUARD_TTL:-900}" ] && skill=$(cat "$last" 2>/dev/null)
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
[ -n "$skill" ] || exit 0
|
||||
case "$skill" in
|
||||
jsc-review:code-review|code-review)
|
||||
deny "jsc-review:code-review 執行中。這支技能只回報發現,修不修由呼叫端決定,執行中不寫檔。本次寫入已擋下。" \
|
||||
"改法:先讓稽核跑完並收下 file:line、嚴重度與重構手法,再由呼叫端決定要不要改。" ;;
|
||||
jsc-review:api-doc|api-doc)
|
||||
deny "jsc-review:api-doc 執行中。這支技能只稽核 Swagger 文件屬性,從不修改程式碼。本次寫入已擋下。" \
|
||||
"改法:先讓稽核跑完並收下缺漏清單,再由呼叫端決定要不要補。" ;;
|
||||
esac
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── commit:擋下 git add -A 後的單次提交,與含簡體字或亂碼的提交訊息
|
||||
|
||||
# 從 stdin JSON 取帶跳脫字元的字串欄位。lib.sh 的 json_str 以 [^"]* 比對,遇到訊息裡的 \"
|
||||
# 就在那裡截斷,提交訊息會少掉後半段——而訊息內容正是這裡要檢查的東西,所以自己解一次跳脫。
|
||||
json_escaped_str() { # $1=欄位名
|
||||
printf '%s' "$STDIN_JSON" | awk -v key="$1" '
|
||||
{ s = s $0 "\n" }
|
||||
END {
|
||||
n = length(s); i = 1; found = 0
|
||||
while (i <= n) {
|
||||
if (substr(s, i, 1) != "\"") { i++; continue }
|
||||
buf = ""; i++
|
||||
while (i <= n) {
|
||||
c = substr(s, i, 1)
|
||||
if (c == "\\") {
|
||||
e = substr(s, i + 1, 1)
|
||||
if (e == "n") buf = buf "\n"
|
||||
else if (e == "t") buf = buf "\t"
|
||||
else if (e == "r") buf = buf "\r"
|
||||
else if (e == "u") { i += 6; continue }
|
||||
else buf = buf e
|
||||
i += 2; continue
|
||||
}
|
||||
if (c == "\"") { i++; break }
|
||||
buf = buf c; i++
|
||||
}
|
||||
if (found) { printf "%s", buf; exit }
|
||||
j = i
|
||||
while (j <= n && substr(s, j, 1) ~ /[ \t\r\n]/) j++
|
||||
if (buf == key && substr(s, j, 1) == ":") {
|
||||
k = j + 1
|
||||
while (k <= n && substr(s, k, 1) ~ /[ \t\r\n]/) k++
|
||||
if (substr(s, k, 1) != "\"") { i = k; continue }
|
||||
found = 1; i = k
|
||||
}
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
cmd="${JSC_TOOL_COMMAND:-$(json_escaped_str command)}"
|
||||
[ -n "$cmd" ] || exit 0
|
||||
case "$cmd" in
|
||||
*git*) ;;
|
||||
*) exit 0 ;; # 不是 git 指令就不關這道閘門的事
|
||||
esac
|
||||
|
||||
# 把所有待提交變更一次加進索引的三種寫法。-A 也認 -vA 這類併寫的短旗標。
|
||||
has_add_all() { # $1=指令
|
||||
printf '%s' "$1" \
|
||||
| grep -qE 'git[[:space:]]+add[[:space:]]+(-[A-Za-z]*A([[:space:]]|$)|--all([[:space:]]|$)|\.([[:space:]]|$))'
|
||||
}
|
||||
|
||||
has_commit() { # $1=指令
|
||||
printf '%s' "$1" | grep -qE 'git[[:space:]]+commit([[:space:]]|$)'
|
||||
}
|
||||
|
||||
if has_add_all "$cmd" && has_commit "$cmd"; then
|
||||
deny "這道指令把全部變更一次加進索引再提交,型別與功能分組會全部消失。本次執行已擋下。" \
|
||||
"改法:依 conventional type 與功能分組,逐組 git add {檔案} 再各自 git commit。
|
||||
分組與訊息格式交給 /jsc-git:commit 處理。"
|
||||
fi
|
||||
|
||||
# 提交訊息:取 -m 後面那一段。帶引號就讀到成對的引號為止,沒帶引號就讀到下一個空白。
|
||||
_sq=$(printf '\047')
|
||||
commit_message() { # $1=指令
|
||||
printf '%s' "$1" | awk -v sq="$_sq" '
|
||||
{
|
||||
s = $0; n = length(s)
|
||||
i = index(s, "-m")
|
||||
if (i == 0) exit
|
||||
i += 2
|
||||
while (i <= n && substr(s, i, 1) ~ /[ \t=]/) i++
|
||||
q = substr(s, i, 1)
|
||||
if (q == "\"" || q == sq) {
|
||||
i++
|
||||
while (i <= n) {
|
||||
c = substr(s, i, 1)
|
||||
if (c == "\\") { out = out substr(s, i + 1, 1); i += 2; continue }
|
||||
if (c == q) break
|
||||
out = out c; i++
|
||||
}
|
||||
} else {
|
||||
while (i <= n && substr(s, i, 1) !~ /[ \t]/) { out = out substr(s, i, 1); i++ }
|
||||
}
|
||||
printf "%s", out
|
||||
}'
|
||||
}
|
||||
|
||||
has_commit "$cmd" || exit 0
|
||||
msg=$(commit_message "$cmd")
|
||||
[ -n "$msg" ] || exit 0
|
||||
|
||||
# 簡體字、亂碼與編碼判定整段轉呼叫 lang-guard.sh,樣式與字表都不在這裡留第二份。
|
||||
# 那支腳本吃的是檔案,所以訊息先落成暫存檔;建不出暫存檔就放行,回報不該再變成一次失敗。
|
||||
lang_hits() { # $1=文字;有問題就把證據印到 stdout 並回傳 1
|
||||
[ -f "$HERE/lang-guard.sh" ] || return 0
|
||||
_t=$(mktemp 2>/dev/null) || return 0
|
||||
printf '%s\n' "$1" > "$_t" 2>/dev/null || { rm -f "$_t"; return 0; }
|
||||
_o=$(JSC_CHANGED_FILE="$_t" sh "$HERE/lang-guard.sh" </dev/null 2>&1)
|
||||
_rc=$?
|
||||
rm -f "$_t"
|
||||
# 只有 exit 2 是「確定命中」。exit 0 是乾淨或資料不足,其餘結束碼代表那支腳本自己出狀況,
|
||||
# 兩種都放行:拿判不出來的結果擋提交,等於把護欄變成故障點。
|
||||
[ "$_rc" -eq 2 ] || return 0
|
||||
# 只留命中證據:開頭那句與 lang-guard.sh 自己的修法兩行由本檔的訊息取代,重複印只是噪音。
|
||||
printf '%s\n' "$_o" | grep -vF "$_t" | grep -v '^\[jsc\]' \
|
||||
| grep -v '^ 修法:' | grep -v '^ 規則正文' | head -n 4
|
||||
return 1
|
||||
}
|
||||
|
||||
evidence=$(lang_hits "$msg") && exit 0
|
||||
deny "提交訊息有簡體字、亂碼或編碼問題。本次執行已擋下。
|
||||
$evidence" \
|
||||
"改法:訊息改寫成繁體中文、UTF-8、無亂碼。字表在 jsc-hooks 的 hooks/simplified.txt,
|
||||
規則正文見 jsc-meta 的 references/ste100.md。"
|
||||
+4
-2
@@ -1,12 +1,14 @@
|
||||
{
|
||||
"name": "jsc-hooks",
|
||||
"version": "0.3.1",
|
||||
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、註解範圍守門、繁中編碼守門、部署後強制重啟",
|
||||
"version": "0.3.2",
|
||||
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、註解範圍守門、繁中編碼守門、部署後強制重啟、寫入與提交閘門",
|
||||
"skills": "./skills/",
|
||||
"jsc": {
|
||||
"requires": {
|
||||
"jsc-cli": ">=0.2.1",
|
||||
"jsc-gitea": ">=0.1.7",
|
||||
"jsc-git": ">=0.1.1",
|
||||
"jsc-meta": ">=0.2.3",
|
||||
"jsc-review": ">=0.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
---
|
||||
name: hooks-install
|
||||
description: Wire jsc hooks (STE100 guard, session timer, skill usage logger, SDLC model gate, plugin version guard, post-deploy restart gate, comment scope scanner, language guard) into every installed AI CLI, purging all pre-existing hooks first — third-party ones included, backed up before removal. Drive it per CLI through tools/wire-cli.sh purge, tools/wire-cli.sh, tools/wire-cli.sh smoke and tools/scan-hook-errors.sh. Hand any hook error, wiring or runtime, to jsc-hooks:repair, which must finish with a PR against develop; aborting the rest of the install to start that repair is allowed. Use after installing or updating the jsc plugin set; not for writing new hooks.
|
||||
description: Wire jsc hooks (STE100 guard, session timer, skill usage logger, SDLC model gate, plugin version guard, post-deploy restart gate, comment scope scanner, language guard, write and commit guard) into every installed AI CLI, purging all pre-existing hooks first — third-party ones included, backed up before removal. Drive it per CLI through tools/wire-cli.sh purge, tools/wire-cli.sh, tools/wire-cli.sh status, tools/wire-cli.sh smoke and tools/scan-hook-errors.sh. Hand any hook error, wiring or runtime, to jsc-hooks:repair, which must finish with a PR against develop; aborting the rest of the install to start that repair is allowed. Use after installing or updating the jsc plugin set; not for writing new hooks.
|
||||
---
|
||||
|
||||
# hooks-install — wire jsc hooks into every installed CLI
|
||||
|
||||
Goal: make the eight hooks (`ste100-guard.sh`, `session-timer.sh`, `skill-usage.sh`, `sdlc-gate.sh`, `version-guard.sh`, `restart-gate.sh`, `comment-scope.sh`, `lang-guard.sh`) effective in every CLI, with nothing else wired alongside them.
|
||||
Goal: make the nine hooks (`ste100-guard.sh`, `session-timer.sh`, `skill-usage.sh`, `sdlc-gate.sh`, `version-guard.sh`, `restart-gate.sh`, `comment-scope.sh`, `lang-guard.sh`, `write-guard.sh`) effective in every CLI, with nothing else wired alongside them.
|
||||
|
||||
Install on a clean slate. Every CLI is purged of all hooks first, third-party ones included, so a later failure has exactly one owner. `tools/wire-cli.sh purge` backs up every file it touches before it removes anything, so the removal stays reversible.
|
||||
|
||||
The wiring commands stored in user config use `$JSC_HOME/current/jsc-hooks`, not the versioned plugin cache path and not the development checkout. `tools/wire-cli.sh {cli}` creates or refreshes that symlink before it writes `notify`, shell aliases or Kiro hook JSON, then verifies the linked scripts exist. If the filesystem cannot create the symlink, the script must say so and explicitly fall back to the current root; it must never write a silent broken path. 外掛提供的 `hooks/hooks.json` 也必須遵守同一條規則:主機有提供 `${CLAUDE_PLUGIN_ROOT}` 時才使用它;其他 CLI 讀取同一份 manifest 時,必須退回 `$JSC_HOME/current/jsc-hooks`,避免 Claude 專用變數未設定時展開成 `/hooks/...`。
|
||||
The wiring commands stored in user config use `$JSC_HOME/current/jsc-hooks`, not the versioned plugin cache path and not the development checkout. `tools/wire-cli.sh {cli}` creates or refreshes that symlink before it writes `notify`, shell aliases or Kiro hook JSON, then verifies the linked scripts exist. If the filesystem cannot create the symlink, the script must say so and explicitly fall back to the current root; it must never write a silent broken path. The bundled `hooks/hooks.json` follows the same rule: use `${CLAUDE_PLUGIN_ROOT}` only where the host provides it, and fall back to `$JSC_HOME/current/jsc-hooks` for any other CLI reading the same manifest, so an unset Claude-only variable never expands into `/hooks/...`.
|
||||
|
||||
Only claude has PreToolUse, PostToolUse and UserPromptSubmit, so only claude reports `wired`. On codex, copilot, antigravity and kiro neither the version guard nor the post-deploy restart gate can be wired at all, and the SDLC gate degrades to the skill-step check, so all four report `degraded` — report that gap as the script words it instead of implying every CLI is covered. On those four the restart gate blocks no skill call whatsoever: the state file is still written and still cleared at the next session start, so the restart itself rests on the `jsc-cli:deploy` closing message.
|
||||
Only claude has PreToolUse, PostToolUse and UserPromptSubmit, so only claude reports `wired`. On codex, copilot, antigravity and kiro neither the version guard, the post-deploy restart gate nor any mode of the write and commit guard can be wired at all, and the SDLC gate degrades to the skill-step check, so all four report `degraded` — report that gap as the script words it instead of implying every CLI is covered. On those four the restart gate blocks no skill call whatsoever: the state file is still written and still cleared at the next session start, so the restart itself rests on the `jsc-cli:deploy` closing message.
|
||||
|
||||
`comment-scope.sh` and `lang-guard.sh` both reach all five, wired at the same set of places, but on a different event and at a different moment each. Report the timing per CLI; never state it as one uniform behaviour:
|
||||
|
||||
@@ -32,23 +32,29 @@ The detailed flow **MUST run as a sub agent**; the main agent only reports the s
|
||||
|
||||
## Steps
|
||||
|
||||
1. Run `jsc-cli/tools/detect-clis.sh`. Done when you hold the list of installed CLIs; when the list is empty, report that and stop.
|
||||
2. For each installed CLI, run `tools/wire-cli.sh purge {cli}`. The script backs up every file it touches, removes all hooks, re-reads each file to confirm the removal, and restores the backup by itself when a check fails. Marker matching trims leading and trailing whitespace, so an indented or padded marker block is still removed as the same jsc-owned block. 對 Codex 而言,後續 `status` 也必須回報已安裝的 `jsc-hooks` manifest 是否仍含有舊版 `UserPromptSubmit` command,因為它可能把 `${CLAUDE_PLUGIN_ROOT}` 展開成 `/hooks/...`。Done when every CLI has printed exactly one `status=purged|skipped|failed reason=...` line and you have noted the backup directory path from its `[jsc]` output.
|
||||
3. For each installed CLI, run `tools/wire-cli.sh {cli}`. The script owns both the wiring and its verification: it refreshes `$JSC_HOME/current/jsc-hooks`, writes the config, alias or hook file inside a `<!-- jsc-hooks -->` (or `# jsc-hooks`) marker block, re-reads every file it wrote, confirms the block is present and correctly placed, and confirms the stored runtime paths resolve to existing scripts before it prints a success status. Trust its first line, `status=wired|degraded|skipped|failed reason=...`. Exit 2 means a bad CLI name, not a wiring outcome — fix the name and rerun. Done when every installed CLI has printed exactly one `status=` line and none exited 2.
|
||||
4. For each installed CLI, run `tools/wire-cli.sh smoke {cli}`. This runs all eight hooks once each, every wired mode included, plus each decision path of the work-package check and of the restart gate, and catches what the wiring check cannot see: a hook that is wired correctly and still fails when it executes. Done when every CLI has printed one `status=ok|failed reason=...` line plus 31 result lines: 14 hook mode lines, 5 work-package decision lines, and 12 restart-gate decision and cleanup lines. The line count is higher than the hook count because `sdlc-gate.sh`, `comment-scope.sh` and `lang-guard.sh` each have multiple wired modes.
|
||||
5. For each installed CLI, run `tools/scan-hook-errors.sh --cli {cli}`. Only claude keeps hook results in its native records and can answer `clean` or `errors`; codex, copilot, antigravity and kiro answer `unavailable`, and their runtime evidence comes from step 4 alone. Done when every CLI has printed one `status=clean|errors|unavailable reason=...` line and the four `unavailable` CLIs are reported as exactly that, not as clean.
|
||||
6. For each error — `purge` failed, wiring failed, smoke failed, or a scanned error with `jsc=true` — run `tools/report-error.sh --hook {script name} --exit {code} --summary "{reason}" --cli {cli}` with the script's `[jsc]` output on stdin, then hand the failure to `jsc-hooks:repair`, which **MUST run as a sub agent** and must finish by opening a PR against `develop`. Aborting the remaining installs here is allowed as long as the repair starts. A scanned error with `jsc=false` belongs to a third-party hook: report it and leave it alone. Done when each error has either an `ERROR_{HASH}` page name on stdout, or an empty exit 0 meaning `JSC_WIKI_REPO_ERROR` and `JSC_WIKI_REPO` are both unset — in that second case carry the reason into step 7 instead. Skip this step when every CLI passed all four checks.
|
||||
7. Report four results per CLI — purge, wiring, smoke, scan — each with the reason its script printed, plus any `ERROR_{HASH}` page name and repair PR URL. Done when every detected CLI has exactly one status per check and every repair has a PR against `develop`.
|
||||
1. Take the CLI list from the caller when it hands one over — `jsc-cli:deploy` passes the list it already detected, and probing the same five executables a second time buys nothing. Run `jsc-cli/tools/detect-clis.sh` yourself only when no list came in; that fallback is what keeps this skill usable when it is called on its own. The script always exits 0 and prints one `name<TAB>path<TAB>version` line per installed CLI. Done when you hold that list and have said which of the two ways produced it; when it is empty, report that no CLI was detected and stop.
|
||||
2. Run the five-stage pipeline **purge → wire → status → smoke → scan** once per detected CLI. Run the first CLI's pipeline on its own, because `tools/wire-cli.sh {cli}` is what refreshes the shared `$JSC_HOME/current/jsc-hooks` link and two CLIs must not rewrite it at the same time; once that first pipeline has finished, run every remaining CLI's pipeline in parallel, one sub agent per CLI — the five stages of one CLI stay in this order, but different CLIs touch different config files and share nothing else. Every stage prints its verdict on its first line, so read that line and never infer the outcome from the prose below it.
|
||||
1. `tools/wire-cli.sh purge {cli}` — backs up every file it touches, removes all hooks, re-reads each file to confirm the removal, and restores the backup by itself when a check fails. Marker matching trims leading and trailing whitespace, so an indented or padded marker block is still removed as the same jsc-owned block. Exit 0 is `purged`, exit 3 is `skipped` (that CLI's executable is not on this machine, so skip its remaining stages too), exit 4 is `failed` and goes to step 3. Exit 2 is a bad CLI name, not a purge outcome — fix the name and rerun the stage.
|
||||
2. `tools/wire-cli.sh {cli}` — owns both the wiring and its verification: it refreshes the link, writes the config, alias or hook file inside a `<!-- jsc-hooks -->` (or `# jsc-hooks`) marker block, re-reads every file it wrote, confirms the block is present and correctly placed, and confirms the stored runtime paths resolve to existing scripts before it prints a success status. Exit 0 is `wired`, exit 1 is `degraded` and is the expected result on the four non-claude CLIs, exit 3 is `skipped`, exit 4 is `failed` and goes to step 3. Exit 2 is a bad CLI name — fix the name and rerun.
|
||||
3. `tools/wire-cli.sh status {cli}` — the read-only inventory of what the previous stage wrote. It writes nothing and runs no hook, so it is safe to run right after wiring. Exit 0 is `wired`, exit 1 is `degraded`, exit 3 is `skipped`, exit 5 is `unwired`, which names every missing item and means the wiring stage has to run again before you continue. Exit 2 is a bad CLI name. For codex this stage is the only one that reads the installed `jsc-hooks` manifest in the Codex plugin cache and reports a stale `UserPromptSubmit` command there, the one that expands `${CLAUDE_PLUGIN_ROOT}` into `/hooks/...`; carry that item into the report.
|
||||
4. `tools/wire-cli.sh smoke {cli}` — runs every wired mode of all nine hooks once, plus each decision path of the work-package check, of the restart gate and of the write and commit guard. It catches what the wiring check cannot see: a hook that is wired correctly and still fails when it executes. It prints its own result-line count as `lines<TAB>{count}` and asserts that count against what it expected to run, so read the number from that line and never restate a number of your own. Exit 0 is `ok`, exit 4 is `failed` — either a hook errored or the line count did not match, and both go to step 3. Exit 2 is a bad CLI name.
|
||||
5. `tools/scan-hook-errors.sh --cli {cli}` — only claude keeps hook results in its native records and can answer `clean` or `errors`; codex, copilot, antigravity and kiro answer `unavailable`, and their runtime evidence comes from the smoke stage alone. Exit 0 covers both `clean` and `unavailable`, exit 1 is `errors` and every entry with `jsc=true` goes to step 3, exit 2 is a bad CLI name.
|
||||
|
||||
Done when every detected CLI has exactly one verdict line per stage, no stage exited 2, the smoke stage's `lines` count matches its own assertion, and the four non-claude CLIs are reported as `unavailable` rather than clean.
|
||||
3. For each error — a failed purge, a failed wiring, an `unwired` status, a failed smoke, or a scanned error with `jsc=true` — run `tools/report-error.sh --hook {script name} --exit {code} --summary "{reason}" --cli {cli}` with the script's `[jsc]` output on stdin, then hand the failure to `jsc-hooks:repair`, which **MUST run as a sub agent** and must finish by opening a PR against `develop`. Aborting the remaining installs here is allowed as long as the repair starts. Exit 0 with an `ERROR_{HASH}` page name and URL on stdout means the page was written; exit 0 with empty output means `JSC_WIKI_REPO_ERROR` and `JSC_WIKI_REPO` are both unset, so carry that reason into step 4 instead; exit 2 means the call itself was malformed — `--hook` or `--summary` is missing — so fix the arguments and rerun the same call; exit 4 means the wiki record did not land, so report the failure text and still start the repair — a page that could not be written is no reason to leave a broken hook wired. Exit 4 covers two cases, and the report has to say which: a failed write, or the script refusing to write the error directory page because it could not read the old one back. That directory is appended to, never overwritten: every row on it is somebody else's error report, so the script reads the page, adds this run's row, and writes the whole page. Only a genuine 404 (`wiki-get` exit 4) means the page is not there yet and lets it build one from the template. An invalid key (exit 7) or any other API failure (exit 8) leaves the old rows unknown, so it skips the directory write and names the code instead — writing a fresh template over a directory it never read would erase every earlier report, with no merge and no backup behind it. A scanned error with `jsc=false` belongs to a third-party hook: report it and leave it alone. Skip this step when every CLI passed all five stages. Done when every error carries one `ERROR_{HASH}` result — a page name and URL, or the recorded reason no page was written — and one repair PR URL against `develop`.
|
||||
4. Report five results per CLI — purge, wiring, status, smoke, scan — each with the reason its script printed, plus the smoke `lines` count, any `ERROR_{HASH}` page name and every repair PR URL. Done when every detected CLI appears with one verdict per stage and every repair has a PR against `develop`.
|
||||
|
||||
## Notes
|
||||
|
||||
- Every hook script accepts both stdin JSON and environment variables (`JSC_CLI`, `JSC_SESSION_ID`, `JSC_SKILL`, `JSC_TOOL_NAME`, `JSC_MODEL`); `jsc-wrap.sh` sets the first two itself.
|
||||
- Every hook script accepts both stdin JSON and environment variables (`JSC_CLI`, `JSC_SESSION_ID`, `JSC_SKILL`, `JSC_TOOL_NAME`, `JSC_TOOL_COMMAND`, `JSC_MODEL`); `jsc-wrap.sh` sets the first two itself.
|
||||
- `session-timer.sh` takes `start` (keep an existing start time), `restart` (always overwrite it, for a CLI with no session id — kiro), `mark` and `report`. `wire-cli.sh` picks the right one per CLI; do not hand-edit the generated hook files. `start` and `restart` also clear the restart gate whenever they decide this SessionStart is a new session, so the wiring of those two events is what lowers the gate after a restart — a CLI wired without them keeps the gate up until the user sets `JSC_RESTART_GATE=off`.
|
||||
- `restart-gate.sh` blocks jsc skill calls while `$JSC_HOME/restart-required.d/{cli}` exists — one file per CLI, named after the CLI code — so a freshly deployed skill set is not used by a process still running the old one. Each CLI reads only its own file: another CLI's file never blocks this one, and a restart clears only the file of the CLI that restarted. `jsc-cli:deploy` writes the current CLI's file through `restart-gate.sh require {install|update} [{domain}...]` at the end of an install or update; `restart-gate.sh report` prints one line per file, so it is visible which CLIs still owe a restart. A leftover old-format single file at `$JSC_HOME/restart-required` blocks every CLI and is deleted on the next `clear` — transitional only, and `hooks/restart-gate.sh` records when it can be dropped. Exempt skills stay callable — `jsc-cli:deploy`, `jsc-hooks:hooks-install`, `jsc-gitea:wiki`, `jsc-log:worklog`, `jsc-log:learn`, `jsc-meta:*`, `jsc-ask:ask`, `jsc-git:pr`, `jsc-git:commit` — because the change report and the worklog still have to be finished after a deploy, and the first six reach that finish line only through the last three: the deploy asks for its mode, the report closes with a PR. The gate matches skill names, not call chains, so a nested call to anything off the list is blocked all the same. `hooks/restart-gate.sh` owns the list; guidelines.md「部署後重啟閘門」carries the same nine with a reason per entry. Escape hatch: `JSC_RESTART_GATE=off`.
|
||||
- `restart-gate.sh` blocks jsc skill calls while `$JSC_HOME/restart-required.d/{cli}` exists — one file per CLI, named after the CLI code — so a freshly deployed skill set is not used by a process still running the old one. Each CLI reads only its own file: another CLI's file never blocks this one, and a restart clears only the file of the CLI that restarted. `jsc-cli:deploy` writes the current CLI's file through `restart-gate.sh require {install|update} [{domain}...]` at the end of an install or update; `restart-gate.sh report` prints one line per file, so it is visible which CLIs still owe a restart. A leftover old-format single file at `$JSC_HOME/restart-required` blocks every CLI and is deleted on the next `clear` — transitional only, and `hooks/restart-gate.sh` records when it can be dropped. The gate matches skill names, not call chains, so a nested call to anything off the exemption list is blocked all the same; `hooks/restart-gate.sh` owns that list with a reason per entry, and `jsc-meta/references/guidelines.md`「部署後重啟閘門」carries the same list. Escape hatch: `JSC_RESTART_GATE=off`.
|
||||
- `write-guard.sh` takes three blocking modes, wired on two PreToolUse matchers, so claude is the only CLI where any of it takes effect, plus a fourth mode, `release`, that is wired nowhere and is called by a skill itself. `stage` reads the stage lock that `sdlc-gate.sh` already owns and blocks `Write`, `Edit` and `MultiEdit` while `plan` or `analyze` holds it, because those two stages produce wiki pages rather than files. `review` reads the current skill — the environment variable first, then the record `skill-usage.sh` keeps — and blocks writes while `jsc-review:code-review` or `jsc-review:api-doc` runs, since both only report findings. It deliberately does **not** block `jsc-review:comment-cleanup`: that skill has to write, limited to comment lines, and deciding that limit needs per-language comment parsing of the whole proposed content, which would block legitimate cleanups more often than it caught bad ones — that boundary stays with the skill text and the later review. `commit` is wired on `Bash` and blocks a single command that stages everything and commits in one go, plus any commit message carrying simplified characters or mojibake, which it decides by calling `lang-guard.sh` rather than keeping a second word list. A `git add -A` split across two separate tool calls is not caught, on purpose: catching it needs cross-call state that the blocked operator has no way to clear. `release` deletes that recorded skill and always exits 0; `jsc-review:code-review` and `jsc-review:api-doc` call it once each as they hand their findings back. It exists because the record says which skill was loaded last, not which one is still running: both audit skills end by leaving the fixing to their caller, and without `release` every write that caller makes stays blocked for the whole TTL, with the escape hatch or a wait as the only way out — a gate must never lock away its own release. Escape hatch: `JSC_WRITE_GUARD=off`, which `release` ignores because clearing a record blocks nobody, plus `JSC_WRITE_GUARD_TTL` for how long a recorded skill counts as still running.
|
||||
- `purge` reaches the user-level config only. Hooks that another plugin ships in its own `hooks.json` stay active, and uninstalling that plugin is the only way to clear them — say so when reporting, and treat their errors as third-party.
|
||||
- Backups land in `$JSC_HOME/backup/hooks/{cli}/{yyyyMMdd_HHmmss}/`, one directory per purge run, under the original file names. Hand that path to the user whenever a purge removed something.
|
||||
- `status claude` reads Claude Code's `installed_plugins.json` and checks the `installPath` that the CLI actually loads. It must not check only the `hooks.json` next to the `wire-cli.sh` that happens to be running, because a development checkout can otherwise hide a broken installed plugin.
|
||||
- `smoke` treats `sdlc-gate.sh check` exit 2 as healthy: that exit is the stage lock blocking a turn on purpose, not a runtime error. `comment-scope.sh` and `lang-guard.sh` exit 2 count as healthy for the same reason — the scan found something and warned about it. Their no-argument mode has no file name during smoke and exits 0 in silence; `sweep` depends on the worktree it runs in, so it answers 2 whenever that worktree happens to carry an offending comment, a simplified character or a mojibake sequence. None of these is a broken hook.
|
||||
- Never set `JSC_READONLY=1` for this skill. `wire-cli.sh` refuses `purge` and wiring with exit 6 under that variable, which is exactly what a health check wants and exactly what an install must not have.
|
||||
- `smoke` treats `sdlc-gate.sh check` exit 2 as healthy: that exit is the stage lock blocking a turn on purpose, not a runtime error. `comment-scope.sh`, `lang-guard.sh` and `write-guard.sh` exit 2 count as healthy for the same reason — the check found something and said so. Their no-argument mode has no file name during smoke and exits 0 in silence; `sweep` depends on the worktree it runs in, so it answers 2 whenever that worktree happens to carry an offending comment, a simplified character or a mojibake sequence, and `write-guard.sh` answers 2 whenever the machine happens to hold a `plan` stage lock or a recent audit skill. None of these is a broken hook.
|
||||
- `comment-scope.sh` takes three modes: `prompt` (inject the rule summary at UserPromptSubmit), no argument at all (scan the file just written at PostToolUse, reading `file_path` from stdin JSON or `JSC_CHANGED_FILE`), and `sweep [dir]` (scan every file the git worktree changed, for the four CLIs with no post-tool hook). All scanning modes read only the lines a diff added, skip markdown and binary files, and turn off entirely with `JSC_COMMENT_SCOPE=off`. The rule text itself lives in one place only, `jsc-review`'s `references/comment-scope.md`; never restate the list anywhere in this repo.
|
||||
- `lang-guard.sh` takes the same three modes as `comment-scope.sh` and is wired at the same places, but it scans differently on purpose: it reads the whole file rather than comment lines only, and it does scan `.md` and plain-text files, because those are exactly the non-code output the rule targets. It flags three things — simplified characters (word list in `hooks/simplified.txt`, the single source of truth for this repo; a missing list skips that check in silence), mojibake (U+FFFD and double-encoding remnants), and non-UTF-8 encoding (decided by `iconv`; no `iconv` skips that check). It skips binaries, generated files, and the three files whose subject is those very characters (`simplified.txt`, `ste100-guard.sh`, `lang-guard.sh`). Turn it off with `JSC_LANG_GUARD=off`. The rule text lives only in `jsc-meta`'s `references/ste100.md`.
|
||||
- `jsc-wrap.sh` runs both sweeps after the CLI exits and always returns the CLI's own exit code. A `sweep` hit warns on stderr and changes nothing else — never let a language or comment warning turn a successful CLI run into a failed one.
|
||||
|
||||
@@ -5,12 +5,14 @@ description: Repair failed hook wiring by delegating diagnosis to installed AI a
|
||||
|
||||
# repair — repair a failed hook
|
||||
|
||||
Single source of guidelines: [`../../references/guidelines.md`](../../references/guidelines.md).
|
||||
Single source of guidelines: `jsc-meta`'s `references/guidelines.md`.
|
||||
|
||||
This skill is exempt from the version guard and the post-deploy restart gate, because it is the only path back from a broken hook. `hooks/version-guard.sh` and `hooks/restart-gate.sh` own those two exemption lists.
|
||||
|
||||
## Flow
|
||||
|
||||
1. Read the failure context from `ERROR_{HASH}` through `jsc-gitea:wiki` or from the failed `status=` line, then confirm the target repo is `hooks` and the PR base branch is `develop`. Completion condition: the failure context and target branch are explicit.
|
||||
2. Detect installed AI CLIs with `../cli/tools/detect-clis.sh`, then delegate diagnosis to one subagent per available CLI. Each subagent must receive the failure context and the `/jsc-shared:spec-output` rules, must stay read-only, and must return one structured repair proposal: root cause, changed files, and verification command. Completion condition: every available CLI has one returned proposal, or there are no CLIs and the main agent has noted that it must diagnose alone.
|
||||
3. Pick the smallest repair that makes the wiring pass, then apply it in the `hooks` repo. If the fix touches wiring behavior, update `hooks/tools/wire-cli.sh`, `hooks/skills/hooks-install/SKILL.md`, and `hooks/README.md` together. Run the relevant verification command before moving on. Completion condition: the fix is on disk and the verification command passes.
|
||||
4. Run `../meta/tools/sync-skill-manifest.sh .`. Completion condition: the README skill list and all three manifests show the same new version.
|
||||
5. Commit, push, and open a PR with `jsc-git:pr develop`. Completion condition: a PR URL comes back and the repair is ready for review.
|
||||
1. Read the failure context from `ERROR_{HASH}` through `jsc-gitea:wiki`, or from the failed `status=` line when no page was written. A wiki read that fails stops the skill: report which page could not be read and ask for the failure output instead of guessing. Done when the failure context names the script, the exit code and the CLI, and the PR base branch is fixed at `develop`.
|
||||
2. Run `jsc-cli/tools/detect-clis.sh`. It always exits 0 and prints one `name<TAB>path<TAB>version` line per installed CLI; empty output means no CLI is installed. Delegate diagnosis to one subagent per detected CLI — each **MUST run as a sub agent**, must receive the failure context, must stay read-only, and must return root cause, the files to change, and the verification command to run. Done when every detected CLI has returned one proposal, or the output was empty and the main agent has recorded that it diagnoses alone.
|
||||
3. Pick the smallest repair that makes the wiring pass, then apply it in the `hooks` repo. When the fix touches wiring behaviour, update `hooks/tools/wire-cli.sh`, `hooks/skills/hooks-install/SKILL.md` and `hooks/README.md` in the same change. Run `tools/wire-cli.sh smoke {cli}` for the affected CLI: exit 0 means the repair holds, exit 4 means it does not — go back to step 2 with the new output, exit 2 means a bad CLI name, so fix the name and rerun. Done when the fix is on disk and smoke exits 0.
|
||||
4. Run `jsc-meta/tools/sync-skill-manifest.sh .` from the repo root. Exit 0 means the README skill list and all three manifests carry the same new version. Exit 1 means a missing path, a missing `JSC-SKILLS` marker or an unreadable manifest — fix the named file and rerun. Exit 2 means a usage error, so pass exactly one path. Any other exit code is an environment fault, never a successful sync: stop and report it. Done when the script exits 0 and the three manifests show the same version.
|
||||
5. Commit, push and open a PR with `jsc-git:pr` against `develop`. When `jsc-git:pr` returns no PR URL, report the repair as applied but unmerged, name the branch that holds it, and hand back the failure reason — never claim a PR exists. Done when a PR URL comes back, or the branch name and the failure reason are both reported.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# 異常目錄 — ERROR_CONTENTS
|
||||
|
||||
> 由 `jsc-hooks` 的失敗回報流程維護。新異常附加在文末,查問題時先看最新一筆。
|
||||
>
|
||||
> 寫入語意:一列代表一次 hook 異常回報。寫入前先讀回整頁,同一筆異常已經有列就更新那一列,沒有才在文末附加一列,最後整頁寫回。禁止整頁覆蓋,也不得改動別人的列。
|
||||
|
||||
## 異常清單
|
||||
|
||||
|
||||
+33
-6
@@ -17,6 +17,15 @@
|
||||
# gitea.sh)時安靜降級:不輸出、exit 0。回報失敗不該再變成一次失敗。
|
||||
# 寫入 wiki 失敗才以 exit 4 回報,訊息走 stderr。
|
||||
#
|
||||
# 結束碼: 0=已寫入 wiki 並印出「{頁名} {網址}」,或安靜降級(找不到 gitea.sh、解析不出
|
||||
# wiki 存取庫、算不出 HASH、建不出暫存檔)——回報失敗不該再變成一次失敗
|
||||
# 2=用法錯誤(缺 --hook 或 --summary)
|
||||
# 4=寫入 wiki 失敗(異常頁與索引目錄頁,任一支寫不進去就算),或目錄頁的舊內容
|
||||
# 讀不回來(wiki-get 回 7 金鑰失效、8 其他 API 失敗)而放棄寫入;訊息走 stderr。
|
||||
# 讀不回來就不寫,是為了不拿範本蓋掉一份還在的目錄頁
|
||||
# 註: 本檔以 `. "$ROOT/hooks/lib.sh"` 載入共用函式,沒有接 `|| true`。lib.sh 讀不到時 sh 會
|
||||
# 就地結束並回 2,跟用法錯誤同碼;分不出是哪一種時,先確認 hooks/lib.sh 在不在。
|
||||
#
|
||||
# 頁名:
|
||||
# ERROR_{HASH},HASH 取「{owner}/{repo} {hook} {時間}」的 SHA-1 前 8 碼(共用 hash 規則)。
|
||||
# 時間放進 hash:同一種失敗再發生時要另開新頁,不覆寫舊紀錄。
|
||||
@@ -110,11 +119,7 @@ fill '{HASH}' "$hash" < "$ROOT/templates/error-page.md" \
|
||||
row=$(printf '| %s | [[%s|%s]] | %s | %s | %s | %s |' \
|
||||
"$ts" "$hook 異常 $ts" "$page" "$repo" "$hook" "$code" "$summary")
|
||||
|
||||
# 目錄頁:已存在就把新列附在文末(最新一筆在最後);不存在就用範本建立
|
||||
if sh "$gsh" wiki-get "$wrepo" ERROR_CONTENTS > "$tmp_list" 2>/dev/null \
|
||||
&& [ -s "$tmp_list" ]; then
|
||||
printf '%s\n' "$row" >> "$tmp_list"
|
||||
else
|
||||
build_contents() { # 用範本建一份全新的目錄頁;只有確定舊頁不存在時才可以呼叫
|
||||
fill '{yyyy-MM-dd HH:mm:ss}' "$ts" < "$ROOT/templates/error-contents.md" \
|
||||
| fill '{HASH}' "$hash" \
|
||||
| fill '{error title}' "$hook 異常 $ts" \
|
||||
@@ -122,12 +127,34 @@ else
|
||||
| fill '{hook_name}' "$hook" \
|
||||
| fill '{exit_code}' "$code" \
|
||||
| fill '{error_summary}' "$summary" > "$tmp_list"
|
||||
fi
|
||||
}
|
||||
|
||||
# 異常目錄頁一律附加,不整頁覆蓋。頁上每一列都是別人回報的異常,本腳本沒有從別處讀過
|
||||
# 那些列,所以先把舊頁讀回來、把新列附在文末(最新一筆在最後),再整頁寫回。
|
||||
# 這個語意完全靠「讀得回舊內容」撐著,因此依 wiki-get 的結束碼分流:
|
||||
# 0 → 讀到既有內容,附加新列(讀得到但整頁是空的,沒有既有列會被蓋掉,套範本才安全)
|
||||
# 4 → 頁面真的還不存在,只有這個碼可以用範本建立新頁
|
||||
# 7 → 金鑰失效或權限不足,舊內容未知,放棄目錄頁寫入
|
||||
# 8 → 其他 API 失敗,舊內容一樣未知,處置同 7
|
||||
# 為什麼 7 與 8 不能當成「頁面不存在」:拿範本蓋掉一份讀不回來的目錄頁,等於刪光所有既有
|
||||
# 列,而 wiki-put 不做合併、也不留備份,蓋掉就救不回來。
|
||||
sh "$gsh" wiki-get "$wrepo" ERROR_CONTENTS > "$tmp_list" 2>/dev/null
|
||||
get_code=$?
|
||||
contents_skip=''
|
||||
case "$get_code" in
|
||||
0) if [ -s "$tmp_list" ]; then printf '%s\n' "$row" >> "$tmp_list"; else build_contents; fi ;;
|
||||
4) build_contents ;;
|
||||
*) contents_skip=$get_code ;;
|
||||
esac
|
||||
|
||||
if ! sh "$gsh" wiki-put "$wrepo" "$page" "$tmp_page" >/dev/null 2>&1; then
|
||||
echo "[jsc] 寫入 $page 失敗($wrepo)。" >&2
|
||||
exit 4
|
||||
fi
|
||||
if [ -n "$contents_skip" ]; then
|
||||
echo "[jsc] 讀取 ERROR_CONTENTS 失敗($wrepo,wiki-get 結束碼 $contents_skip),舊內容未知,不寫目錄頁;$page 已建立。" >&2
|
||||
exit 4
|
||||
fi
|
||||
if ! sh "$gsh" wiki-put "$wrepo" ERROR_CONTENTS "$tmp_list" >/dev/null 2>&1; then
|
||||
echo "[jsc] 寫入 ERROR_CONTENTS 失敗($wrepo),$page 已建立。" >&2
|
||||
exit 4
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
#
|
||||
# 產出: 每筆錯誤附加一行 JSON 到 $JSC_HOME/errors/hooks.jsonl(格式比照 hooks/skill-usage.sh):
|
||||
# {ts,cli,hook,event,exit,detail,jsc}
|
||||
# jsc 欄位:command 或 stderr 命中 ste100-guard.sh、session-timer.sh、skill-usage.sh、
|
||||
# sdlc-gate.sh、version-guard.sh 任一支就是 true,否則 false。分得出來才用得上——
|
||||
# 非 jsc 的 hook 錯誤不是 jsc 該修的,hooks-install 只回報、不轉 jsc-hooks:repair。
|
||||
# jsc 欄位:command 或 stderr 命中九支 hook 腳本任一支,或命中 jsc-hooks 路徑,就是 true,
|
||||
# 否則 false。分得出來才用得上——非 jsc 的 hook 錯誤不是 jsc 該修的,hooks-install 只回報、
|
||||
# 不轉 jsc-hooks:repair。
|
||||
#
|
||||
# 輸出: 第一行 `status={clean|errors|unavailable} reason=...`(可供程式判讀),
|
||||
# errors 時其後每筆一行人類可讀的繁中摘要(hook 名、退出碼、是否屬 jsc)。
|
||||
@@ -52,7 +52,7 @@ case "$cli" in
|
||||
codex|copilot|antigravity|kiro)
|
||||
printf 'status=unavailable reason=%s\n' "$cli 沒有 hook 結果紀錄,執行期錯誤掃不到"
|
||||
echo "[jsc] $cli:原生紀錄只留工作階段與提示內容,沒有記下 hook 的退出碼與 stderr。"
|
||||
echo "[jsc] $cli:改跑 tools/wire-cli.sh smoke $cli,主動執行五支 hook 驗執行期。"
|
||||
echo "[jsc] $cli:改跑 tools/wire-cli.sh smoke $cli,主動執行九支 hook 驗執行期。"
|
||||
exit 0 ;;
|
||||
esac
|
||||
|
||||
@@ -92,10 +92,16 @@ extract_errors() {
|
||||
}
|
||||
return out
|
||||
}
|
||||
# 判定這筆錯誤是不是 jsc 自己的 hook。腳本名逐支列,再補一條 jsc-hooks 路徑判定:
|
||||
# 接線寫進設定的命令一律走 $JSC_HOME/current/jsc-hooks,路徑本身就是證據,新增 hook 時
|
||||
# 就算忘了補進下面這張清單也還認得出來。認錯邊的代價不對稱——漏認會把 jsc 的錯誤當成
|
||||
# 第三方的,只回報不修正。
|
||||
function is_jsc(t) {
|
||||
if (index(t, "ste100-guard.sh") || index(t, "session-timer.sh") \
|
||||
|| index(t, "skill-usage.sh") || index(t, "sdlc-gate.sh") \
|
||||
|| index(t, "version-guard.sh")) return "true"
|
||||
|| index(t, "version-guard.sh") || index(t, "restart-gate.sh") \
|
||||
|| index(t, "comment-scope.sh") || index(t, "lang-guard.sh") \
|
||||
|| index(t, "write-guard.sh") || index(t, "jsc-hooks")) return "true"
|
||||
return "false"
|
||||
}
|
||||
# 摘要收斂成單行短字串:TSV 欄位不能有 tab,jsonl 欄位不能有裸換行;
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
# 用法: scan-logs.sh --cli {name} [--session {sid}]
|
||||
# 支援: copilot(~/.config/copilot/)、antigravity(全域日誌目錄)、codex(~/.codex/sessions/、~/.codex/log/)。
|
||||
# 去重: 以 $JSC_HOME/usage/scan-state/ 記住每個日誌檔已掃描的位元組數,重掃只讀新增段落。
|
||||
# 結束碼: 0=回填完成,或找不到該 CLI 的日誌目錄、尚未支援該 CLI(兩種都只印一行說明就結束,
|
||||
# 回填不到資料不算失敗)
|
||||
# 2=用法錯誤(沒給 --cli)
|
||||
# 註: 本檔以 `. "$HERE/../hooks/lib.sh"` 載入共用函式,沒有接 `|| true`。lib.sh 讀不到時
|
||||
# sh 會就地結束並回 2,跟用法錯誤同碼;分不出是哪一種時,先確認 hooks/lib.sh 在不在。
|
||||
HERE=$(cd "$(dirname "$0")" && pwd); . "$HERE/../hooks/lib.sh"
|
||||
|
||||
cli=""; sid=""
|
||||
|
||||
+195
-24
@@ -3,9 +3,12 @@
|
||||
# 用法:
|
||||
# wire-cli.sh {claude|codex|copilot|antigravity|kiro} 接線
|
||||
# wire-cli.sh purge {claude|codex|copilot|antigravity|kiro} 備份後移除該 CLI 的所有 hook
|
||||
# wire-cli.sh smoke {claude|codex|copilot|antigravity|kiro} 跑一輪八支 hook,驗執行期
|
||||
# wire-cli.sh smoke {claude|codex|copilot|antigravity|kiro} 跑一輪九支 hook,驗執行期
|
||||
# wire-cli.sh status {claude|codex|copilot|antigravity|kiro} 唯讀盤點接線現況,不寫檔也不執行 hook
|
||||
#
|
||||
# JSC_READONLY=1 時只准 status 與 smoke,purge 與接線一律拒絕並回 exit 6。體檢類技能全程帶著
|
||||
# 這個變數跑,「子命令打錯一個字就重新接線或刪檔」的風險就由程式擋掉,不靠呼叫端自我約束。
|
||||
#
|
||||
# purge 移除的是「所有 hook」,含非 jsc 的第三方項目。安裝一律先 purge 再接線:混著別人的
|
||||
# hook 接線,出錯時分不清是誰的 hook 壞掉,也修不了。移除前每個要動的檔案先原樣複製到
|
||||
# $JSC_HOME/backup/hooks/{cli}/{yyyyMMdd_HHmmss}/,備份失敗就不移除。
|
||||
@@ -17,9 +20,15 @@
|
||||
# restart-gate.sh 的部署後重啟閘門同法:當前 CLI 沒有狀態檔、只有別支 CLI 有狀態檔、當前
|
||||
# CLI 那份存在、豁免技能、逃生門、取不到技能名各跑一次,再驗狀態檔本身在不在——清除只刪自己
|
||||
# 那一份、別支那一份留著、舊格式的單一狀態檔照樣擋得下來且清得掉。
|
||||
# write-guard.sh 的四種模式同法:階段鎖、稽核技能、提交指令與 release 解除各自的判定路徑,
|
||||
# 都用暫時的 $JSC_HOME 跑過一次並比對結束碼。
|
||||
#
|
||||
# smoke 自己數結果行並自我斷言:`lines<TAB>{數量}` 那一行印的是其後 `[jsc]` 結果行的實際條數,
|
||||
# 與腳本內宣告的預期條數逐類比對,不符就回非零。數字寫在腳本裡、由腳本自己印,散文引用那一行
|
||||
# 就好,不必在 SKILL.md 或 README 各抄一份——抄了就會在加減判定路徑時漂移。
|
||||
#
|
||||
# 接線行為(依 CLI 而定,皆為冪等:重跑只取代既有的 jsc-hooks 標記段落,不會重複疊加):
|
||||
# claude — 什麼都不用寫,hooks.json 已自動接線八支 hook
|
||||
# claude — 什麼都不用寫,hooks.json 已自動接線九支 hook
|
||||
# codex — 在 shell rc 檔加上 codex 別名,轉呼叫 tools/jsc-wrap.sh codex(開始計時,
|
||||
# 結束時收尾掃一次註解範圍與繁中編碼);在 config.toml 設 notify(每輪補
|
||||
# session-timer.sh start 再 mark,最後 comment-scope.sh sweep 與
|
||||
@@ -36,10 +45,11 @@
|
||||
# 皆帶 JSC_CLI=kiro
|
||||
#
|
||||
# 覆蓋範圍要據實回報,不得暗示每個 CLI 都有保護:
|
||||
# claude 八支 hook 全接,回報 wired
|
||||
# claude 九支 hook 全接,回報 wired
|
||||
# codex、copilot、antigravity、kiro 只有別名、notify 或規則檔,接不上 PreToolUse、PostToolUse
|
||||
# 與 UserPromptSubmit,版本前置檢查、部署後重啟閘門與
|
||||
# SDLC 模型鎖都沒接上,一律回報 degraded 並在 reason 講明。
|
||||
# 與 UserPromptSubmit,版本前置檢查、部署後重啟閘門、寫入與
|
||||
# 提交閘門與 SDLC 模型鎖都沒接上,一律回報 degraded 並在
|
||||
# reason 講明。
|
||||
# 重啟閘門在這四個 CLI 上一次技能呼叫都擋不下來:狀態檔照樣
|
||||
# 寫、下次工作階段開始照樣清,只是中間沒有任何判定點,重啟
|
||||
# 只能靠 /jsc-cli:deploy 收尾的提示自己動手
|
||||
@@ -65,8 +75,9 @@
|
||||
# 2=用法錯誤 3=skipped(該 CLI 未偵測到執行檔,略過)
|
||||
# 4=failed(寫入或驗證沒過,接線沒生效;由 hooks-install 呼叫 report-error.sh 回報)
|
||||
# 結束碼(purge): 0=purged 2=用法錯誤 3=skipped 4=failed
|
||||
# 結束碼(smoke): 0=ok 2=用法錯誤 4=failed
|
||||
# 結束碼(smoke): 0=ok 2=用法錯誤 4=failed(含結果行數與預期不符)
|
||||
# 結束碼(status): 0=wired 1=degraded 2=用法錯誤 3=skipped 5=unwired(該接的段落缺了至少一項)
|
||||
# 結束碼(唯讀模式): 6=readonly(JSC_READONLY=1 之下拒絕 purge 與接線),status 與 smoke 不受影響
|
||||
# status 之外的動作都會寫檔,體檢類技能(/jsc-cli:doctor)只能呼叫 status。判讀邏輯跟接線
|
||||
# 共用同一組檔案位置與標記字串,分兩份實作就會各自漂移,體檢說沒接、實際上接著。
|
||||
#
|
||||
@@ -102,6 +113,20 @@ case "$cli" in
|
||||
*) usage ;;
|
||||
esac
|
||||
|
||||
# 唯讀契約在程式層把關,不靠呼叫端記得只打 status。子命令解析完就判:預設動作是接線,
|
||||
# 所以少打一個子命令就會直接改環境,這個判定要擋的正是那一次手滑。
|
||||
if [ "${JSC_READONLY:-}" = "1" ]; then
|
||||
case "$action" in
|
||||
status|smoke) ;;
|
||||
*)
|
||||
printf 'status=readonly reason=%s\n' "JSC_READONLY=1 之下只准 status 與 smoke,$action 會動到接線,已拒絕"
|
||||
echo "[jsc] 目前是唯讀模式(JSC_READONLY=1),purge 與接線一律拒絕,環境沒有被動過。" >&2
|
||||
echo "[jsc] 要盤點接線現況請用 wire-cli.sh status $cli;要驗執行期請用 wire-cli.sh smoke $cli。" >&2
|
||||
echo "[jsc] 確定要改接線,請在沒有 JSC_READONLY 的環境重跑,或改走 /jsc-hooks:hooks-install。" >&2
|
||||
exit 6 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# STE100 規則段落的唯一來源:ste100-guard.sh 的實際輸出
|
||||
ste100_text() { sh "$HOOKS/ste100-guard.sh" 2>/dev/null | sed '/^exit /d'; }
|
||||
|
||||
@@ -716,6 +741,23 @@ if [ "$action" = smoke ]; then
|
||||
smoke_out=$(mktemp) || { printf 'status=failed reason=%s\n' "無法建立暫存檔"; exit 4; }
|
||||
smoke_fails=0
|
||||
|
||||
# 每一類實際跑過的結果行數。收尾時與下面宣告的預期條數逐類比對,加減判定路徑卻忘了改預期
|
||||
# 就會當場失敗,散文與程式之間不會再各記一份數字。
|
||||
smoke_n_hook=0; smoke_n_model=0; smoke_n_wp=0; smoke_n_rs=0; smoke_n_wg=0
|
||||
# 預期條數(改動判定路徑時一起改):每一類都要有自己的計數器,印得出結果行卻沒人計數的
|
||||
# 那一類會讓總數永遠對不上,斷言也就形同虛設。
|
||||
# hook 模式 九支 hook 的每個接線模式各一條。sdlc-gate.sh、comment-scope.sh、
|
||||
# lang-guard.sh 與 write-guard.sh 各有多個模式,所以比 hook 支數多
|
||||
# 模型來源 sdlc-gate.sh lock 取模型代號的四條來源判定路徑
|
||||
# 工作包 sdlc-gate.sh wp-check skill 的歸屬判定路徑
|
||||
# 重啟閘門 restart-gate.sh 的判定、清除路徑與狀態檔範圍檢查
|
||||
# 寫入閘門 write-guard.sh 三種擋人模式與 release 解除模式的判定路徑
|
||||
SMOKE_EXPECT_HOOK=17
|
||||
SMOKE_EXPECT_MODEL=4
|
||||
SMOKE_EXPECT_WP=6
|
||||
SMOKE_EXPECT_RS=16
|
||||
SMOKE_EXPECT_WG=21
|
||||
|
||||
# 跑一支 hook 並判定結果。$1=腳本檔名 $2=子命令(可省略)
|
||||
# $2 不加引號展開:子命令是固定字面字,空字串時要展成「沒有參數」而不是空參數。
|
||||
smoke_one() {
|
||||
@@ -726,6 +768,7 @@ if [ "$action" = smoke ]; then
|
||||
# 去掃那個檔,掃到違規就 exit 2,冒煙測試變成看環境臉色,測不出腳本本身跑不跑得完。
|
||||
_out=$(printf '{}' | JSC_CLI="$cli" JSC_SKILL="" SKILL="" JSC_CHANGED_FILE="" \
|
||||
sh "$HOOKS/$_h" $_s 2>&1); _rc=$?
|
||||
smoke_n_hook=$((smoke_n_hook + 1))
|
||||
if [ "$_rc" -eq 0 ]; then
|
||||
printf '[jsc] %s%s:exit 0,正常。\n' "$_h" "${_s:+ $_s}" >> "$smoke_out"
|
||||
elif [ "$_h" = sdlc-gate.sh ] && [ "$_s" = check ] && [ "$_rc" -eq 2 ]; then
|
||||
@@ -742,6 +785,11 @@ if [ "$action" = smoke ]; then
|
||||
# 沿用同一條例外:lang-guard.sh 掃描模式的 exit 2 是「掃到簡體字、亂碼或編碼問題」的
|
||||
# 設計行為。sweep 一樣看工作區乾不乾淨,髒工作區本來就會回 2,不是 hook 壞掉。
|
||||
printf '[jsc] %s%s:exit 2,掃到簡體字或亂碼並發出警告,屬設計行為,不算錯誤。\n' "$_h" "${_s:+ $_s}" >> "$smoke_out"
|
||||
elif [ "$_h" = write-guard.sh ] && [ "$_rc" -eq 2 ]; then
|
||||
# 同一條例外:write-guard.sh 的 exit 2 是「擋下這次寫入或提交」的設計行為。這一輪用的是
|
||||
# 真正的 $JSC_HOME,機器上剛好鎖在 plan 階段、或最近一次呼叫的是稽核技能時本來就會回 2,
|
||||
# 不是 hook 壞掉。三種模式的判定結果由下面自備狀態檔的那一段逐條驗。
|
||||
printf '[jsc] %s%s:exit 2,擋下這次寫入或提交,屬設計行為,不算錯誤。\n' "$_h" "${_s:+ $_s}" >> "$smoke_out"
|
||||
else
|
||||
smoke_fails=$((smoke_fails + 1))
|
||||
printf '[jsc] %s%s:exit %s,執行期出錯:%s\n' "$_h" "${_s:+ $_s}" "$_rc" \
|
||||
@@ -762,6 +810,8 @@ if [ "$action" = smoke ]; then
|
||||
|
||||
smoke_model_case() { # $1=情境 $2=來源模式 $3=預期來源片段
|
||||
_name="$1"; _mode="$2"; _want="$3"
|
||||
# 先計數再開工:底下建不出暫存目錄那條路徑也會印一條結果行,計數放在後面就會漏掉它。
|
||||
smoke_n_model=$((smoke_n_model + 1))
|
||||
_home=$(mktemp -d 2>/dev/null) || {
|
||||
smoke_fails=$((smoke_fails + 1))
|
||||
printf '[jsc] sdlc-gate.sh model:建不出暫存目錄,模型來源判定沒驗到。\n' >> "$smoke_out"
|
||||
@@ -808,6 +858,7 @@ if [ "$action" = smoke ]; then
|
||||
smoke_wp_case() { # $1=情境 $2=技能名 $3=預期結束碼 $4=JSC_WP_GATE 值(可省略)
|
||||
_out=$(JSC_HOME="$wp_home" JSC_CLI="$cli" JSC_SKILL="$2" SKILL="$2" \
|
||||
JSC_WP_GATE="${4:-}" sh "$HOOKS/sdlc-gate.sh" wp-check skill </dev/null 2>&1); _rc=$?
|
||||
smoke_n_wp=$((smoke_n_wp + 1))
|
||||
if [ "$_rc" -eq "$3" ]; then
|
||||
printf '[jsc] sdlc-gate.sh wp-check skill(%s):exit %s,與預期相同。\n' "$1" "$_rc" >> "$smoke_out"
|
||||
else
|
||||
@@ -822,7 +873,9 @@ if [ "$action" = smoke ]; then
|
||||
printf 'repo=jsc/smoke\nindex=12\nwp=WP-03\n' > "$wp_home/wp/jsc-smoke-12.pr"
|
||||
smoke_wp_case "PR 屬於領取中的工作包,技能 implement" implement 0
|
||||
printf 'repo=jsc/smoke\nindex=9\nwp=WP-01\n' > "$wp_home/wp/jsc-smoke-9.pr"
|
||||
smoke_wp_case "PR 屬於別的工作包,技能 plan" plan 2
|
||||
smoke_wp_case "PR 屬於別的工作包,技能 analyze" analyze 2
|
||||
# plan 只提醒不擋,理由見 sdlc-gate.sh 檔頭「plan 已從擋人名單移出」。
|
||||
smoke_wp_case "PR 屬於別的工作包,技能 plan" plan 0
|
||||
smoke_wp_case "PR 屬於別的工作包,技能 implement" implement 0
|
||||
smoke_wp_case "逃生門 JSC_WP_GATE=off" plan 0 off
|
||||
rm -rf "$wp_home"
|
||||
@@ -840,6 +893,7 @@ if [ "$action" = smoke ]; then
|
||||
smoke_rs_case() { # $1=情境 $2=技能名 $3=預期結束碼 $4=JSC_RESTART_GATE 值(可省略)
|
||||
_out=$(JSC_HOME="$rs_home" JSC_CLI="$cli" JSC_SKILL="$2" SKILL="$2" \
|
||||
JSC_RESTART_GATE="${4:-}" sh "$HOOKS/restart-gate.sh" </dev/null 2>&1); _rc=$?
|
||||
smoke_n_rs=$((smoke_n_rs + 1))
|
||||
if [ "$_rc" -eq "$3" ]; then
|
||||
printf '[jsc] restart-gate.sh(%s):exit %s,與預期相同。\n' "$1" "$_rc" >> "$smoke_out"
|
||||
else
|
||||
@@ -850,6 +904,7 @@ if [ "$action" = smoke ]; then
|
||||
}
|
||||
# 狀態檔在不在也要比:一支 CLI 一份的重點就在「該留的留、該刪的刪」,只看結束碼看不出來。
|
||||
smoke_rs_file() { # $1=情境 $2=狀態檔 $3=exist 或 absent
|
||||
smoke_n_rs=$((smoke_n_rs + 1))
|
||||
if { [ "$3" = exist ] && [ -f "$2" ]; } || { [ "$3" = absent ] && [ ! -f "$2" ]; }; then
|
||||
printf '[jsc] restart-gate.sh(%s):狀態檔 %s,與預期相同。\n' "$1" "$3" >> "$smoke_out"
|
||||
else
|
||||
@@ -908,12 +963,114 @@ if [ "$action" = smoke ]; then
|
||||
smoke_one lang-guard.sh prompt
|
||||
smoke_one lang-guard.sh
|
||||
smoke_one lang-guard.sh sweep
|
||||
# write-guard.sh 三種模式接在兩個 matcher 上,三個都要驗。這一輪用真正的 $JSC_HOME,只確認
|
||||
# 腳本跑得完;擋下時的 exit 2 由上面的白名單放行,判定結果本身在下一段用暫時狀態檔逐條驗。
|
||||
smoke_one write-guard.sh stage
|
||||
smoke_one write-guard.sh review
|
||||
smoke_one write-guard.sh commit
|
||||
|
||||
# 寫入與提交閘門(write-guard.sh):上面三支只證明跑得完,三種模式的判定路徑一條都沒走到。
|
||||
# 這裡自備一份暫時的 $JSC_HOME 與暫時的指令字串,把每條路徑各跑一次並比對結束碼。用暫時目錄
|
||||
# 是為了不動到使用者真正的階段鎖與技能紀錄——冒煙測試不該把別人的階段鎖讀成擋人的理由。
|
||||
# 一律 </dev/null:三種模式都讀標準輸入,管線沒人關閉時整支卡死。
|
||||
smoke_wg_case() { # $1=情境 $2=模式 $3=預期結束碼 $4=技能名 $5=指令 $6=額外環境設定(KEY=值)
|
||||
# 第六個參數省略時仍要餵一個合法的 KEY=值 給 env,否則它會把空字串當成要執行的指令。
|
||||
# 這個名字沒有任何 hook 讀它,只是佔位。
|
||||
_extra="${6:-JSC_WRITE_GUARD_UNUSED=1}"
|
||||
_out=$(env JSC_HOME="$wg_home" JSC_CLI="$cli" JSC_SESSION_ID=smoke-write \
|
||||
JSC_SKILL="${4:-}" SKILL="${4:-}" JSC_TOOL_COMMAND="${5:-}" "$_extra" \
|
||||
sh "$HOOKS/write-guard.sh" "$2" </dev/null 2>&1); _rc=$?
|
||||
smoke_n_wg=$((smoke_n_wg + 1))
|
||||
if [ "$_rc" -eq "$3" ]; then
|
||||
printf '[jsc] write-guard.sh %s(%s):exit %s,與預期相同。\n' "$2" "$1" "$_rc" >> "$smoke_out"
|
||||
else
|
||||
smoke_fails=$((smoke_fails + 1))
|
||||
printf '[jsc] write-guard.sh %s(%s):exit %s,預期 %s,寫入閘門判定壞了:%s\n' \
|
||||
"$2" "$1" "$_rc" "$3" "$(printf '%s' "$_out" | tr '\n' ' ' | cut -c1-200)" >> "$smoke_out"
|
||||
fi
|
||||
}
|
||||
if wg_home=$(mktemp -d 2>/dev/null) && mkdir -p "$wg_home/sessions" 2>/dev/null; then
|
||||
wg_state="$wg_home/sessions/smoke-write.stage"
|
||||
wg_last="$wg_home/sessions/smoke-write.lastskill"
|
||||
# stage:階段鎖狀態檔的格式沿用 sdlc-gate.sh 那一份,這裡照樣寫三欄。
|
||||
smoke_wg_case "沒有階段鎖" stage 0
|
||||
printf 'plan\treasoning-max\tsmoke\n' > "$wg_state"
|
||||
smoke_wg_case "階段鎖 plan" stage 2
|
||||
printf 'analyze\treasoning-max\tsmoke\n' > "$wg_state"
|
||||
smoke_wg_case "階段鎖 analyze" stage 2
|
||||
printf 'implement\tcoding\tsmoke\n' > "$wg_state"
|
||||
smoke_wg_case "階段鎖 implement" stage 0
|
||||
printf 'plan\treasoning-max\tsmoke\n' > "$wg_state"
|
||||
smoke_wg_case "逃生門 JSC_WRITE_GUARD=off" stage 0 "" "" JSC_WRITE_GUARD=off
|
||||
rm -f "$wg_state"
|
||||
# review:技能名先看環境變數,取不到才讀 skill-usage.sh 記下的那一份,所以兩條來源都要驗。
|
||||
smoke_wg_case "沒有技能紀錄" review 0
|
||||
smoke_wg_case "jsc-review:code-review 執行中" review 2 jsc-review:code-review
|
||||
smoke_wg_case "jsc-review:api-doc 執行中" review 2 jsc-review:api-doc
|
||||
smoke_wg_case "jsc-review:comment-cleanup 本來就要寫檔" review 0 jsc-review:comment-cleanup
|
||||
smoke_wg_case "其他技能 jsc-sdlc:implement" review 0 jsc-sdlc:implement
|
||||
printf 'jsc-review:code-review' > "$wg_last"
|
||||
smoke_wg_case "技能紀錄讀自 skill-usage.sh 那一份" review 2
|
||||
smoke_wg_case "技能紀錄已過期" review 0 "" "" JSC_WRITE_GUARD_TTL=0
|
||||
# release:稽核技能收尾時自己按的解除鍵。驗它自己跑得完、清完之後 review 真的不再擋、
|
||||
# 以及紀錄本來就不在時照樣算成功——收尾呼叫可能被重跑,第二次失敗只會讓呼叫端誤判。
|
||||
smoke_wg_case "release 清掉技能紀錄" release 0
|
||||
smoke_wg_case "release 之後 review 不再擋" review 0
|
||||
smoke_wg_case "release 冪等,紀錄不存在也算成功" release 0
|
||||
rm -f "$wg_last"
|
||||
# commit:指令改由 JSC_TOOL_COMMAND 餵,不必為了測試去拼一份 stdin JSON。
|
||||
smoke_wg_case "不是 git 指令" commit 0 "" "ls -al"
|
||||
smoke_wg_case "git add -A 後接單次提交" commit 2 "" "git add -A && git commit -m 修正"
|
||||
smoke_wg_case "只有 git add -A,沒有提交" commit 0 "" "git add -A"
|
||||
# 測試用的簡體字以八進位位元組組出來,不在原始碼裡留簡體字面:留了的話 lang-guard.sh
|
||||
# 每次掃到這一行都會命中自己的測試資料,訊號會被自己的噪音蓋掉。
|
||||
wg_bad=$(printf '\345\244\215\351\227\256')
|
||||
smoke_wg_case "提交訊息含簡體字" commit 2 "" "git commit -m \"$wg_bad\""
|
||||
smoke_wg_case "提交訊息為繁體中文" commit 0 "" "git commit -m \"修正問題\""
|
||||
smoke_wg_case "逃生門 JSC_WRITE_GUARD=off" commit 0 "" "git add -A && git commit -m x" JSC_WRITE_GUARD=off
|
||||
rm -rf "$wg_home"
|
||||
else
|
||||
smoke_fails=$((smoke_fails + 1))
|
||||
printf '[jsc] write-guard.sh:建不出暫存目錄,寫入與提交閘門判定沒驗到。\n' >> "$smoke_out"
|
||||
fi
|
||||
|
||||
# 自我斷言:實際跑過的條數對上宣告的預期條數,再對上真正印出來的行數。三邊一致才算數,
|
||||
# 少跑一條或多印一行都會在這裡現形,散文就不必再自己記一份數字。
|
||||
smoke_lines=$(wc -l < "$smoke_out" 2>/dev/null | tr -d ' ')
|
||||
[ -n "$smoke_lines" ] || smoke_lines=0
|
||||
smoke_total=$((SMOKE_EXPECT_HOOK + SMOKE_EXPECT_MODEL + SMOKE_EXPECT_WP \
|
||||
+ SMOKE_EXPECT_RS + SMOKE_EXPECT_WG))
|
||||
smoke_mismatch=""
|
||||
[ "$smoke_n_hook" = "$SMOKE_EXPECT_HOOK" ] \
|
||||
|| smoke_mismatch="${smoke_mismatch}hook 模式 $smoke_n_hook 條(預期 $SMOKE_EXPECT_HOOK);"
|
||||
[ "$smoke_n_model" = "$SMOKE_EXPECT_MODEL" ] \
|
||||
|| smoke_mismatch="${smoke_mismatch}模型來源 $smoke_n_model 條(預期 $SMOKE_EXPECT_MODEL);"
|
||||
[ "$smoke_n_wp" = "$SMOKE_EXPECT_WP" ] \
|
||||
|| smoke_mismatch="${smoke_mismatch}工作包 $smoke_n_wp 條(預期 $SMOKE_EXPECT_WP);"
|
||||
[ "$smoke_n_rs" = "$SMOKE_EXPECT_RS" ] \
|
||||
|| smoke_mismatch="${smoke_mismatch}重啟閘門 $smoke_n_rs 條(預期 $SMOKE_EXPECT_RS);"
|
||||
[ "$smoke_n_wg" = "$SMOKE_EXPECT_WG" ] \
|
||||
|| smoke_mismatch="${smoke_mismatch}寫入閘門 $smoke_n_wg 條(預期 $SMOKE_EXPECT_WG);"
|
||||
[ "$smoke_lines" = "$smoke_total" ] \
|
||||
|| smoke_mismatch="${smoke_mismatch}結果行數 $smoke_lines 行(預期 $smoke_total);"
|
||||
|
||||
smoke_breakdown="hook 模式 $SMOKE_EXPECT_HOOK 條、模型來源 $SMOKE_EXPECT_MODEL 條、工作包 $SMOKE_EXPECT_WP 條、重啟閘門 $SMOKE_EXPECT_RS 條、寫入閘門 $SMOKE_EXPECT_WG 條"
|
||||
if [ -n "$smoke_mismatch" ]; then
|
||||
printf 'status=failed reason=%s\n' "冒煙結果行數與預期不符:${smoke_mismatch}判定路徑有增減時要一併改腳本裡的預期條數"
|
||||
printf 'lines\t%s\n' "$smoke_lines"
|
||||
cat "$smoke_out"
|
||||
rm -f "$smoke_out"
|
||||
echo "[jsc] 請先以 tools/report-error.sh 回報,再交給 /jsc-hooks:repair 自動修正並開 develop PR。" >&2
|
||||
exit 4
|
||||
fi
|
||||
|
||||
if [ "$smoke_fails" -eq 0 ]; then
|
||||
printf 'status=ok reason=%s\n' "八支 hook 的每個接線模式都跑得完,工作包歸屬判定與部署後重啟閘門的每條路徑也各走過一次,沒有執行期錯誤"
|
||||
printf 'status=ok reason=%s\n' "九支 hook 的每個接線模式都跑得完,模型來源、工作包歸屬、部署後重啟閘門與寫入提交閘門的每條路徑也各走過一次($smoke_breakdown),沒有執行期錯誤"
|
||||
printf 'lines\t%s\n' "$smoke_lines"
|
||||
cat "$smoke_out"; rm -f "$smoke_out"; exit 0
|
||||
fi
|
||||
printf 'status=failed reason=%s\n' "$smoke_fails 支 hook 有執行期錯誤"
|
||||
printf 'status=failed reason=%s\n' "$smoke_fails 條判定有執行期錯誤"
|
||||
printf 'lines\t%s\n' "$smoke_lines"
|
||||
cat "$smoke_out"
|
||||
rm -f "$smoke_out"
|
||||
echo "[jsc] 請先以 tools/report-error.sh 回報,再交給 /jsc-hooks:repair 自動修正並開 develop PR。" >&2
|
||||
@@ -996,9 +1153,9 @@ if [ "$action" = status ]; then
|
||||
claude_hooks="$claude_root/hooks/hooks.json"
|
||||
if [ -n "$claude_root" ] && [ -f "$claude_hooks" ]; then st_item hooks.json "$claude_hooks" present
|
||||
else st_item hooks.json "${claude_hooks:-$HOME/.claude/plugins/installed_plugins.json}" missing; fi
|
||||
# 八支 hook 全靠這一個檔宣告,只看檔案在不在會漏掉「檔在、某支沒接進去」。
|
||||
# 後來才加進來的 comment-scope.sh、lang-guard.sh 與 restart-gate.sh 是最可能漏的三支,
|
||||
# 所以各列一項。
|
||||
# 九支 hook 全靠這一個檔宣告,只看檔案在不在會漏掉「檔在、某支沒接進去」。
|
||||
# 後來才加進來的 comment-scope.sh、lang-guard.sh、restart-gate.sh 與 write-guard.sh
|
||||
# 是最可能漏的四支,所以各列一項。
|
||||
if [ -f "$claude_hooks" ] && grep -qF 'comment-scope.sh' "$claude_hooks" 2>/dev/null
|
||||
then st_item comment-scope "$claude_hooks" present
|
||||
else st_item comment-scope "$claude_hooks" missing; fi
|
||||
@@ -1007,7 +1164,13 @@ if [ "$action" = status ]; then
|
||||
else st_item lang-guard "$claude_hooks" missing; fi
|
||||
if [ -f "$claude_hooks" ] && grep -qF 'restart-gate.sh' "$claude_hooks" 2>/dev/null
|
||||
then st_item restart-gate "$claude_hooks" present
|
||||
else st_item restart-gate "$claude_hooks" missing; fi ;;
|
||||
else st_item restart-gate "$claude_hooks" missing; fi
|
||||
# write-guard.sh 接在兩個 matcher 上,三種模式各自是一件事,只驗腳本名會漏掉少接的那一個
|
||||
for _m in stage review commit; do
|
||||
if [ -f "$claude_hooks" ] && grep -qF "write-guard.sh\\\" $_m" "$claude_hooks" 2>/dev/null
|
||||
then st_item "write-guard-$_m" "$claude_hooks" present
|
||||
else st_item "write-guard-$_m" "$claude_hooks" missing; fi
|
||||
done ;;
|
||||
|
||||
codex)
|
||||
config="${CODEX_HOME:-$HOME/.codex}/config.toml"
|
||||
@@ -1044,7 +1207,7 @@ if [ "$action" = status ]; then
|
||||
else
|
||||
st_item plugin-user-prompt-root "$_codex_plugin_hooks" missing
|
||||
fi
|
||||
st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查與部署後重啟閘門,註解範圍與繁中編碼改為每輪結束掃整個工作區,不是逐檔即時" ;;
|
||||
st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查、部署後重啟閘門與寫入提交閘門,註解範圍與繁中編碼改為每輪結束掃整個工作區,不是逐檔即時" ;;
|
||||
|
||||
copilot)
|
||||
st_rc_alias alias jsc-hooks:copilot
|
||||
@@ -1052,7 +1215,7 @@ if [ "$action" = status ]; then
|
||||
st_comment_scope comment-scope "${JSC_COPILOT_INSTRUCTIONS:-$HOME/.config/copilot/copilot-instructions.md}"
|
||||
st_lang_guard lang-guard "${JSC_COPILOT_INSTRUCTIONS:-$HOME/.config/copilot/copilot-instructions.md}"
|
||||
st_runtime_paths alias-paths "$HOME/.bashrc" "# jsc-hooks:copilot" "# /jsc-hooks:copilot"
|
||||
st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查與部署後重啟閘門,註解範圍與繁中編碼只在工作階段結束時掃一次整個工作區" ;;
|
||||
st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查、部署後重啟閘門與寫入提交閘門,註解範圍與繁中編碼只在工作階段結束時掃一次整個工作區" ;;
|
||||
|
||||
antigravity)
|
||||
st_rc_alias alias jsc-hooks:antigravity
|
||||
@@ -1060,7 +1223,7 @@ if [ "$action" = status ]; then
|
||||
st_comment_scope comment-scope "${JSC_ANTIGRAVITY_RULES:-$HOME/.antigravity/AGENTS.md}"
|
||||
st_lang_guard lang-guard "${JSC_ANTIGRAVITY_RULES:-$HOME/.antigravity/AGENTS.md}"
|
||||
st_runtime_paths alias-paths "$HOME/.bashrc" "# jsc-hooks:antigravity" "# /jsc-hooks:antigravity"
|
||||
st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查與部署後重啟閘門,註解範圍與繁中編碼只在工作階段結束時掃一次整個工作區" ;;
|
||||
st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查、部署後重啟閘門與寫入提交閘門,註解範圍與繁中編碼只在工作階段結束時掃一次整個工作區" ;;
|
||||
|
||||
kiro)
|
||||
# kiro 的 hook 檔綁在工作區,這裡看的一律是目前工作目錄底下那一份
|
||||
@@ -1089,7 +1252,7 @@ if [ "$action" = status ]; then
|
||||
else st_item lang-guard-sweep ./.kiro/hooks/jsc-hooks.json missing; fi
|
||||
st_runtime_paths session-runtime-paths ./.kiro/hooks/jsc-hooks-session-start.json
|
||||
st_runtime_paths hook-runtime-paths ./.kiro/hooks/jsc-hooks.json
|
||||
st_degrade="SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查與部署後重啟閘門,註解範圍與繁中編碼改為每輪提示送出時掃整個工作區,不是逐檔即時" ;;
|
||||
st_degrade="SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查、部署後重啟閘門與寫入提交閘門,註解範圍與繁中編碼改為每輪提示送出時掃整個工作區,不是逐檔即時" ;;
|
||||
esac
|
||||
|
||||
if [ "$st_missing" -gt 0 ]; then
|
||||
@@ -1100,7 +1263,7 @@ if [ "$action" = status ]; then
|
||||
printf 'status=degraded reason=%s\n' "$st_degrade"
|
||||
cat "$st_items"; rm -f "$st_items"; exit 1
|
||||
fi
|
||||
printf 'status=wired reason=%s\n' "hooks.json 自動接線全部八支 hook"
|
||||
printf 'status=wired reason=%s\n' "hooks.json 自動接線全部九支 hook"
|
||||
cat "$st_items"; rm -f "$st_items"; exit 0
|
||||
fi
|
||||
|
||||
@@ -1118,10 +1281,14 @@ case "$cli" in
|
||||
|| fail "$HOOKS/hooks.json 沒有接上 lang-guard.sh,繁中與編碼檢查不會生效"
|
||||
grep -qF 'restart-gate.sh' "$HOOKS/hooks.json" 2>/dev/null \
|
||||
|| fail "$HOOKS/hooks.json 沒有接上 restart-gate.sh,部署後重啟閘門不會生效"
|
||||
for m in stage review commit; do
|
||||
grep -qF "write-guard.sh\\\" $m" "$HOOKS/hooks.json" 2>/dev/null \
|
||||
|| fail "$HOOKS/hooks.json 沒有接上 write-guard.sh $m,這個模式不會生效"
|
||||
done
|
||||
printf 'status=wired reason=%s\n' "hooks.json 自動接線"
|
||||
echo "$WIRE_PATH_NOTE"
|
||||
echo "[jsc] claude:由 hooks/hooks.json 自動接線全部八支 hook,無需寫入設定。"
|
||||
echo "[jsc] claude:只有 claude 有 pre-tool hook,版本前置檢查與部署後重啟閘門只在這裡擋得下技能呼叫;其他四個 CLI 兩道閘門都接不上。"
|
||||
echo "[jsc] claude:由 hooks/hooks.json 自動接線全部九支 hook,無需寫入設定。"
|
||||
echo "[jsc] claude:只有 claude 有 pre-tool hook,版本前置檢查、部署後重啟閘門與 write-guard.sh 的三種模式只在這裡擋得下來;其他四個 CLI 這幾道閘門都接不上。"
|
||||
echo "[jsc] claude:只有 claude 有 post-tool hook,comment-scope.sh 與 lang-guard.sh 的逐檔即時掃描只在這裡接得上;其他四個 CLI 改用 sweep 掃整個工作區,時機晚一輪或晚到工作階段結束。"
|
||||
exit 0 ;;
|
||||
|
||||
@@ -1159,7 +1326,7 @@ case "$cli" in
|
||||
has_block "$agents" "<!-- jsc-hooks -->" || fail "$agents 寫入後讀不到 jsc-hooks 標記段落"
|
||||
has_comment_scope "$agents" || fail "$agents 寫入後讀不到註解範圍規則"
|
||||
has_lang_guard "$agents" || fail "$agents 寫入後讀不到繁中與編碼規則"
|
||||
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查與部署後重啟閘門,註解範圍與繁中編碼改為每輪結束掃整個工作區,不是逐檔即時"
|
||||
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查、部署後重啟閘門與寫入提交閘門,註解範圍與繁中編碼改為每輪結束掃整個工作區,不是逐檔即時"
|
||||
echo "$WIRE_PATH_NOTE"
|
||||
echo "[jsc] codex:已在 shell rc 加上 $bin 別名,轉呼叫 tools/jsc-wrap.sh codex,啟動當下開始計時。"
|
||||
echo "[jsc] codex:別名要開新的 shell 或重新 source rc 檔才生效。"
|
||||
@@ -1168,6 +1335,7 @@ case "$cli" in
|
||||
echo "[jsc] codex:SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 sdlc-gate.sh lock 寫入。"
|
||||
echo "[jsc] codex:版本前置檢查接不上(codex 沒有 pre-tool hook),改由 /jsc-cli:deploy 定期更新。"
|
||||
echo "[jsc] codex:部署後重啟閘門也接不上(同樣是沒有 pre-tool hook),一次技能呼叫都擋不下來;狀態檔照樣寫、下次工作階段開始照樣清,重啟要自己動手。"
|
||||
echo "[jsc] codex:write-guard.sh 的階段寫入、稽核寫入與提交檢查三種模式也接不上(同樣是沒有 pre-tool hook),一次寫入或提交都擋不下來,那三條規則在這裡只剩 SKILL.md 的散文。"
|
||||
echo "[jsc] codex:註解範圍與繁中編碼除了規則提示,每輪結束會由 notify 各掃一次整個 git 工作區(codex 沒有 post-tool hook,接不到逐檔即時掃描),回饋比 claude 晚一輪。"
|
||||
exit 1 ;;
|
||||
|
||||
@@ -1182,7 +1350,7 @@ case "$cli" in
|
||||
has_block "$instr" "<!-- jsc-hooks -->" || fail "$instr 寫入後讀不到 jsc-hooks 標記段落"
|
||||
has_comment_scope "$instr" || fail "$instr 寫入後讀不到註解範圍規則"
|
||||
has_lang_guard "$instr" || fail "$instr 寫入後讀不到繁中與編碼規則"
|
||||
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查與部署後重啟閘門,註解範圍與繁中編碼只在工作階段結束時掃一次整個工作區"
|
||||
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查、部署後重啟閘門與寫入提交閘門,註解範圍與繁中編碼只在工作階段結束時掃一次整個工作區"
|
||||
echo "$WIRE_PATH_NOTE"
|
||||
echo "[jsc] copilot:已在 shell rc 加上 $bin 別名,轉呼叫 tools/jsc-wrap.sh copilot。"
|
||||
echo "[jsc] copilot:已在 $instr 寫入 STE100、註解範圍與繁中編碼規則段落(prompt 降級)。"
|
||||
@@ -1190,6 +1358,7 @@ case "$cli" in
|
||||
echo "[jsc] copilot:SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 sdlc-gate.sh lock 寫入。"
|
||||
echo "[jsc] copilot:版本前置檢查接不上(copilot 沒有 pre-tool hook),改由 /jsc-cli:deploy 定期更新。"
|
||||
echo "[jsc] copilot:部署後重啟閘門也接不上(同樣是沒有 pre-tool hook),一次技能呼叫都擋不下來;狀態檔照樣寫、下次工作階段開始照樣清,重啟要自己動手。"
|
||||
echo "[jsc] copilot:write-guard.sh 的階段寫入、稽核寫入與提交檢查三種模式也接不上(同樣是沒有 pre-tool hook),一次寫入或提交都擋不下來,那三條規則在這裡只剩 SKILL.md 的散文。"
|
||||
echo "[jsc] copilot:註解範圍與繁中編碼除了規則提示,工作階段結束時由 jsc-wrap.sh 收尾各掃一次整個 git 工作區(copilot 連逐輪事件都沒有),回饋要等到離開 CLI 才看得到。"
|
||||
exit 1 ;;
|
||||
|
||||
@@ -1204,7 +1373,7 @@ case "$cli" in
|
||||
has_block "$rules" "<!-- jsc-hooks -->" || fail "$rules 寫入後讀不到 jsc-hooks 標記段落"
|
||||
has_comment_scope "$rules" || fail "$rules 寫入後讀不到註解範圍規則"
|
||||
has_lang_guard "$rules" || fail "$rules 寫入後讀不到繁中與編碼規則"
|
||||
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查與部署後重啟閘門,註解範圍與繁中編碼只在工作階段結束時掃一次整個工作區"
|
||||
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查、部署後重啟閘門與寫入提交閘門,註解範圍與繁中編碼只在工作階段結束時掃一次整個工作區"
|
||||
echo "$WIRE_PATH_NOTE"
|
||||
echo "[jsc] antigravity:已在 shell rc 加上 $bin 別名,轉呼叫 tools/jsc-wrap.sh antigravity。"
|
||||
echo "[jsc] antigravity:已在 $rules 寫入 STE100、註解範圍與繁中編碼規則段落(prompt 降級)。"
|
||||
@@ -1212,6 +1381,7 @@ case "$cli" in
|
||||
echo "[jsc] antigravity:SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 sdlc-gate.sh lock 寫入。"
|
||||
echo "[jsc] antigravity:版本前置檢查接不上(antigravity 沒有 pre-tool hook),改由 /jsc-cli:deploy 定期更新。"
|
||||
echo "[jsc] antigravity:部署後重啟閘門也接不上(同樣是沒有 pre-tool hook),一次技能呼叫都擋不下來;狀態檔照樣寫、下次工作階段開始照樣清,重啟要自己動手。"
|
||||
echo "[jsc] antigravity:write-guard.sh 的階段寫入、稽核寫入與提交檢查三種模式也接不上(同樣是沒有 pre-tool hook),一次寫入或提交都擋不下來,那三條規則在這裡只剩 SKILL.md 的散文。"
|
||||
echo "[jsc] antigravity:註解範圍與繁中編碼除了規則提示,工作階段結束時由 jsc-wrap.sh 收尾各掃一次整個 git 工作區(antigravity 連逐輪事件都沒有),回饋要等到離開 CLI 才看得到。"
|
||||
exit 1 ;;
|
||||
|
||||
@@ -1264,12 +1434,13 @@ EOF
|
||||
|| fail "$hookfile 的 run 沒有接到 lang-guard.sh prompt,每輪不會注入繁中與編碼規則"
|
||||
grep -qF 'lang-guard.sh\" sweep' "$hookfile" 2>/dev/null \
|
||||
|| fail "$hookfile 的 run 沒有接到 lang-guard.sh sweep,kiro 每輪不會掃簡體字與亂碼"
|
||||
printf 'status=degraded reason=%s\n' "SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查與部署後重啟閘門,註解範圍與繁中編碼改為每輪提示送出時掃整個工作區,不是逐檔即時"
|
||||
printf 'status=degraded reason=%s\n' "SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查、部署後重啟閘門與寫入提交閘門,註解範圍與繁中編碼改為每輪提示送出時掃整個工作區,不是逐檔即時"
|
||||
echo "$WIRE_PATH_NOTE"
|
||||
echo "[jsc] kiro:已建立 $startfile(sessionStart 開始計時)與 $hookfile(JSC_CLI=kiro),兩份都已驗證。"
|
||||
echo "[jsc] kiro:SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 sdlc-gate.sh lock 寫入。"
|
||||
echo "[jsc] kiro:版本前置檢查接不上(kiro 沒有 pre-tool hook),改由 /jsc-cli:deploy 定期更新。"
|
||||
echo "[jsc] kiro:部署後重啟閘門也接不上(同樣是沒有 pre-tool hook),一次技能呼叫都擋不下來;狀態檔照樣寫、下次工作階段開始照樣清,重啟要自己動手。"
|
||||
echo "[jsc] kiro:write-guard.sh 的階段寫入、稽核寫入與提交檢查三種模式也接不上(同樣是沒有 pre-tool hook),一次寫入或提交都擋不下來,那三條規則在這裡只剩 SKILL.md 的散文。"
|
||||
echo "[jsc] kiro:註解範圍與繁中編碼除了規則提示,每輪提示送出時會各掃一次整個 git 工作區(kiro 沒有 post-tool hook),掃到的是上一輪寫的檔。"
|
||||
exit 1 ;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user