feat/lang-guard-traditional-chinese-encoding #28

Merged
admin merged 6 commits from feat/lang-guard-traditional-chinese-encoding into develop 2026-08-27 01:58:38 +00:00
12 changed files with 1350 additions and 70 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-hooks",
"version": "0.2.0",
"version": "0.2.2",
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查",
"skills": "./skills",
"author": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-hooks",
"version": "0.2.0",
"version": "0.2.2",
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查",
"skills": "./skills"
}
+3 -2
View File
@@ -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 使用。
## 規則
@@ -9,7 +9,8 @@
3. 技能準則的唯一來源:`plugins/meta` 存取庫的 `references/guidelines.md`。
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. 主 agent 不需要處理細節的流程,一律建立 sub agent 處理。
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 處理。
## 呼叫慣例
+10 -8
View File
@@ -27,13 +27,14 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
| `hooks/version-guard.sh` | PreToolUse(Skill) | 技能使用前的版本前置檢查:本機**實際載入**版本落後遠端發佈版本就以 exit 2 擋下該次呼叫並提示更新指令(更新指令依當前 CLI 給)。只擋落後這一種情況:超前放行(開發技能組時本機本來就會超前),讀不到本機版本、推導不出站台、查不到遠端版本也一律放行。逃生門 `JSC_VERSION_GUARD=off`。豁免 `jsc-cli:deploy`、`jsc-hooks:hooks-install`、`jsc-cli:models`、`jsc-meta:*` |
| `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 階段技能呼叫,從 transcript 讀出實際模型 id 比對該階段必要標籤(`$JSC_HOME/model-tags.tsv`),不符就拒絕上鎖;`check` 在模型不符時以 exit 2 擋下該輪提示(其他 hook 一律 exit 0,此處是刻意例外);`unlock` 為逃生門。另含工作包 PR 閘門:`wp-lock {owner}/{repo} {index}` 記下一筆未結清的工作包 PR、`wp-unlock {owner}/{repo} {index}` 結清那一筆(檔案不存在也算成功)、`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`,不是這裡。逃生門 `JSC_WP_GATE=off`。這道閘門只讀檔案、不打網路,PR 的真實合併狀態由 `jsc-sdlc/tools/wp-gate.sh` 查證 |
Claude 由 `hooks/hooks.json` 自動接線六支 hook;其他 CLI 用 `hooks-install` 技能接線、改裝包裝啟動器,或降級為規則檔。
Claude 由 `hooks/hooks.json` 自動接線七支 hook;其他 CLI 用 `hooks-install` 技能接線、改裝包裝啟動器,或降級為規則檔。
> 覆蓋範圍要據實看待:只有 claude 同時有 PreToolUse、PostToolUse 與 UserPromptSubmit,六支 hook 全接得上,回報 `wired`。codex、copilot、antigravity、kiro 都沒有 pre-tool hook,接不上 `version-guard.sh` 的版本前置檢查,SDLC 模型鎖也只剩技能步驟檢查,這四個 CLI 一律回報 `degraded`,靠 `/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` 的版本前置檢查,SDLC 模型鎖也只剩技能步驟檢查,這四個 CLI 一律回報 `degraded`,靠 `/jsc-cli:deploy` 定期更新。codex 另外沒有工作階段開始事件,計時改由 `tools/jsc-wrap.sh` 的 `codex` 別名在啟動當下開始;沒走別名啟動時,時間從第一輪回應算起。
> `comment-scope.sh` 五個 CLI 都掃得到,但時機不同,不能當成五支一樣:
> `comment-scope.sh` 與 `lang-guard.sh` 五個 CLI 都掃得到,接的是同一批位置,但時機不同,不能當成五支一樣:
| CLI | 掃描時機 | 接在哪裡 |
| --- | --- | --- |
@@ -42,7 +43,7 @@ Claude 由 `hooks/hooks.json` 自動接線六支 hook;其他 CLI 用 `hooks-in
| kiro | 每輪提示送出時,掃整個 git 工作區(掃到的是上一輪寫的檔) | `.kiro/hooks/jsc-hooks.json` 的 `userPromptSubmit` |
| copilot、antigravity | 工作階段結束時掃一次 | `tools/jsc-wrap.sh` 收尾 |
> `sweep` 看的是 `git diff HEAD`,涵蓋範圍與 claude 一樣,差的是回饋速度:claude 當下就叫,其他四個要等到該輪或該階段結束。不在 git 工作區內時 `sweep` 安靜 exit 0,等於沒掃。規則提示(`prompt` 模式)在五個 CLI 都照樣寫進規則檔,與 STE100 共用同一個標記段落——晚一輪的警告,價值仍低於一開始就不要寫。判不出來的項目(專案代號、客戶名稱)一律交給 `/jsc-review:code-review` 第 2 組。
> 上表對 `comment-scope.sh` 與 `lang-guard.sh` 同時成立,兩支接在同一批位置。`sweep` 看的是 `git diff HEAD`,涵蓋範圍與 claude 一樣,差的是回饋速度:claude 當下就叫,其他四個要等到該輪或該階段結束。不在 git 工作區內時 `sweep` 安靜 exit 0,等於沒掃。規則提示(`prompt` 模式)在五個 CLI 都照樣寫進規則檔,三段(STE100、註解範圍、繁中編碼)共用同一個標記段落——晚一輪的警告,價值仍低於一開始就不要寫。判不出來的項目(專案代號、客戶名稱)一律交給 `/jsc-review:code-review` 第 2 組。
> `version-guard.sh report` 是非 hook 的子指令:印出每個已安裝 jsc plugin 的
> 「{domain} {本機} {遠端} {落後|最新|超前|查詢失敗}」,最後一行 `behind {落後個數}`。
@@ -55,10 +56,10 @@ 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` 掃整個 git 工作區的註解範圍(copilot 與 antigravity 沒有任何逐輪事件,整個工作階段只有這裡掃得到)。收尾掃描一律不影響結束碼:包裝器原樣回傳 CLI 自己的結束碼,`sweep` 命中只把警告印到 stderr。`JSC_CLI` 存 CLI 代號,實際執行的是對應的執行檔(antigravity 是 agy、kiro 是 kiro-cli) |
| `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}` 是接線:對應的設定編輯、包裝別名安裝、hook 檔建立,皆以 `<!-- jsc-hooks -->`(或 `# jsc-hooks`)標記整段重寫,重跑等同先移除再重裝;寫完每個檔案會重讀驗證位置正確才回報成功(codex 的 `notify` 必須是根層鍵、kiro 的 JSON 必須成對且 `on`、`run` 在最上層),以 `status=wired\|degraded\|skipped\|failed` 回報。`purge {cli}` 是移除:把該 CLI 的**所有** hook 清掉,含非 jsc 的第三方項目,動到的檔案先原樣備份到 `$JSC_HOME/backup/hooks/{cli}/{yyyyMMdd_HHmmss}/`,備份失敗就不移除,移除後重讀驗證,驗不過自動還原備份,以 `status=purged\|skipped\|failed` 回報。`smoke {cli}` 是執行期冒煙測試:六支 hook 的每個接線模式各跑一次,非零退出即為錯誤(例外有兩個:`sdlc-gate.sh check` 的 exit 2 是階段鎖的設計行為,`comment-scope.sh` 掃描模式的 exit 2 是掃到違規註解的設計行為——`sweep` 在髒工作區本來就會回 2,不算 hook 壞掉),以 `status=ok\|failed` 回報。`status {cli}` 是唯讀盤點:只讀設定檔判斷標記段落在不在,不寫檔也不執行 hook,每個接線點印一行 `item<TAB>{項目}<TAB>{路徑}<TAB>{present\|missing}`,以 `status=wired\|degraded\|unwired\|skipped` 回報(結束碼 0、1、5、3)。體檢類技能(`/jsc-cli:doctor`)只能用這個子命令,另外三個都會動到環境 |
| `tools/wire-cli.sh` | 單一 CLI 的 hook 生命週期,共三個用法。`{cli}` 是接線:對應的設定編輯、包裝別名安裝、hook 檔建立,皆以 `<!-- jsc-hooks -->`(或 `# jsc-hooks`)標記整段重寫,重跑等同先移除再重裝;寫完每個檔案會重讀驗證位置正確才回報成功(codex 的 `notify` 必須是根層鍵、kiro 的 JSON 必須成對且 `on`、`run` 在最上層),以 `status=wired\|degraded\|skipped\|failed` 回報。`purge {cli}` 是移除:把該 CLI 的**所有** hook 清掉,含非 jsc 的第三方項目,動到的檔案先原樣備份到 `$JSC_HOME/backup/hooks/{cli}/{yyyyMMdd_HHmmss}/`,備份失敗就不移除,移除後重讀驗證,驗不過自動還原備份,以 `status=purged\|skipped\|failed` 回報。`smoke {cli}` 是執行期冒煙測試:七支 hook 的每個接線模式各跑一次,非零退出即為錯誤(例外有三個:`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=wired\|degraded\|unwired\|skipped` 回報(結束碼 0、1、5、3)。體檢類技能(`/jsc-cli:doctor`)只能用這個子命令,另外三個都會動到環境 |
| `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` 回報 |
## 失敗回報範本
@@ -79,7 +80,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` 自動接線,無需寫入),接著 `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` 自動接線,無需寫入),接著 `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 錯誤只回報,不轉修正。
### `repair`
@@ -99,7 +100,8 @@ Claude 由 `hooks/hooks.json` 自動接線六支 hook;其他 CLI 用 `hooks-in
| `JSC_VERSION_TTL` | 遠端版本查詢的快取秒數 | 預設 600 |
| `JSC_WP_GATE` | 設 `off` 完全略過工作包 PR 閘門(`wp-check` 一律放行) | 啟用閘門 |
| `JSC_COMMENT_SCOPE` | 設 `off` 完全略過註解範圍檢查(`comment-scope.sh` 三種模式都直接結束) | 啟用檢查 |
| `JSC_CHANGED_FILE` | 非 Claude CLI 要掃描的檔案路徑,代替 stdin JSON 的 `file_path`,供 `comment-scope.sh` 使用 | 安靜降級,不掃描 |
| `JSC_LANG_GUARD` | 設 `off` 完全略過繁中與編碼檢查(`lang-guard.sh` 三種模式都直接結束) | 啟用檢查 |
| `JSC_CHANGED_FILE` | 非 Claude CLI 要掃描的檔案路徑,代替 stdin JSON 的 `file_path`,供 `comment-scope.sh` 與 `lang-guard.sh` 使用 | 安靜降級,不掃描 |
| `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` | 非 Claude CLI 的目前模型,供 `sdlc-gate.sh` 比對;優先序在 transcript 實際值與 stdin `model` 之後 | 改讀 `~/.claude/settings.json`,再不行就安靜降級 |
+8
View File
@@ -28,6 +28,10 @@
{
"type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/comment-scope.sh\" prompt"
},
{
"type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/lang-guard.sh\" prompt"
}
]
}
@@ -83,6 +87,10 @@
{
"type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/comment-scope.sh\""
},
{
"type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/lang-guard.sh\""
}
]
}
+178
View File
@@ -0,0 +1,178 @@
#!/usr/bin/env sh
# lang-guard.sh — 非程式碼輸出一律繁體中文、UTF-8、無亂碼、無簡體字(hook > prompt 的強制層)。
# 規則正文的唯一來源:jsc-meta 的 references/ste100.md。本腳本只實作可用樣式判定的三項:
# 簡體字、亂碼、非 UTF-8 編碼;用詞、標點、語氣那些判不出來的交給 ste100-guard.sh 的提示層。
#
# 用法:
# lang-guard.sh prompt 注入規則摘要(UserPromptSubmit 或規則檔取文字用)
# lang-guard.sh 掃描剛寫入的單一檔案(PostToolUse)
# lang-guard.sh sweep [dir] 掃描整個工作區這次改過的所有檔案(沒有 post-tool hook 的 CLI 用)
#
# 為什麼要有 sweep:只有 claude 接得到 PostToolUse,逐檔精準掃得到。codex 只有每輪結束的
# notify、kiro 只有 userPromptSubmit、copilot 與 antigravity 只有包裝別名,這四個都拿不到
# 「剛剛寫了哪個檔」,只能改成掃整個工作區的 git diff。時機晚一點,涵蓋範圍一樣。
#
# 輸入相容:
# Claude: PostToolUse 的 stdin JSON,取 tool_input.file_path。
# 其他 CLI: 環境變數 JSC_CHANGED_FILE。
# 兩者都取不到就安靜降級(exit 0)。
#
# 掃描範圍跟 comment-scope.sh 有兩點刻意不同,不要照抄那支的判斷:
# 1. 三項檢查都掃整個檔案,不是只掃註解行。程式碼的任何位置都不該出現簡體字或亂碼——
# 字串常值、識別字、資料內容一樣算輸出。comment-scope.sh 只掃註解,是因為它抓的是
# 註解夾帶文件編號,那件事只發生在註解裡。
# 2. markdown 與純文字檔要掃。它們正是「非程式碼輸出」的主場:README、wiki 頁、PR 描述、
# commit 訊息都是這類檔案。comment-scope.sh 刻意跳過 .md,因為那裡沒有程式碼註解。
#
# 掃描深度:檔案在 git 工作區內且已追蹤,就只掃 `git diff HEAD` 的新增行,不翻舊帳;
# 不在 git 內或檔案尚未追蹤才整檔掃描。sweep 一律只看 git diff。
#
# 結束碼:0=沒命中或資料不足;2=命中,訊息走 stderr 交回模型自行修正(不擋寫入,檔案已經寫好了)。
# 逃生門:JSC_LANG_GUARD=off。
set -u
. "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/lib.sh" 2>/dev/null || true
[ "${JSC_LANG_GUARD:-on}" = "off" ] && exit 0
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
WORDLIST="$HERE/simplified.txt"
if [ "${1:-}" = "prompt" ]; then
echo "[jsc] 所有非程式碼輸出一律繁體中文、UTF-8、無亂碼、無簡體字。適用範圍:程式碼註解、commit 訊息、PR 描述與標題、wiki 頁、對使用者的回報、README 與各種文件、錯誤訊息與日誌文字。程式碼本身的關鍵字、識別字、API 欄位名維持原樣,但其中的中文一樣要是繁體。"
echo "[jsc] 送出前自我檢查:1)簡體字(例:应、为、这、说、发、国)一律換成繁體;2)替代字元「U+FFFD」與雙重編碼亂碼(Ã、â 開頭的怪序列)代表編碼壞掉,重寫那段而不是保留;3)檔案一律存成 UTF-8,不加 BOM。規則正文見 jsc-meta 的 references/ste100.md。"
exit 0
fi
# 簡體字樣式:由 hooks/simplified.txt 組出,字表是單一真實來源,腳本裡不留第二份。
# 讀不到字表就回傳 1,呼叫端安靜跳過這一項,不中斷整支腳本——少抓一項,好過整支 hook 死掉。
simplified_pattern() {
[ -f "$WORDLIST" ] || return 1
_p=$(sed -e 's/#.*//' -e 's/[[:space:]]//g' "$WORDLIST" 2>/dev/null \
| grep -v '^$' | tr '\n' '|' | sed 's/|$//')
[ -n "$_p" ] || return 1
printf '%s' "$_p"
}
# 亂碼樣式一律用位元組比對(LC_ALL=C),不靠語系的字元範圍:
# 替代字元 U+FFFD(EF BF BD)本身;
# � ——「U+FFFD 的 UTF-8 位元組再被當成 Latin-1 讀一次」的雙重編碼殘骸;
# U+00C0–U+00FF 的字後面緊接 U+0080–U+00BF 的字(ä、æ¸、è©、ç”),這是 UTF-8 被當成
# Latin-1 讀一次再存回 UTF-8 的固定長相。中文的 UTF-8 前導位元組落在 E4–E9,被誤讀後
# 就變成 ä–é 開頭、後面接 U+0080–U+00BF 的兩三個字,所以前導字要收整個 Latin-1 字母段,
# 只收「Ã」會漏掉最常見的中文亂碼;
# â 後面接任何非 ASCII 字(’、“),引號與破折號被雙重編碼時的典型長相。
# 用位元組比對是因為 grep -E 的字元範圍在不同語系下行為不一致,位元組範圍到哪都一樣。
# 誤報防線:正常的西歐文字(câmara、crème、naïve)重音字後面接的是 ASCII 字母,不會命中。
mojibake_pattern() {
_fffd=$(printf '\357\277\275')
_lo=$(printf '\200'); _hi=$(printf '\277')
_c3=$(printf '\303'); _c2=$(printf '\302') # U+00C0–U+00FF 與 U+0080–U+00BF 的前導位元組
_bx=$(printf '\303\242') # 「â」
_l2=$(printf '\302'); _h2=$(printf '\364')
printf '%s|%s|%s[%s-%s]%s[%s-%s]|%s[%s-%s]' \
"$_fffd" '�' \
"$_c3" "$_lo" "$_hi" "$_c2" "$_lo" "$_hi" \
"$_bx" "$_l2" "$_h2"
}
hit() { # $1=樣式 $2=說明;命中就把說明與最多三行證據印到 stdout
m=$(printf '%s\n' "$lines" | LC_ALL=C grep -nE "$1" 2>/dev/null | head -n 3)
[ -n "$m" ] || return 0
printf ' %s\n' "$2"
printf '%s\n' "$m" | sed 's/^/ /'
}
scan_file() { # $1=檔案路徑;命中就把報告印到 stdout 並回傳 1,沒命中回傳 0
f=$1
[ -f "$f" ] || return 0
# 產生檔與壓縮輸出跳過:內容不是人寫的,抓到也沒有人要改。
case "$f" in
*.lock|*.min.js|*.min.css|*.map) return 0 ;;
esac
# 字表與兩支語言規則腳本跳過:這幾份檔案裡的簡體字與亂碼樣本是「被討論的對象」,
# 不是被使用。同一個道理,jsc-meta 的 ste100-lint.sh 也跳過 references/ste100.md。
# 不跳過的話,這支 hook 每次都會先抓到自己,訊號全被自己的噪音蓋掉。
case "$f" in
*/simplified.txt|simplified.txt) return 0 ;;
*/ste100-guard.sh|ste100-guard.sh) return 0 ;;
*/lang-guard.sh|lang-guard.sh) return 0 ;;
*/references/ste100.md|ste100.md) return 0 ;;
*/ste100-lint.sh|ste100-lint.sh) return 0 ;;
esac
# 二進位檔跳過。只認 NUL 位元組——拿「非可列印字元」當判準會把所有含中文的檔案誤判成二進位。
raw=$(head -c 1024 "$f" 2>/dev/null | wc -c)
txt=$(head -c 1024 "$f" 2>/dev/null | LC_ALL=C tr -d '\000' | wc -c)
[ "$raw" = "$txt" ] || return 0
d=$(dirname -- "$f")
if git -C "$d" rev-parse --is-inside-work-tree >/dev/null 2>&1 &&
git -C "$d" ls-files --error-unmatch -- "$f" >/dev/null 2>&1; then
lines=$(git -C "$d" diff HEAD -- "$f" 2>/dev/null | sed -n 's/^+[^+]/&/p' | cut -c2-)
[ -n "$lines" ] || return 0
else
lines=$(cat "$f" 2>/dev/null)
fi
out=$(
_sp=$(simplified_pattern) && hit "$_sp" '簡體字,改成繁體'
hit "$(mojibake_pattern)" '亂碼:替代字元或雙重編碼殘骸,重寫這一段'
# 編碼檢查沒有行號可指,命中就整檔報一行。沒有 iconv 就跳過這一項。
if command -v iconv >/dev/null 2>&1; then
printf '%s\n' "$lines" | iconv -f UTF-8 -t UTF-8 >/dev/null 2>&1 \
|| printf ' %s\n' '非 UTF-8 編碼,整檔轉存成 UTF-8(不加 BOM)'
fi
)
[ -n "$out" ] || return 0
printf '%s\n' "$f"
printf '%s\n' "$out"
return 1
}
advice() {
printf ' 修法:簡體字換成對應繁體字;亂碼那段重打,不要留著半壞的字元;檔案存成 UTF-8。\n'
printf ' 規則正文見 jsc-meta 的 references/ste100.md,字表在 hooks/simplified.txt。誤判時用 JSC_LANG_GUARD=off 關閉。\n'
}
if [ "${1:-}" = "sweep" ]; then
target=${2:-.}
[ -d "$target" ] || exit 0
root=$(git -C "$target" rev-parse --show-toplevel 2>/dev/null) || exit 0
[ -n "$root" ] || exit 0
changed=$(git -C "$root" diff --name-only HEAD 2>/dev/null)
[ -n "$changed" ] || exit 0
# 報告累積在暫存檔:迴圈跑在管線的子行程裡,變數帶不回來。
tmp=${TMPDIR:-/tmp}/jsc-lang-guard.$$
: > "$tmp" 2>/dev/null || exit 0
printf '%s\n' "$changed" | while IFS= read -r rel; do
[ -n "$rel" ] || continue
scan_file "$root/$rel" >> "$tmp" 2>/dev/null
done
if [ -s "$tmp" ]; then
{
printf '[jsc] 工作區有簡體字、亂碼或編碼問題,請就地修正:\n'
sed 's/^/ /' "$tmp"
advice
} >&2
rm -f "$tmp"
exit 2
fi
rm -f "$tmp"
exit 0
fi
read_stdin 2>/dev/null || STDIN_JSON=""
file=$(json_str file_path 2>/dev/null || true)
[ -n "$file" ] || file="${JSC_CHANGED_FILE:-}"
[ -n "$file" ] || exit 0
report=$(scan_file "$file") && exit 0
{
printf '[jsc] 這個檔案有簡體字、亂碼或編碼問題,請就地修正:\n'
printf '%s\n' "$report"
advice
} >&2
exit 2
+1019
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -3,6 +3,6 @@
# Claude: UserPromptSubmit 的 stdout 會成為額外 context。
# 其他 CLI: 由 hooks-install 以各自的規則檔(AGENTS.md 等)落地,本腳本仍可被 wrapper 呼叫。
# 完整規則的唯一來源:jsc-meta 的 references/ste100.md。
echo "[jsc] 輸出規則:STE100 繁體中文,擬人台灣感。短句、一句一指令、主動語態、術語一致;台灣用語(預設、支援、相容、資訊);全形標點;去 AI 味(不用「總的來說」「首先/其次/最後」開場收尾套路、不諂媚);直接講重點,UTF-8 無亂碼。完整規則見 jsc-meta 的 references/ste100.md。"
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
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-hooks",
"version": "0.2.0",
"version": "0.2.2",
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查",
"skills": "./skills/"
}
+8 -7
View File
@@ -1,17 +1,17 @@
---
name: hooks-install
description: Wire jsc hooks (STE100 guard, session timer, skill usage logger, SDLC model gate, plugin version guard, comment scope scanner) 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, 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.
---
# hooks-install — wire jsc hooks into every installed CLI
Goal: make the six hooks (`ste100-guard.sh`, `session-timer.sh`, `skill-usage.sh`, `sdlc-gate.sh`, `version-guard.sh`, `comment-scope.sh`) effective in every CLI, with nothing else wired alongside them.
Goal: make the seven hooks (`ste100-guard.sh`, `session-timer.sh`, `skill-usage.sh`, `sdlc-gate.sh`, `version-guard.sh`, `comment-scope.sh`, `lang-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.
Only claude has PreToolUse, PostToolUse and UserPromptSubmit, so only claude reports `wired`. On codex, copilot, antigravity and kiro the version guard cannot 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.
`comment-scope.sh` now reaches all five, but on a different event and at a different moment each. Report the timing per CLI; never state it as one uniform behaviour:
`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:
| CLI | Scanning moment | Wired through |
| --- | --- | --- |
@@ -20,7 +20,7 @@ Only claude has PreToolUse, PostToolUse and UserPromptSubmit, so only claude rep
| kiro | On every prompt submit, over the whole git worktree — it sees what the previous turn wrote | `userPromptSubmit` in `.kiro/hooks/jsc-hooks.json` |
| copilot, antigravity | Once, when the session ends | `tools/jsc-wrap.sh` teardown |
The `sweep` mode reads `git diff HEAD`, so its coverage matches what claude sees; only the feedback delay differs. Outside a git worktree `sweep` exits 0 in silence and nothing is scanned at all — say so when the user works outside git. The `prompt` rule reminder still goes into every rule file alongside the STE100 block, because a warning that arrives a turn late is worth less than not writing the comment in the first place.
The table above holds for both scanners. The `sweep` mode reads `git diff HEAD`, so its coverage matches what claude sees; only the feedback delay differs. Outside a git worktree `sweep` exits 0 in silence and nothing is scanned at all — say so when the user works outside git. Both `prompt` rule reminders still go into every rule file alongside the STE100 block, because a warning that arrives a turn late is worth less than not writing the offending text in the first place.
The lock file still works on those four because the SDLC skills call `sdlc-gate.sh lock {stage}` directly — that call is where the capability-tag comparison happens, so the gate keeps its force even where the prompt hook cannot be wired.
The gate needs `$JSC_HOME/model-tags.tsv`; when it is missing, report that `jsc-cli:models` (or `jsc-cli/tools/model-tags.sh sync`) must run once, because `sdlc-gate.sh lock` refuses to lock without it.
@@ -33,7 +33,7 @@ The detailed flow **MUST run as a sub agent**; the main agent only reports the s
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. 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 writes the config, alias or hook file inside a `<!-- jsc-hooks -->` (or `# jsc-hooks`) marker block, re-reads every file it wrote, and confirms the block is present and correctly placed 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 six hooks once each, every wired mode included, 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 one result line per hook.
4. For each installed CLI, run `tools/wire-cli.sh smoke {cli}`. This runs all seven hooks once each, every wired mode included, 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 one result line per hook.
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`.
@@ -44,8 +44,9 @@ The detailed flow **MUST run as a sub agent**; the main agent only reports the s
- `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.
- `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.
- `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` exit 2 counts as healthy for the same reason — it means the scan found a comment and warned about it. The 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. Neither is a broken hook.
- `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.
- `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.
- `jsc-wrap.sh` runs `sweep` 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 comment warning turn a successful CLI run into a failed one.
- `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.
- `tools/report-error.sh` is operator- or skill-invoked only. Never wire it to fire from a failing hook: hooks stay silent and exit 0, and a failing hook that reports itself can loop.
- Data lands in `$JSC_HOME` (default `~/.jsc`), consumed by `jsc-log:worklog` and `jsc-log:stats`.
+5 -1
View File
@@ -3,7 +3,8 @@
# 用法: jsc-wrap.sh {cli} [args...]
# 行為: 匯出 JSC_CLI 與 JSC_SESSION_ID → session-timer start → 執行 CLI →
# 結束後 session-timer mark、以 scan-logs.sh 回填用量、以 comment-scope.sh sweep
# 掃一次整個工作區的註解範圍,最後回傳 CLI 的結束碼。
# 掃一次整個工作區的註解範圍,再以 lang-guard.sh sweep 掃一次繁中與編碼,
# 最後回傳 CLI 的結束碼。
# 注意: JSC_CLI 存的是 CLI 代號(antigravity、kiro),實際執行的是 cli_bin 對應的
# 執行檔(agy、kiro-cli)。直接拿代號當指令跑會 127,因為沒有這兩個執行檔。
HERE=$(cd "$(dirname "$0")" && pwd)
@@ -32,4 +33,7 @@ sh "$HERE/scan-logs.sh" --cli "$cli" --session "$JSC_SESSION_ID" </dev/null || t
# 警告只走 sweep 自己的 stderr,包裝器一律原樣回傳 $rc——包裝器改掉結束碼,呼叫端的
# `cmd && next` 就會誤判,註解檢查不該有這種副作用。
sh "$HOOKS/comment-scope.sh" sweep </dev/null || true
# 繁中與編碼收尾掃描:同一個理由、同一種寫法。`|| true` 一樣不可省,lang-guard.sh 掃到
# 簡體字或亂碼也會 exit 2,接住它才不會蓋掉底下要回傳的 CLI 結束碼。
sh "$HOOKS/lang-guard.sh" sweep </dev/null || true
exit "$rc"
+115 -48
View File
@@ -3,7 +3,7 @@
# 用法:
# 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
#
# purge 移除的是「所有 hook」,含非 jsc 的第三方項目。安裝一律先 purge 再接線:混著別人的
@@ -14,37 +14,42 @@
# 跑起來不出錯(缺 node、路徑錯、權限不足都只在真的執行時才現形)。
#
# 接線行為(依 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 掃整個工作區,
# JSC_CLI=codex);在 AGENTS.md 附加 STE100 與註解範圍規則段落(prompt 降級)
# 結束時收尾掃一次註解範圍與繁中編碼);在 config.toml 設 notify(每輪補
# session-timer.sh start 再 mark,最後 comment-scope.sh sweep 與
# lang-guard.sh sweep 掃整個工作區,JSC_CLI=codex);在 AGENTS.md 附加
# STE100、註解範圍與繁中編碼規則段落(prompt 降級)
# copilot — 在 shell rc 檔加上 copilot 別名,轉呼叫 tools/jsc-wrap.sh copilot(結束時
# 收尾掃一次註解範圍);在 copilot-instructions.md 附加 STE100 與註解範圍規則段落
# 收尾掃一次註解範圍與繁中編碼);在 copilot-instructions.md 附加 STE100、
# 註解範圍與繁中編碼規則段落
# antigravity — 在 shell rc 檔加上 agy 別名,轉呼叫 tools/jsc-wrap.sh antigravity(同樣收尾
# 掃一次);在全域規則檔附加 STE100 與註解範圍規則段落
# kiro — 在工作區 .kiro/hooks/ 下建立 jsc-hooks.json(每輪 mark 加 STE100
# 與註解範圍規則,再 comment-scope.sh sweep 掃整個工作區)與
# jsc-hooks-session-start.json(sessionStart 開始計時),皆帶 JSC_CLI=kiro
# 掃一次);在全域規則檔附加 STE100、註解範圍與繁中編碼規則段落
# kiro — 在工作區 .kiro/hooks/ 下建立 jsc-hooks.json(每輪 mark 加 STE100、
# 註解範圍與繁中編碼規則,再 comment-scope.sh sweep 與 lang-guard.sh sweep
# 掃整個工作區)與 jsc-hooks-session-start.json(sessionStart 開始計時),
# 皆帶 JSC_CLI=kiro
#
# 覆蓋範圍要據實回報,不得暗示每個 CLI 都有保護:
# claude 六支 hook 全接,回報 wired
# claude 七支 hook 全接,回報 wired
# codex、copilot、antigravity、kiro 只有別名、notify 或規則檔,接不上 PreToolUse、PostToolUse
# 與 UserPromptSubmit,版本前置檢查與 SDLC 模型鎖都沒接上,
# 一律回報 degraded 並在 reason 講明
#
# 註解範圍掃描每個 CLI 的時機都不同,回報時不得寫成五支一樣:
# 註解範圍與繁中編碼掃描每個 CLI 的時機都不同(兩支腳本接在同一批位置),回報時不得寫成五支一樣:
# claude 掛在 PostToolUse,寫完哪個檔就掃哪個,逐檔即時
# codex 掛在每輪結束的 notify,掃整個 git 工作區這輪改過的檔(sweep)
# kiro 掛在 userPromptSubmit,掃整個 git 工作區,掃到的是上一輪寫的檔
# copilot、antigravity 沒有任何逐輪事件,只有工作階段結束時由 jsc-wrap.sh 收尾掃一次
# 時機晚一點、涵蓋範圍一樣:sweep 看的是 git diff,那一輪寫過的檔一個都不會漏。真正的差別
# 在回饋速度——claude 當下就叫,其他四個要等到該輪或該階段結束。
# 在回饋速度——claude 當下就叫,其他四個要等到該輪或該階段結束。lang-guard.sh 的三種模式
# 一律跟著 comment-scope.sh 接在同一批位置,兩支的掃描時機表完全一致。
# 所有 jsc 標記段落都採整段重寫,重跑等同先移除舊內容再重裝
#
# 寫入後自我驗證,通過才回報成功:每個寫過的檔案重新讀一次,確認標記段落存在且落在
# 正確位置(codex 的 notify 必須是根層鍵,不能被歸進前一張表;kiro 的 JSON 必須成對
# 且 on、run 在最上層),內容也要涵蓋這次該接上的每一支腳本(含 comment-scope.sh sweep)。
# 且 on、run 在最上層),內容也要涵蓋這次該接上的每一支腳本(含 comment-scope.sh sweep
# 與 lang-guard.sh sweep)。
# 腳本說寫好了卻寫錯位置或少接一支,是最難查的失敗,所以驗證放在腳本裡。
#
# 輸出: 第一行固定為 `status=... reason=...`(可供程式判讀),其後為人類可讀的繁中說明。
@@ -91,9 +96,12 @@ ste100_text() { sh "$HOOKS/ste100-guard.sh" 2>/dev/null | sed '/^exit /d'; }
# 規則正文不在這裡抄一份:抄了就會跟腳本各自漂移,兩邊講的規則對不起來。
comment_scope_text() { sh "$HOOKS/comment-scope.sh" prompt 2>/dev/null | sed '/^exit /d'; }
# 寫進規則檔的完整段落:語言規則加註解範圍規則,共用同一組 jsc-hooks 標記。
# 兩段合在一個標記段落裡,purge 與重跑接線都是整段處理,不必各自再記一組標記。
rules_text() { ste100_text; comment_scope_text; }
# 繁中與編碼規則段落的唯一來源:lang-guard.sh prompt 的實際輸出。理由同上,不在這裡抄一份。
lang_guard_text() { sh "$HOOKS/lang-guard.sh" prompt 2>/dev/null | sed '/^exit /d'; }
# 寫進規則檔的完整段落:語言規則加註解範圍規則加繁中編碼規則,共用同一組 jsc-hooks 標記。
# 三段合在一個標記段落裡,purge 與重跑接線都是整段處理,不必各自再記一組標記。
rules_text() { ste100_text; comment_scope_text; lang_guard_text; }
# 驗證:規則檔真的收到註解範圍那一段了嗎。比對字串取自腳本的第一行實際輸出,
# 不是另外抄一句關鍵字——抄的關鍵字改腳本時不會跟著改,驗證就會永遠通過。
@@ -104,6 +112,14 @@ has_comment_scope() {
grep -qF "$_first" "$1" 2>/dev/null
}
# 驗證:規則檔真的收到繁中與編碼那一段了嗎。同樣取腳本的第一行實際輸出來比對,理由同上。
# $1=檔案
has_lang_guard() {
_first=$(lang_guard_text | head -n1)
[ -n "$_first" ] || return 1
grep -qF "$_first" "$1" 2>/dev/null
}
# 以標記整段取代(冪等);標記不存在就在檔尾新增;檔案不存在就建立。
# 適用 markdown 規則檔與 shell rc 檔:這兩種檔案沒有「區段」概念,附在檔尾就對了。
# $1=檔案 $2=開頭標記行 $3=結尾標記行 $4=標記之間要寫入的內容
@@ -616,8 +632,8 @@ if [ "$action" = smoke ]; then
_h="$1"; _s="${2:-}"
# 技能名一律清空:冒煙要驗的是「沒有技能情境時腳本跑得完」。留著繼承來的 JSC_SKILL,
# sdlc-gate.sh wp-check skill 會拿它當真實呼叫判定,有未結清 PR 時就誤報成執行期錯誤。
# JSC_CHANGED_FILE 同理清空:留著繼承來的檔名,comment-scope.sh 會真的去掃那個檔,
# 掃到違規註解就 exit 2,冒煙測試變成看環境臉色,測不出腳本本身跑不跑得完。
# JSC_CHANGED_FILE 同理清空:留著繼承來的檔名,comment-scope.sh 與 lang-guard.sh 會真的
# 去掃那個檔,掃到違規就 exit 2,冒煙測試變成看環境臉色,測不出腳本本身跑不跑得完。
_out=$(printf '{}' | JSC_CLI="$cli" JSC_SKILL="" SKILL="" JSC_CHANGED_FILE="" \
sh "$HOOKS/$_h" $_s 2>&1); _rc=$?
if [ "$_rc" -eq 0 ]; then
@@ -632,6 +648,10 @@ if [ "$action" = smoke ]; then
# 無參數模式冒煙時取不到檔名,正常會走 exit 0;sweep 則看工作區乾不乾淨——工作區剛好
# 有違規註解就回 2。那是 hook 正常工作,不是 hook 壞掉,不能因此判定接線失敗。
printf '[jsc] %s%s:exit 2,掃到違規註解並發出警告,屬設計行為,不算錯誤。\n' "$_h" "${_s:+ $_s}" >> "$smoke_out"
elif [ "$_h" = lang-guard.sh ] && [ "$_rc" -eq 2 ]; then
# 沿用同一條例外:lang-guard.sh 掃描模式的 exit 2 是「掃到簡體字、亂碼或編碼問題」的
# 設計行為。sweep 一樣看工作區乾不乾淨,髒工作區本來就會回 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" \
@@ -655,9 +675,14 @@ if [ "$action" = smoke ]; then
smoke_one comment-scope.sh prompt
smoke_one comment-scope.sh
smoke_one comment-scope.sh sweep
# lang-guard.sh 同樣有三個接在不同事件的模式,三個都要驗,判定理由與 comment-scope.sh 相同:
# prompt 一律 exit 0,無參數模式取不到檔名時安靜 exit 0,sweep 在髒工作區回 2 由白名單放行。
smoke_one lang-guard.sh prompt
smoke_one lang-guard.sh
smoke_one lang-guard.sh sweep
if [ "$smoke_fails" -eq 0 ]; then
printf 'status=ok reason=%s\n' "六支 hook 的每個接線模式都跑得完,沒有執行期錯誤"
printf 'status=ok reason=%s\n' "七支 hook 的每個接線模式都跑得完,沒有執行期錯誤"
cat "$smoke_out"; rm -f "$smoke_out"; exit 0
fi
printf 'status=failed reason=%s\n' "$smoke_fails 支 hook 有執行期錯誤"
@@ -694,6 +719,13 @@ if [ "$action" = status ]; then
else st_item "$1" "$2" missing; fi
}
# 繁中與編碼規則寫進規則檔了嗎。同樣與 STE100 共用標記段落,所以要單獨比對內容:
# 標記在、內容卻是舊版沒有這一段時,這一項才看得出來缺了。$1=項目名 $2=檔案
st_lang_guard() {
if [ -f "$2" ] && has_lang_guard "$2"; then st_item "$1" "$2" present
else st_item "$1" "$2" missing; fi
}
# 別名段落只要任何一個既有 rc 檔帶有標記就算接上。$1=項目名 $2=標記名
st_rc_alias() {
for _rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.config/fish/config.fish"; do
@@ -707,11 +739,14 @@ if [ "$action" = status ]; then
claude)
if [ -f "$HOOKS/hooks.json" ]; then st_item hooks.json "$HOOKS/hooks.json" present
else st_item hooks.json "$HOOKS/hooks.json" missing; fi
# 六支 hook 全靠這一個檔宣告,只看檔案在不在會漏掉「檔在、某支沒接進去」。
# 最後加進來的 comment-scope.sh 是最可能漏的一支,所以單獨列一項。
# 七支 hook 全靠這一個檔宣告,只看檔案在不在會漏掉「檔在、某支沒接進去」。
# 後來才加進來的 comment-scope.sh 與 lang-guard.sh 是最可能漏的兩支,所以各列一項。
if [ -f "$HOOKS/hooks.json" ] && grep -qF 'comment-scope.sh' "$HOOKS/hooks.json" 2>/dev/null
then st_item comment-scope "$HOOKS/hooks.json" present
else st_item comment-scope "$HOOKS/hooks.json" missing; fi ;;
else st_item comment-scope "$HOOKS/hooks.json" missing; fi
if [ -f "$HOOKS/hooks.json" ] && grep -qF 'lang-guard.sh' "$HOOKS/hooks.json" 2>/dev/null
then st_item lang-guard "$HOOKS/hooks.json" present
else st_item lang-guard "$HOOKS/hooks.json" missing; fi ;;
codex)
config="${CODEX_HOME:-$HOME/.codex}/config.toml"
@@ -729,22 +764,32 @@ if [ "$action" = status ]; then
else
st_item notify-sweep "$config" missing
fi
# 兩支 sweep 各算一項:只驗其中一支會讓「舊版只接了註解範圍」看起來像接線完整
if [ -f "$config" ] && grep -qF 'lang-guard.sh' "$config" 2>/dev/null &&
grep -qF 'sweep' "$config" 2>/dev/null; then
st_item notify-lang-sweep "$config" present
else
st_item notify-lang-sweep "$config" missing
fi
st_rc_alias alias jsc-hooks:codex
st_block ste100 "${CODEX_HOME:-$HOME/.codex}/AGENTS.md" "<!-- jsc-hooks -->"
st_comment_scope comment-scope "${CODEX_HOME:-$HOME/.codex}/AGENTS.md"
st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍改為每輪結束掃整個工作區,不是逐檔即時" ;;
st_lang_guard lang-guard "${CODEX_HOME:-$HOME/.codex}/AGENTS.md"
st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍與繁中編碼改為每輪結束掃整個工作區,不是逐檔即時" ;;
copilot)
st_rc_alias alias jsc-hooks:copilot
st_block ste100 "${JSC_COPILOT_INSTRUCTIONS:-$HOME/.config/copilot/copilot-instructions.md}" "<!-- jsc-hooks -->"
st_comment_scope comment-scope "${JSC_COPILOT_INSTRUCTIONS:-$HOME/.config/copilot/copilot-instructions.md}"
st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍只在工作階段結束時掃一次整個工作區" ;;
st_lang_guard lang-guard "${JSC_COPILOT_INSTRUCTIONS:-$HOME/.config/copilot/copilot-instructions.md}"
st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍與繁中編碼只在工作階段結束時掃一次整個工作區" ;;
antigravity)
st_rc_alias alias jsc-hooks:antigravity
st_block ste100 "${JSC_ANTIGRAVITY_RULES:-$HOME/.antigravity/AGENTS.md}" "<!-- jsc-hooks -->"
st_comment_scope comment-scope "${JSC_ANTIGRAVITY_RULES:-$HOME/.antigravity/AGENTS.md}"
st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍只在工作階段結束時掃一次整個工作區" ;;
st_lang_guard lang-guard "${JSC_ANTIGRAVITY_RULES:-$HOME/.antigravity/AGENTS.md}"
st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍與繁中編碼只在工作階段結束時掃一次整個工作區" ;;
kiro)
# kiro 的 hook 檔綁在工作區,這裡看的一律是目前工作目錄底下那一份
@@ -762,7 +807,16 @@ if [ "$action" = status ]; then
grep -qF 'comment-scope.sh\" sweep' ./.kiro/hooks/jsc-hooks.json 2>/dev/null
then st_item comment-scope-sweep ./.kiro/hooks/jsc-hooks.json present
else st_item comment-scope-sweep ./.kiro/hooks/jsc-hooks.json missing; fi
st_degrade="SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍改為每輪提示送出時掃整個工作區,不是逐檔即時" ;;
# lang-guard.sh 的兩個模式同理各算一項
if [ -f ./.kiro/hooks/jsc-hooks.json ] &&
grep -qF 'lang-guard.sh\" prompt' ./.kiro/hooks/jsc-hooks.json 2>/dev/null
then st_item lang-guard ./.kiro/hooks/jsc-hooks.json present
else st_item lang-guard ./.kiro/hooks/jsc-hooks.json missing; fi
if [ -f ./.kiro/hooks/jsc-hooks.json ] &&
grep -qF 'lang-guard.sh\" sweep' ./.kiro/hooks/jsc-hooks.json 2>/dev/null
then st_item lang-guard-sweep ./.kiro/hooks/jsc-hooks.json present
else st_item lang-guard-sweep ./.kiro/hooks/jsc-hooks.json missing; fi
st_degrade="SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍與繁中編碼改為每輪提示送出時掃整個工作區,不是逐檔即時" ;;
esac
if [ "$st_missing" -gt 0 ]; then
@@ -773,7 +827,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
@@ -784,9 +838,11 @@ case "$cli" in
[ -f "$HOOKS/hooks.json" ] || fail "找不到 $HOOKS/hooks.json,claude 接不到任何 hook"
grep -qF 'comment-scope.sh' "$HOOKS/hooks.json" 2>/dev/null \
|| fail "$HOOKS/hooks.json 沒有接上 comment-scope.sh,註解範圍檢查不會生效"
grep -qF 'lang-guard.sh' "$HOOKS/hooks.json" 2>/dev/null \
|| fail "$HOOKS/hooks.json 沒有接上 lang-guard.sh,繁中與編碼檢查不會生效"
printf 'status=wired reason=%s\n' "hooks.json 自動接線"
echo "[jsc] claude:由 hooks/hooks.json 自動接線全部六支 hook,無需寫入設定。"
echo "[jsc] claude:只有 claude 有 post-tool hook,comment-scope.sh 的逐檔即時掃描只在這裡接得上;其他四個 CLI 改用 sweep 掃整個工作區,時機晚一輪或晚到工作階段結束。"
echo "[jsc] claude:由 hooks/hooks.json 自動接線全部七支 hook,無需寫入設定。"
echo "[jsc] claude:只有 claude 有 post-tool hook,comment-scope.sh 與 lang-guard.sh 的逐檔即時掃描只在這裡接得上;其他四個 CLI 改用 sweep 掃整個工作區,時機晚一輪或晚到工作階段結束。"
exit 0 ;;
codex)
@@ -805,7 +861,8 @@ case "$cli" in
# 寫過的檔一個都不會漏。
timer="sh '$HOOKS/session-timer.sh'"
scope="sh '$HOOKS/comment-scope.sh'"
notify_line="notify = [\"env\", \"JSC_CLI=codex\", \"sh\", \"-c\", \"$timer start </dev/null; $timer mark </dev/null; $scope sweep </dev/null\"]"
lang="sh '$HOOKS/lang-guard.sh'"
notify_line="notify = [\"env\", \"JSC_CLI=codex\", \"sh\", \"-c\", \"$timer start </dev/null; $timer mark </dev/null; $scope sweep </dev/null; $lang sweep </dev/null\"]"
alias_line="alias $bin='sh \"$HERE/jsc-wrap.sh\" codex'"
replace_block_toml "$config" "# jsc-hooks" "# /jsc-hooks" "$notify_line" \
|| fail "無法寫入 $config"
@@ -814,19 +871,22 @@ case "$cli" in
|| fail "$config 的 notify 沒有落在根層(被歸進某張表,codex 讀不到,hook 會靜靜失效)"
grep -qF "$scope sweep" "$config" 2>/dev/null \
|| fail "$config 的 notify 沒有接到 comment-scope.sh sweep,codex 每輪結束不會掃註解範圍"
grep -qF "$lang sweep" "$config" 2>/dev/null \
|| fail "$config 的 notify 沒有接到 lang-guard.sh sweep,codex 每輪結束不會掃簡體字與亂碼"
write_alias_rc "jsc-hooks:codex" "$alias_line" || fail "無法把 $bin 別名寫進 shell rc 檔"
replace_block "$agents" "<!-- jsc-hooks -->" "<!-- /jsc-hooks -->" "$(rules_text)" \
|| fail "無法寫入 $agents"
has_block "$agents" "<!-- jsc-hooks -->" || fail "$agents 寫入後讀不到 jsc-hooks 標記段落"
has_comment_scope "$agents" || fail "$agents 寫入後讀不到註解範圍規則"
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍改為每輪結束掃整個工作區,不是逐檔即時"
has_lang_guard "$agents" || fail "$agents 寫入後讀不到繁中與編碼規則"
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍與繁中編碼改為每輪結束掃整個工作區,不是逐檔即時"
echo "[jsc] codex:已在 shell rc 加上 $bin 別名,轉呼叫 tools/jsc-wrap.sh codex,啟動當下開始計時。"
echo "[jsc] codex:別名要開新的 shell 或重新 source rc 檔才生效。"
echo "[jsc] codex:已設定 $config 的 notify(根層鍵,已驗證),每輪補 session-timer.sh start 再 mark,最後跑 comment-scope.sh sweep。"
echo "[jsc] codex:已在 $agents 寫入 STE100 與註解範圍規則段落(prompt 降級)。"
echo "[jsc] codex:已設定 $config 的 notify(根層鍵,已驗證),每輪補 session-timer.sh start 再 mark,最後跑 comment-scope.sh sweep 與 lang-guard.sh sweep。"
echo "[jsc] codex:已在 $agents 寫入 STE100、註解範圍與繁中編碼規則段落(prompt 降級)。"
echo "[jsc] codex:SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 sdlc-gate.sh lock 寫入。"
echo "[jsc] codex:版本前置檢查接不上(codex 沒有 pre-tool hook),改由 /jsc-cli:deploy 定期更新。"
echo "[jsc] codex:註解範圍除了規則提示,每輪結束會由 notify 掃一次整個 git 工作區(codex 沒有 post-tool hook,接不到逐檔即時掃描),回饋比 claude 晚一輪。"
echo "[jsc] codex:註解範圍與繁中編碼除了規則提示,每輪結束會由 notify 各掃一次整個 git 工作區(codex 沒有 post-tool hook,接不到逐檔即時掃描),回饋比 claude 晚一輪。"
exit 1 ;;
copilot)
@@ -839,13 +899,14 @@ case "$cli" in
|| fail "無法寫入 $instr"
has_block "$instr" "<!-- jsc-hooks -->" || fail "$instr 寫入後讀不到 jsc-hooks 標記段落"
has_comment_scope "$instr" || fail "$instr 寫入後讀不到註解範圍規則"
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍只在工作階段結束時掃一次整個工作區"
has_lang_guard "$instr" || fail "$instr 寫入後讀不到繁中與編碼規則"
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍與繁中編碼只在工作階段結束時掃一次整個工作區"
echo "[jsc] copilot:已在 shell rc 加上 $bin 別名,轉呼叫 tools/jsc-wrap.sh copilot。"
echo "[jsc] copilot:已在 $instr 寫入 STE100 與註解範圍規則段落(prompt 降級)。"
echo "[jsc] copilot:已在 $instr 寫入 STE100、註解範圍與繁中編碼規則段落(prompt 降級)。"
echo "[jsc] copilot:別名要開新的 shell 或重新 source rc 檔才生效。"
echo "[jsc] copilot:SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 sdlc-gate.sh lock 寫入。"
echo "[jsc] copilot:版本前置檢查接不上(copilot 沒有 pre-tool hook),改由 /jsc-cli:deploy 定期更新。"
echo "[jsc] copilot:註解範圍除了規則提示,工作階段結束時由 jsc-wrap.sh 收尾掃一次整個 git 工作區(copilot 連逐輪事件都沒有),回饋要等到離開 CLI 才看得到。"
echo "[jsc] copilot:註解範圍與繁中編碼除了規則提示,工作階段結束時由 jsc-wrap.sh 收尾各掃一次整個 git 工作區(copilot 連逐輪事件都沒有),回饋要等到離開 CLI 才看得到。"
exit 1 ;;
antigravity)
@@ -858,13 +919,14 @@ case "$cli" in
|| fail "無法寫入 $rules"
has_block "$rules" "<!-- jsc-hooks -->" || fail "$rules 寫入後讀不到 jsc-hooks 標記段落"
has_comment_scope "$rules" || fail "$rules 寫入後讀不到註解範圍規則"
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍只在工作階段結束時掃一次整個工作區"
has_lang_guard "$rules" || fail "$rules 寫入後讀不到繁中與編碼規則"
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍與繁中編碼只在工作階段結束時掃一次整個工作區"
echo "[jsc] antigravity:已在 shell rc 加上 $bin 別名,轉呼叫 tools/jsc-wrap.sh antigravity。"
echo "[jsc] antigravity:已在 $rules 寫入 STE100 與註解範圍規則段落(prompt 降級)。"
echo "[jsc] antigravity:已在 $rules 寫入 STE100、註解範圍與繁中編碼規則段落(prompt 降級)。"
echo "[jsc] antigravity:別名要開新的 shell 或重新 source rc 檔才生效。"
echo "[jsc] antigravity:SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 sdlc-gate.sh lock 寫入。"
echo "[jsc] antigravity:版本前置檢查接不上(antigravity 沒有 pre-tool hook),改由 /jsc-cli:deploy 定期更新。"
echo "[jsc] antigravity:註解範圍除了規則提示,工作階段結束時由 jsc-wrap.sh 收尾掃一次整個 git 工作區(antigravity 連逐輪事件都沒有),回饋要等到離開 CLI 才看得到。"
echo "[jsc] antigravity:註解範圍與繁中編碼除了規則提示,工作階段結束時由 jsc-wrap.sh 收尾各掃一次整個 git 工作區(antigravity 連逐輪事件都沒有),回饋要等到離開 CLI 才看得到。"
exit 1 ;;
kiro)
@@ -885,16 +947,16 @@ case "$cli" in
"run": "sh \\"$HOOKS/session-timer.sh\\" restart </dev/null"
}
EOF
# userPromptSubmit 那一輪的 run 最後再掛 comment-scope.sh sweep:kiro 沒有 post-tool hook,
# 拿不到「剛剛寫了哪個檔」,只能改掃整個 git 工作區的 diff。掃到的是上一輪寫的檔——
# 提示送出時,上一輪的寫入早就落地了,時機合理。
# userPromptSubmit 那一輪的 run 最後再掛 comment-scope.sh sweep 與 lang-guard.sh sweep:
# kiro 沒有 post-tool hook,拿不到「剛剛寫了哪個檔」,只能改掃整個 git 工作區的 diff。
# 掃到的是上一輪寫的檔——提示送出時,上一輪的寫入早就落地了,時機合理。
cat > "$hookfile" 2>/dev/null <<EOF || fail "無法寫入 $hookfile"
{
"name": "jsc-hooks",
"description": "jsc session timer + STE100 guard + comment scope bridge (auto-generated by jsc-hooks:hooks-install, do not edit by hand)",
"description": "jsc session timer + STE100 guard + comment scope + language guard bridge (auto-generated by jsc-hooks:hooks-install, do not edit by hand)",
"on": ["sessionEnd", "userPromptSubmit"],
"env": { "JSC_CLI": "kiro" },
"run": "sh \\"$HOOKS/session-timer.sh\\" mark </dev/null; sh \\"$HOOKS/ste100-guard.sh\\" </dev/null; sh \\"$HOOKS/comment-scope.sh\\" prompt </dev/null; sh \\"$HOOKS/comment-scope.sh\\" sweep </dev/null"
"run": "sh \\"$HOOKS/session-timer.sh\\" mark </dev/null; sh \\"$HOOKS/ste100-guard.sh\\" </dev/null; sh \\"$HOOKS/comment-scope.sh\\" prompt </dev/null; sh \\"$HOOKS/comment-scope.sh\\" sweep </dev/null; sh \\"$HOOKS/lang-guard.sh\\" prompt </dev/null; sh \\"$HOOKS/lang-guard.sh\\" sweep </dev/null"
}
EOF
for f in "$startfile" "$hookfile"; do
@@ -911,10 +973,15 @@ EOF
|| fail "$hookfile 的 run 沒有接到 comment-scope.sh prompt,每輪不會注入註解範圍規則"
grep -qF 'comment-scope.sh\" sweep' "$hookfile" 2>/dev/null \
|| fail "$hookfile 的 run 沒有接到 comment-scope.sh sweep,kiro 每輪不會掃註解範圍"
printf 'status=degraded reason=%s\n' "SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查,註解範圍改為每輪提示送出時掃整個工作區,不是逐檔即時"
grep -qF 'lang-guard.sh' "$hookfile" 2>/dev/null || fail "$hookfile 的 run 沒有接到 lang-guard.sh"
grep -qF 'lang-guard.sh\" prompt' "$hookfile" 2>/dev/null \
|| 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 可接版本前置檢查,註解範圍與繁中編碼改為每輪提示送出時掃整個工作區,不是逐檔即時"
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:註解範圍除了規則提示,每輪提示送出時會掃一次整個 git 工作區(kiro 沒有 post-tool hook),掃到的是上一輪寫的檔。"
echo "[jsc] kiro:註解範圍與繁中編碼除了規則提示,每輪提示送出時會各掃一次整個 git 工作區(kiro 沒有 post-tool hook),掃到的是上一輪寫的檔。"
exit 1 ;;
esac