fix(codex-hooks): 避免 Claude manifest 在 Codex 變成空路徑

This commit is contained in:
2026-08-28 18:07:20 +08:00
parent 9785af335a
commit 955a36adb9
6 changed files with 37 additions and 19 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc-hooks", "name": "jsc-hooks",
"version": "0.3.0", "version": "0.3.1",
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、註解範圍守門、繁中編碼守門、部署後強制重啟", "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、註解範圍守門、繁中編碼守門、部署後強制重啟",
"skills": "./skills", "skills": "./skills",
"author": { "author": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc-hooks", "name": "jsc-hooks",
"version": "0.3.0", "version": "0.3.1",
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、註解範圍守門、繁中編碼守門、部署後強制重啟", "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、註解範圍守門、繁中編碼守門、部署後強制重啟",
"skills": "./skills", "skills": "./skills",
"jsc": { "jsc": {
+14 -14
View File
@@ -5,7 +5,7 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/session-timer.sh\" start" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/session-timer.sh\" start'"
} }
] ]
} }
@@ -15,23 +15,23 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/ste100-guard.sh\"" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/ste100-guard.sh\"'"
}, },
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/sdlc-gate.sh\" check" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/sdlc-gate.sh\" check'"
}, },
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/sdlc-gate.sh\" wp-check prompt" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/sdlc-gate.sh\" wp-check prompt'"
}, },
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/comment-scope.sh\" prompt" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/comment-scope.sh\" prompt'"
}, },
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/lang-guard.sh\" prompt" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/lang-guard.sh\" prompt'"
} }
] ]
} }
@@ -41,7 +41,7 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/session-timer.sh\" mark" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/session-timer.sh\" mark'"
} }
] ]
} }
@@ -51,7 +51,7 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/session-timer.sh\" mark" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/session-timer.sh\" mark'"
} }
] ]
} }
@@ -62,15 +62,15 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/restart-gate.sh\"" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/restart-gate.sh\"'"
}, },
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/version-guard.sh\"" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/version-guard.sh\"'"
}, },
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/sdlc-gate.sh\" wp-check skill" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/sdlc-gate.sh\" wp-check skill'"
} }
] ]
} }
@@ -81,7 +81,7 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/skill-usage.sh\"" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/skill-usage.sh\"'"
} }
] ]
}, },
@@ -90,11 +90,11 @@
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/comment-scope.sh\"" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/comment-scope.sh\"'"
}, },
{ {
"type": "command", "type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/lang-guard.sh\"" "command": "sh -c 'root=\"${CLAUDE_PLUGIN_ROOT:-${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks}\"; exec sh \"$root/hooks/lang-guard.sh\"'"
} }
] ]
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc-hooks", "name": "jsc-hooks",
"version": "0.3.0", "version": "0.3.1",
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、註解範圍守門、繁中編碼守門、部署後強制重啟", "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查、註解範圍守門、繁中編碼守門、部署後強制重啟",
"skills": "./skills/", "skills": "./skills/",
"jsc": { "jsc": {
+2 -2
View File
@@ -9,7 +9,7 @@ Goal: make the eight hooks (`ste100-guard.sh`, `session-timer.sh`, `skill-usage.
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. 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. 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/...`。
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 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.
@@ -33,7 +33,7 @@ The detailed flow **MUST run as a sub agent**; the main agent only reports the s
## Steps ## 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. 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. 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. 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. 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. 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. 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.
+18
View File
@@ -188,6 +188,18 @@ has_lang_guard() {
grep -qF "$_first" "$1" 2>/dev/null grep -qF "$_first" "$1" 2>/dev/null
} }
codex_plugin_hooks_json() {
_base="${CODEX_HOME:-$HOME/.codex}/plugins/cache/jsc/jsc-hooks"
[ -d "$_base" ] || return 1
find "$_base" -path '*/hooks/hooks.json' -type f 2>/dev/null | sort | tail -n1
}
codex_plugin_hooks_safe() {
_hooks_json=$(codex_plugin_hooks_json || true)
[ -n "$_hooks_json" ] || return 0
! grep -qF 'command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/' "$_hooks_json" 2>/dev/null
}
# 以標記整段取代(冪等);標記不存在就在檔尾新增;檔案不存在就建立。 # 以標記整段取代(冪等);標記不存在就在檔尾新增;檔案不存在就建立。
# 適用 markdown 規則檔與 shell rc 檔:這兩種檔案沒有「區段」概念,附在檔尾就對了。 # 適用 markdown 規則檔與 shell rc 檔:這兩種檔案沒有「區段」概念,附在檔尾就對了。
# $1=檔案 $2=開頭標記行 $3=結尾標記行 $4=標記之間要寫入的內容 # $1=檔案 $2=開頭標記行 $3=結尾標記行 $4=標記之間要寫入的內容
@@ -986,6 +998,12 @@ if [ "$action" = status ]; then
st_lang_guard lang-guard "${CODEX_HOME:-$HOME/.codex}/AGENTS.md" st_lang_guard lang-guard "${CODEX_HOME:-$HOME/.codex}/AGENTS.md"
st_runtime_paths runtime-paths "$config" "# jsc-hooks" "# /jsc-hooks" st_runtime_paths runtime-paths "$config" "# jsc-hooks" "# /jsc-hooks"
st_runtime_paths alias-paths "$HOME/.bashrc" "# jsc-hooks:codex" "# /jsc-hooks:codex" st_runtime_paths alias-paths "$HOME/.bashrc" "# jsc-hooks:codex" "# /jsc-hooks:codex"
_codex_plugin_hooks=$(codex_plugin_hooks_json || true)
if codex_plugin_hooks_safe; then
st_item plugin-user-prompt-root "${_codex_plugin_hooks:-${CODEX_HOME:-$HOME/.codex}/plugins/cache/jsc/jsc-hooks}" present
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) copilot)