chore(marketplace 正本): 正本移至 plugins/meta(升版 0.0.6) #9

Merged
admin merged 5 commits from chore/marketplace-canon-to-meta into develop 2026-08-24 10:13:03 +00:00
15 changed files with 514 additions and 37 deletions
Showing only changes of commit d79a84ef26 - Show all commits
+24 -10
View File
@@ -1,75 +1,89 @@
{
"name": "jsc",
"description": "jsc 跨 AI 助理技能組的統一 marketplace(claude / codex / copilot / antigravity / kiro)。",
"owner": {
"name": "JSC"
},
"plugins": [
{
"name": "jsc-ask",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/ask.git"
}
},
"description": "決策樹問詢與問詢紀錄(QUESTION_* wiki 頁)"
},
{
"name": "jsc-cli",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/cli.git"
}
},
"description": "CLI 偵測、模型能力標籤與技能庫批次部署"
},
{
"name": "jsc-git",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/git.git"
}
},
"description": "Commit 分組認可與 Push Request 建立"
},
{
"name": "jsc-gitea",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/gitea.git"
}
},
"description": "Gitea API 工具、Wiki 讀寫與存取庫批次同步"
},
{
"name": "jsc-hooks",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/hooks.git"
}
},
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄"
},
{
"name": "jsc-log",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/log.git"
}
},
"description": "工作日誌(LOG_* wiki 頁)與技能使用統計"
},
{
"name": "jsc-meta",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/meta.git"
}
},
"description": "技能組自我管理:新建、更新、刪除技能與技能準則"
},
{
"name": "jsc-pkg",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/pkg.git"
}
},
"description": "套件批次更新(nodejs/python/dotnet),失敗還原"
},
{
"name": "jsc-review",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/review.git"
}
},
"description": "程式碼審查:Refactoring 壞味道六組 + 註解規範 + 淺模組"
},
{
"name": "jsc-sdlc",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/sdlc.git"
}
},
"description": "開發生命週期:規劃/分析/實作/維護(wiki 追蹤)"
}
]
}
+1 -1
View File
@@ -59,7 +59,7 @@
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/meta.git"
},
"description": "技能組自我管理:新建/更新/刪除技能與技能準則"
"description": "技能組自我管理:新建、更新、刪除技能與技能準則"
},
{
"name": "jsc-pkg",
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "jsc-hooks",
"version": "0.0.2",
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄",
"version": "0.0.5",
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖",
"skills": "./skills",
"author": {
"name": "JSC"
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-hooks",
"version": "0.0.2",
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄",
"version": "0.0.5",
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖",
"skills": "./skills"
}
+1 -1
View File
@@ -1,6 +1,6 @@
# jsc-hooks — 給 AI 助理的指引
本 repo 是 jsc 技能組的 `hooks` domain(跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄),可同時被 Claude Code / Codex / Copilot / Antigravity / Kiro 使用。
本 repo 是 jsc 技能組的 `hooks` domain(跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖),可同時被 Claude Code / Codex / Copilot / Antigravity / Kiro 使用。
## 規則
+13 -3
View File
@@ -23,8 +23,17 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/jsc.git),安
| `hooks/ste100-guard.sh` | UserPromptSubmit | 注入 STE100 繁體中文輸出規則(hook > prompt 強制層) |
| `hooks/session-timer.sh` | SessionStart / Stop / SessionEnd | 記錄工作階段起訖;`report` 子指令供 `jsc-log:worklog` 取花費時間 |
| `hooks/skill-usage.sh` | PostToolUse(Skill) | 記錄技能使用與呼叫鏈到 `$JSC_HOME/usage/*.jsonl`,供 `jsc-log:stats` 統計 |
| `hooks/sdlc-gate.sh` | UserPromptSubmit | SDLC 階段能力標籤閘門與模型鎖:`lock {stage}` 由 jsc-sdlc 階段技能呼叫,從 transcript 讀出實際模型 id 比對該階段必要標籤(`$JSC_HOME/model-tags.tsv`),不符就拒絕上鎖;`check` 在模型不符時以 exit 2 擋下該輪提示(其他 hook 一律 exit 0,此處是刻意例外);`unlock` 為逃生門 |
Claude 由 `hooks/hooks.json` 自動接線;其他 CLI 用 `hooks-install` 技能接線或降級為規則檔。
Claude 由 `hooks/hooks.json` 自動接線;其他 CLI 用 `hooks-install` 技能接線、改裝包裝啟動器,或降級為規則檔。
## 工具
| 腳本 | 用途 |
| --- | --- |
| `tools/jsc-wrap.sh` | 無 hook 系統 CLI 的包裝啟動器:匯出 `JSC_CLI`、`JSC_SESSION_ID`,前後接 `session-timer.sh`,結束時自動跑 `scan-logs.sh` 回填 |
| `tools/scan-logs.sh` | 離線回填:解析 copilot、antigravity、codex 的原生日誌,把技能用量與階段界線補進 `$JSC_HOME`,重掃不重複 |
| `tools/wire-cli.sh` | 單一 CLI 的接線流程:`{cli}` 對應的設定編輯、包裝別名安裝、hook 檔建立,皆以 `<!-- jsc-hooks -->`(或 `# jsc-hooks`)標記整段取代,重跑不重複;以 `status=wired\|degraded\|skipped` 回報結果 |
## 失敗回報範本
@@ -44,7 +53,7 @@ Claude 由 `hooks/hooks.json` 自動接線;其他 CLI 用 `hooks-install` 技
### `hooks-install`
把三支 hook 接線到所有已安裝的 CLI:偵測 CLI 後逐一套用原生 hook 設定,無 hook 系統的 CLI 降級為規則檔(以 `<!-- jsc-hooks -->` 標記整段取代,不重複追加)。
把四支 hook 接線到所有已安裝的 CLI:偵測 CLI 後,逐一呼叫 `tools/wire-cli.sh {cli}` 完成接線(claude 由 `hooks.json` 自動接線,無需寫入)。copilot、antigravity 由該腳本裝上 `tools/jsc-wrap.sh` 包裝別名補上計時與用量回填(結束時自動跑 `tools/scan-logs.sh`),語言規則仍追加到各自的規則檔(以 `<!-- jsc-hooks -->` 標記整段取代,不重複追加)。codex、kiro 的 SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 SDLC 技能直接呼叫 `sdlc-gate.sh lock` 寫入。腳本以 `status=wired|degraded|skipped` 回報結果,供技能對照 verify 表。
<!-- JSC-SKILLS:END -->
@@ -53,7 +62,8 @@ Claude 由 `hooks/hooks.json` 自動接線;其他 CLI 用 `hooks-install` 技
| 變數 | 用途 | 未設定時 |
| --- | --- | --- |
| `JSC_HOME` | Hook 資料目錄 | 預設 `~/.jsc` |
| `JSC_CLI` / `JSC_SESSION_ID` / `JSC_SKILL` | 非 Claude CLI 接線時由 wrapper 設定 | 安靜降級 |
| `JSC_CLI` / `JSC_SESSION_ID` / `JSC_SKILL` | 非 Claude CLI 接線時由 `tools/jsc-wrap.sh` 或接線設定提供 | 安靜降級 |
| `JSC_MODEL` | 非 Claude CLI 的目前模型,供 `sdlc-gate.sh` 比對;優先序在 transcript 實際值與 stdin `model` 之後 | 改讀 `~/.claude/settings.json`,再不行就安靜降級 |
## 相關 domain
+4 -1
View File
@@ -4,7 +4,10 @@
{ "hooks": [ { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/session-timer.sh\" start" } ] }
],
"UserPromptSubmit": [
{ "hooks": [ { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/ste100-guard.sh\"" } ] }
{ "hooks": [
{ "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/ste100-guard.sh\"" },
{ "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/sdlc-gate.sh\" check" }
] }
],
"Stop": [
{ "hooks": [ { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/session-timer.sh\" mark" } ] }
+17 -1
View File
@@ -1,7 +1,9 @@
#!/usr/bin/env sh
# lib.sh — jsc hooks 共用函式。所有 hook 腳本 source 此檔。
# 輸入相容:Claude 式 stdin JSON、或環境變數(codex/copilot/antigravity/kiro 接線時設定)。
# 缺資料時安靜降級,hook 永遠 exit 0,不可中斷宿主 CLI。
# 缺資料時安靜降級,hook 預設 exit 0,不可中斷宿主 CLI。
# 唯一例外:sdlc-gate.sh check 在「SDLC 階段鎖存在且模型不符」時會 exit 2 擋下該輪提示;
# 其餘情況(無鎖、資料不足無法判定)仍照舊 exit 0。
JSC_HOME="${JSC_HOME:-$HOME/.jsc}"
mkdir -p "$JSC_HOME/sessions" "$JSC_HOME/usage" 2>/dev/null || true
@@ -24,6 +26,20 @@ session_id() {
printf '%s' "$sid"
}
# 目前實際使用的模型 id:讀 transcript 最後一筆帶 model 的訊息。
# 這是 shell 唯一能「驗證」的模型來源——模型自我回報無法驗證,等同沒有閘門。
# 取不到(無 transcript_path、檔案不存在、尚無 assistant 訊息)時不輸出,由呼叫端決定如何降級。
transcript_model() {
tp=$(json_str transcript_path)
[ -n "$tp" ] && [ -f "$tp" ] || return 0
# 只掃尾端若干行即可命中最近一輪;`<...>` 這類佔位模型名(例如 <synthetic>)排除。
tail -n 500 "$tp" 2>/dev/null \
| grep -o '"model":"[^"]*"' \
| sed 's/^"model":"//; s/"$//' \
| grep -v '^<' \
| tail -n 1
}
# 目前 CLI 名稱:環境變數 > 依 stdin 特徵猜測 > unknown
cli_name() {
if [ -n "${JSC_CLI:-}" ]; then printf '%s' "$JSC_CLI"
+167
View File
@@ -0,0 +1,167 @@
#!/usr/bin/env sh
# sdlc-gate.sh — SDLC 階段模型閘門與 session 模型鎖(stage ∈ plan/analyze/implement/maintain)。
#
# 判準是階段的「能力標籤」,不是模型名稱:
# - 標籤資料讀 $JSC_HOME/model-tags.tsv,由 jsc-cli/tools/model-tags.sh sync 產生。
# - 目前模型 id 取自 transcript 記錄的實際值(lib.sh 的 transcript_model),
# 不採用模型自我回報——自我回報無法驗證,等同沒有閘門。
# - 鎖存的是「階段的必要標籤」,不是「上鎖那一刻的模型」。鎖當時的模型只留作記錄,
# 否則用不合格的模型起跑就會把自己鎖成合格,閘門永遠通過。
#
# 狀態檔:$JSC_HOME/sessions/{sid}.stage,單行「{stage}<TAB>{必要標籤}<TAB>{上鎖時的模型}」。
#
# 用法:
# sdlc-gate.sh lock {stage} 階段閘門:比對實際模型與該階段必要標籤,通過才上鎖。
# exit 0 = 通過並已上鎖;exit 1 = 未通過,呼叫端必須停止流程。
# sdlc-gate.sh unlock 移除狀態檔(被擋住又確定要放行時的逃生門)。
# sdlc-gate.sh check hook 模式(UserPromptSubmit):模型不符即擋下該輪提示。
# sdlc-gate.sh report 印出 {sid} {stage} {必要標籤} {上鎖時的模型};無鎖不印。
#
# exit code 例外:其他 jsc hook 一律 exit 0 不中斷宿主 CLI;本檔 check 是刻意的例外——
# 鎖存在且模型不符時 exit 2 擋下該輪提示。只用提示注入的話模型可以無視,閘門形同虛設。
# 無鎖、或資料不足無法判定時,仍照舊 exit 0 安靜降級。
HERE=$(dirname "$0"); . "$HERE/lib.sh"
read_stdin
sid=$(session_id)
state="$JSC_HOME/sessions/$sid.stage"
TAGS_TSV="$JSC_HOME/model-tags.tsv"
STAGES="plan analyze implement maintain"
# --- 標籤查詢(資料來源:model-tags.tsv) ---
# 某階段的必要標籤;不限標籤的階段回傳 any。查不到不輸出。
stage_tags() { # $1=階段
[ -s "$TAGS_TSV" ] || return 0
awk -F'\t' -v s="$1" '$1 == "stage" && $2 == s { print $3; exit }' "$TAGS_TSV"
}
# 某模型的能力標籤。模型鍵與實際 id 雙向包含即視為同一家族
#(例:表列 claude-haiku-4-5 對得上 claude-haiku-4-5-20251001);多筆命中取最長鍵。
model_tags() { # $1=模型 id
[ -s "$TAGS_TSV" ] || return 0
awk -F'\t' -v m="$1" '
$1 == "model" && (index(m, $2) > 0 || index($2, m) > 0) {
if (length($2) > best_len) { best_len = length($2); best = $3 }
}
END { if (best != "") print best }
' "$TAGS_TSV"
}
# 必要標籤中,該模型缺少的部分(逗號分隔);全部具備則不輸出。
missing_tags() { # $1=必要標籤 $2=模型標籤
awk -v req="$1" -v have="$2" '
BEGIN {
n = split(have, h, ","); for (i = 1; i <= n; i++) owned[h[i]] = 1
n = split(req, r, ",")
for (i = 1; i <= n; i++) if (!(r[i] in owned)) out = (out == "" ? r[i] : out "," r[i])
print out
}'
}
# 具備某組必要標籤的模型清單,用來告訴使用者可以切去哪個模型。
eligible_models() { # $1=必要標籤
[ -s "$TAGS_TSV" ] || return 0
awk -F'\t' -v req="$1" '
$1 == "model" {
split("", owned, ":")
n = split($3, h, ","); for (i = 1; i <= n; i++) owned[h[i]] = 1
n = split(req, r, ","); ok = 1
for (i = 1; i <= n; i++) if (!(r[i] in owned)) ok = 0
if (ok) out = (out == "" ? $2 : out "、" $2)
}
END { print out }
' "$TAGS_TSV"
}
# 目前模型:transcript 實際值 > stdin JSON model > JSC_MODEL > ~/.claude/settings.json。
# transcript 排最前面,因為那是唯一可驗證的實際值,其餘都只是宣告值。
current_model() {
m=$(transcript_model)
[ -n "$m" ] || m=$(json_str model)
[ -n "$m" ] || m="${JSC_MODEL:-}"
if [ -z "$m" ] && { [ -z "${JSC_CLI:-}" ] || [ "${JSC_CLI:-}" = "claude" ]; }; then
m=$(tr -d '\n' < "$HOME/.claude/settings.json" 2>/dev/null \
| sed -n 's/.*"model"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)
fi
printf '%s' "$m"
}
case "${1:-check}" in
lock)
stage="${2:-}"
case " $STAGES " in
*" $stage "*) ;;
*) echo "[jsc][SDLC 閘門][ERR]:階段須為 $STAGES 之一,收到「${stage:-空值}」。" >&2; exit 1 ;;
esac
req=$(stage_tags "$stage")
if [ -z "$req" ]; then
echo "[jsc][SDLC 閘門][ERR]:讀不到階段「$stage」的必要標籤($TAGS_TSV 不存在或缺該階段)。請先執行 jsc-cli/tools/model-tags.sh sync 再重跑本階段,本次不進行任何工作。" >&2
exit 1
fi
cur=$(current_model)
if [ -z "$cur" ]; then
echo "[jsc][SDLC 閘門][ERR]:判定不出目前實際使用的模型,無法驗證是否符合階段「$stage」。請確認 transcript 可讀,或設定 JSC_MODEL 後重跑,本次不進行任何工作。" >&2
exit 1
fi
if [ "$req" != "any" ]; then
have=$(model_tags "$cur")
if [ -z "$have" ]; then
echo "[jsc][SDLC 閘門][ERR]:模型「$cur」不在能力標籤表上,無法判定是否夠格跑階段「$stage」(需要 $req)。請把該模型補進 jsc-cli/references/model-tags.md 後執行 model-tags.sh sync,本次不進行任何工作。" >&2
exit 1
fi
miss=$(missing_tags "$req" "$have")
if [ -n "$miss" ]; then
ok=$(eligible_models "$req")
echo "[jsc][SDLC 閘門][ERR]:階段「$stage」需要標籤「$req」,模型「$cur」缺少「$miss」。請切換到下列任一模型後重跑:${ok:-(表上無合格模型,請補表)}。本次不進行任何工作。" >&2
exit 1
fi
fi
printf '%s\t%s\t%s\n' "$stage" "$req" "$cur" > "$state" 2>/dev/null || {
echo "[jsc][SDLC 閘門][ERR]:寫不進狀態檔 $state,階段鎖未生效。" >&2; exit 1; }
echo "[jsc][SDLC 閘門][OK]:階段「$stage」通過(需要 $req,目前模型 $cur),已上鎖。"
exit 0 ;;
unlock)
rm -f "$state" 2>/dev/null || true
exit 0 ;;
check)
[ -f "$state" ] || exit 0
stage=$(cut -f1 "$state" 2>/dev/null | head -n1)
req=$(cut -f2 "$state" 2>/dev/null | head -n1)
# 舊格式(空白分隔、無必要標籤欄)讀不出 req,安靜降級不擋。
[ -n "$stage" ] && [ -n "$req" ] && [ "$stage" != "$req" ] || exit 0
[ "$req" = "any" ] && exit 0
cur=$(current_model)
# 判定不出模型時只提醒,不擋——否則使用者會被鎖在無法送出提示的狀態。
if [ -z "$cur" ]; then
echo "[jsc] SDLC 階段「${stage}」需要標籤「${req}」,但判定不出目前模型。請自行確認模型是否合格。"
exit 0
fi
have=$(model_tags "$cur")
if [ -z "$have" ]; then
echo "[jsc][SDLC 閘門][ERR]:模型「${cur}」不在能力標籤表上,無法確認是否夠格跑階段「${stage}」(需要 ${req})。請補進 jsc-cli/references/model-tags.md 並執行 model-tags.sh sync;確定要放行請執行 jsc-hooks/hooks/sdlc-gate.sh unlock。本輪提示已擋下。" >&2
exit 2
fi
miss=$(missing_tags "$req" "$have")
[ -z "$miss" ] && exit 0
ok=$(eligible_models "$req")
echo "[jsc][SDLC 閘門][ERR]:SDLC 階段「${stage}」需要標籤「${req}」,目前模型「${cur}」缺少「${miss}」。請切換到下列任一模型後重送:${ok:-(表上無合格模型,請補表)}。要結束本階段的鎖請執行 jsc-hooks/hooks/sdlc-gate.sh unlock。本輪提示已擋下。" >&2
exit 2 ;;
report)
if [ -f "$state" ]; then
line=$(sed -n '1p' "$state" 2>/dev/null | tr '\t' ' ')
[ -n "$line" ] && echo "$sid $line"
fi
exit 0 ;;
esac
exit 0
+1
View File
@@ -4,4 +4,5 @@
# 其他 CLI: 由 hooks-install 以各自的規則檔(AGENTS.md 等)落地,本腳本仍可被 wrapper 呼叫。
# 完整規則的唯一來源:jsc-meta 的 references/ste100.md。
echo "[jsc] 輸出規則:STE100 繁體中文,擬人台灣感。短句、一句一指令、主動語態、術語一致;台灣用語(預設、支援、相容、資訊);全形標點;去 AI 味(不用「總的來說」「首先/其次/最後」開場收尾套路、不諂媚);直接講重點,UTF-8 無亂碼。完整規則見 jsc-meta 的 references/ste100.md。"
echo "[jsc] 送出前自我檢查,命中任一項就先改再送出:1)簡體字(例:应、为、这、说、后、发);2)句尾用半形標點(. , ! ?),應為全形(。,!?);3)套路句(「總的來說」「綜上所述」「首先…其次…最後」);4)中文並列用半形「/」,應改頓號「、」;5)諂媚開場(「好問題」「當然可以」)。"
exit 0
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-hooks",
"version": "0.0.2",
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄",
"version": "0.0.5",
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖",
"skills": "./skills/"
}
+16 -14
View File
@@ -1,30 +1,32 @@
---
name: hooks-install
description: Wire jsc hooks (STE100 guard, session timer, skill usage logger) into every installed AI CLI. Detect CLIs via jsc-cli detect-clis.sh, then apply native hook config per CLI or fall back to instruction files. 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) into every installed AI CLI. Detect CLIs via jsc-cli detect-clis.sh, then apply native hook config, the jsc-wrap.sh launcher, or an instruction-file fallback per CLI. 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 three hooks (`ste100-guard.sh`, `session-timer.sh`, `skill-usage.sh`) effective in every CLI.
Goal: make the four hooks (`ste100-guard.sh`, `session-timer.sh`, `skill-usage.sh`, `sdlc-gate.sh`) effective in every CLI.
Claude wiring is automatic via `hooks.json`. On codex and kiro the SDLC gate degrades to the skill-step check only; the lock file still works 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.
The detailed flow **MUST run as a sub agent**; the main agent only reports the summary.
## Steps
1. Run `jsc-cli/tools/detect-clis.sh` to get the installed CLIs and their paths.
2. Wire each CLI per the table below (hooks first; degrade to an instruction file when the CLI has no hook system):
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 {cli}`. The script performs the config edit, wrapper alias install, or hook file creation for that CLI, and replaces its `<!-- jsc-hooks -->` (or `# jsc-hooks`) marker block idempotently — reruns never duplicate content. Read its exit code and first output line (`status=wired|degraded|skipped reason=...`), then confirm against the table below:
| CLI | Wiring |
| --- | --- |
| claude | the plugin loads `jsc-hooks/hooks/hooks.json` automatically on install; verify only: `claude plugin list` shows `jsc-hooks` and `/hooks` shows the registrations |
| codex | set `notify` in `~/.codex/config.toml` to `session-timer.sh mark` (with `JSC_CLI=codex`); append the STE100 rule to `~/.codex/AGENTS.md` (prompt fallback) |
| copilot | no hook system: append the STE100 rule to `~/.config/copilot/copilot-instructions.md` (prompt fallback) |
| antigravity | no public hook system: append the STE100 rule to the global rules file (prompt fallback) |
| kiro | create an agent hook under the workspace `.kiro/hooks/` that runs the matching script (with `JSC_CLI=kiro`) |
| CLI | Exit / status | Verify |
| --- | --- | --- |
| claude | `status=wired` (exit 0) — hooks.json auto-wires everything, nothing to write | `claude plugin list` shows `jsc-hooks` and `/hooks` shows the registrations |
| codex | `status=degraded` (exit 1) — notify + AGENTS.md prompt fallback | `~/.codex/config.toml` contains the `notify` entry and `AGENTS.md` contains the block |
| copilot | `status=wired` (exit 0) when the CLI is detected, `status=skipped` (exit 3) otherwise | the alias resolves to `jsc-wrap.sh copilot` and `copilot-instructions.md` contains the block |
| antigravity | `status=wired` (exit 0) when the CLI is detected, `status=skipped` (exit 3) otherwise | the alias resolves to `jsc-wrap.sh antigravity` and the rules file contains the block |
| kiro | `status=degraded` (exit 1) | the hook file exists under `.kiro/hooks/` and names the script |
3. The appended rule text is always the exact output of `ste100-guard.sh` (a Traditional Chinese literal). Wrap it in `<!-- jsc-hooks -->` markers; on re-run, replace the whole block instead of appending again.
4. Report the wiring result for every CLI: succeeded, degraded to prompt, or the skip reason.
A row counts as done only when its verify check passes. Exit 2 means bad usage (wrong CLI name), not a wiring outcome.
3. Report the exact `status=` line `tools/wire-cli.sh` printed for each CLI; do not reinterpret or recompute the outcome by hand. Done when every detected CLI has exactly one reported status: wired, degraded, or skipped with a reason.
## Notes
- The hook scripts accept both stdin JSON and environment variables (`JSC_CLI`, `JSC_SESSION_ID`, `JSC_SKILL`); a wrapper only needs to set the variables.
- The hook scripts accept both stdin JSON and environment variables (`JSC_CLI`, `JSC_SESSION_ID`, `JSC_SKILL`, `JSC_MODEL`); `jsc-wrap.sh` sets the first two itself.
- Data lands in `$JSC_HOME` (default `~/.jsc`), consumed by `jsc-log:worklog` and `jsc-log:stats`.
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env sh
# jsc-wrap.sh — 無 hook 系統 CLI 的包裝啟動器(例:copilot、antigravity)。
# 用法: jsc-wrap.sh {cli} [args...]
# 行為: 匯出 JSC_CLI 與 JSC_SESSION_ID → session-timer start → 執行 CLI →
# 結束後 session-timer mark 並以 scan-logs.sh 回填用量,最後回傳 CLI 的結束碼。
HERE=$(cd "$(dirname "$0")" && pwd)
HOOKS="$HERE/../hooks"
cli="${1:-}"
if [ -z "$cli" ]; then
echo "用法:jsc-wrap.sh {cli} [args...]" >&2
exit 2
fi
shift
JSC_CLI="$cli"
# 未提供 session id 就自動產生({cli}-時間戳-PID),讓計時與用量共用同一個 session
[ -n "${JSC_SESSION_ID:-}" ] || JSC_SESSION_ID="$cli-$(date +%Y%m%d%H%M%S)-$$"
export JSC_CLI JSC_SESSION_ID
sh "$HOOKS/session-timer.sh" start </dev/null
"$cli" "$@"
rc=$?
# 收尾:補記結束時間,並從原生日誌回填技能用量
sh "$HOOKS/session-timer.sh" mark </dev/null
sh "$HERE/scan-logs.sh" --cli "$cli" --session "$JSC_SESSION_ID" </dev/null || true
exit "$rc"
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env sh
# scan-logs.sh — 離線回填:解析各 CLI 的原生日誌,把可辨識的技能使用與工作階段界線
# 補進 $JSC_HOME/usage/skills.jsonl(與 skill-usage.sh 同一種紀錄格式)與 $JSC_HOME/sessions/。
# 用法: scan-logs.sh --cli {name} [--session {sid}]
# 支援: copilot(~/.config/copilot/)、antigravity(全域日誌目錄)、codex(~/.codex/sessions/、~/.codex/log/)。
# 去重: 以 $JSC_HOME/usage/scan-state/ 記住每個日誌檔已掃描的位元組數,重掃只讀新增段落。
HERE=$(cd "$(dirname "$0")" && pwd); . "$HERE/../hooks/lib.sh"
cli=""; sid=""
while [ $# -gt 0 ]; do
case "$1" in
--cli) cli="${2:-}"; shift; shift ;;
--session) sid="${2:-}"; shift; shift ;;
*) shift ;;
esac
done
if [ -z "$cli" ]; then
echo "用法:scan-logs.sh --cli {name} [--session {sid}]" >&2
exit 2
fi
STATE="$JSC_HOME/usage/scan-state"
mkdir -p "$STATE" 2>/dev/null || true
# 檔案修改時間(epoch),取不到就用現在
mtime_epoch() {
stat -c %Y "$1" 2>/dev/null || date -r "$1" +%s 2>/dev/null || now_epoch
}
# 回填工作階段界線:start 只補缺,end 取較新值(不覆蓋較新的即時紀錄)
record_session() { # $1=sid $2=epoch
[ -f "$JSC_HOME/sessions/$1.start" ] || echo "$2" > "$JSC_HOME/sessions/$1.start"
old=$(cat "$JSC_HOME/sessions/$1.end" 2>/dev/null || echo 0)
if [ "$2" -gt "$old" ] 2>/dev/null; then echo "$2" > "$JSC_HOME/sessions/$1.end"; fi
}
# 印出日誌檔自上次掃描後的新增內容,並更新位移(位移即去重機制)
new_content() { # $1=file
key=$(printf '%s' "$1" | cksum | tr ' \t' '--')
off_f="$STATE/$cli-$key.offset"
off=$(cat "$off_f" 2>/dev/null || echo 0)
size=$(wc -c < "$1" 2>/dev/null || echo 0)
[ "$size" -gt "$off" ] 2>/dev/null || return 0
tail -c +"$((off + 1))" "$1" 2>/dev/null
echo "$size" > "$off_f"
}
# 從新增內容萃取 jsc 技能代號,逐筆追加(同一批先去重;格式與 skill-usage.sh 完全相同)
append_skills() { # stdin=內容 $1=sid
grep -oE 'jsc-[a-z]+:[a-z0-9][a-z0-9-]*' 2>/dev/null | sort -u | while IFS= read -r sk; do
printf '{"ts":"%s","cli":"%s","session":"%s","skill":"%s"}\n' \
"$(now_iso)" "$cli" "$1" "$sk" >> "$JSC_HOME/usage/skills.jsonl"
done
}
# 掃描一個日誌目錄;$2=預設 sid(空字串時用檔名當 sid);$3=sessions 時回填階段界線
scan_dir() {
find "$1" -maxdepth 3 -type f \( -name '*.json' -o -name '*.jsonl' -o -name '*.log' \) 2>/dev/null \
| while IFS= read -r f; do
s="$2"
[ -n "$s" ] || s="$cli-$(basename "$f" | sed 's/\.[^.]*$//')"
[ "${3:-}" = "sessions" ] && record_session "$s" "$(mtime_epoch "$f")"
new_content "$f" | append_skills "$s"
done
return 0
}
case "$cli" in
copilot)
d="$HOME/.config/copilot"
if [ ! -d "$d" ]; then echo "[jsc] 找不到 copilot 日誌目錄($d),略過回填。"; exit 0; fi
[ -d "$d/history-session-state" ] && scan_dir "$d/history-session-state" "$sid" sessions
[ -d "$d/history" ] && scan_dir "$d/history" "$sid" sessions
[ -d "$d/logs" ] && scan_dir "$d/logs" "$sid" logs ;;
antigravity)
found=""
for d in "$HOME/.antigravity/logs" "$HOME/.config/antigravity/logs" "$HOME/.antigravity"; do
if [ -d "$d" ]; then found=1; scan_dir "$d" "$sid" sessions; break; fi
done
if [ -z "$found" ]; then echo "[jsc] 找不到 antigravity 日誌目錄,略過回填。"; exit 0; fi ;;
codex)
d1="$HOME/.codex/sessions"; d2="$HOME/.codex/log"
if [ ! -d "$d1" ] && [ ! -d "$d2" ]; then
echo "[jsc] 找不到 codex 日誌目錄($d1、$d2),略過回填。"; exit 0
fi
[ -d "$d1" ] && scan_dir "$d1" "$sid" sessions
[ -d "$d2" ] && scan_dir "$d2" "$sid" logs ;;
*)
echo "[jsc] 尚未支援 $cli 的日誌回填,略過。" ;;
esac
exit 0
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env sh
# wire-cli.sh — 把 jsc 四支 hook 接線到單一 CLI(供 hooks-install 技能呼叫)。
# 用法: wire-cli.sh {claude|codex|copilot|antigravity|kiro}
# 行為(依 CLI 而定,皆為冪等:重跑只取代既有的 jsc-hooks 標記段落,不會重複疊加):
# claude — 什麼都不用寫,hooks.json 已自動接線四支 hook
# codex — 在 config.toml 設 notify(呼叫 session-timer.sh mark,JSC_CLI=codex);
# 在 AGENTS.md 附加 STE100 規則段落(prompt 降級)
# copilot — 在 shell rc 檔加上 copilot 別名,轉呼叫 tools/jsc-wrap.sh copilot;
# 在 copilot-instructions.md 附加 STE100 規則段落
# antigravity — 在 shell rc 檔加上 agy 別名,轉呼叫 tools/jsc-wrap.sh antigravity;
# 在全域規則檔附加 STE100 規則段落
# kiro — 在工作區 .kiro/hooks/ 下建立 jsc-hooks.json(JSC_CLI=kiro)
#
# 輸出: 第一行固定為 `status={wired|degraded|skipped} reason=...`(可供程式判讀),
# 其後為人類可讀的繁中說明。
# 結束碼: 0=wired(已完整接線) 1=degraded(降級為 prompt/技能步驟檢查)
# 2=用法錯誤 3=skipped(該 CLI 未偵測到執行檔,略過)
set -u
HERE=$(cd "$(dirname "$0")" && pwd)
ROOT=$(cd "$HERE/.." && pwd)
HOOKS="$ROOT/hooks"
cli="${1:-}"
case "$cli" in
claude|codex|copilot|antigravity|kiro) ;;
*)
echo "用法:wire-cli.sh {claude|codex|copilot|antigravity|kiro}" >&2
exit 2 ;;
esac
# 每個 CLI 對應的實際執行檔名稱(antigravity 的執行檔是 agy,其餘與 CLI 代號同名)
cli_bin() {
case "$1" in
antigravity) printf 'agy' ;;
kiro) printf 'kiro-cli' ;;
*) printf '%s' "$1" ;;
esac
}
# STE100 規則段落的唯一來源:ste100-guard.sh 的實際輸出
ste100_text() { sh "$HOOKS/ste100-guard.sh" 2>/dev/null | sed '/^exit /d'; }
# 以標記整段取代(冪等);標記不存在就在檔尾新增;檔案不存在就建立。
# $1=檔案 $2=開頭標記行 $3=結尾標記行 $4=標記之間要寫入的內容
replace_block() {
file="$1"; bopen="$2"; bshut="$3"; content="$4"
dir=$(dirname "$file")
mkdir -p "$dir" 2>/dev/null || return 1
touch "$file" 2>/dev/null || return 1
block=$(printf '%s\n%s\n%s' "$bopen" "$content" "$bshut")
if grep -qF "$bopen" "$file" 2>/dev/null; then
awk -v bopen="$bopen" -v bshut="$bshut" -v block="$block" '
$0==bopen { print block; skip=1; next }
$0==bshut { skip=0; next }
skip { next }
{ print }
' "$file" > "$file.jsc-tmp" 2>/dev/null && mv "$file.jsc-tmp" "$file"
else
printf '\n%s\n' "$block" >> "$file"
fi
}
# 找出可寫入別名的 shell rc 檔;都不存在就以 ~/.bashrc 為預設(自動建立)。
# 印出找到/建立的 rc 檔路徑,一行一個。
rc_files() {
found=""
for f in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.config/fish/config.fish"; do
[ -f "$f" ] && { printf '%s\n' "$f"; found=1; }
done
[ -n "$found" ] || printf '%s\n' "$HOME/.bashrc"
}
skip() { # $1=reason
printf 'status=skipped reason=%s\n' "$1"
echo "[jsc] 略過:$1"
exit 3
}
case "$cli" in
claude)
printf 'status=wired reason=%s\n' "hooks.json 自動接線"
echo "[jsc] claude:由 hooks/hooks.json 自動接線全部四支 hook,無需寫入設定。"
exit 0 ;;
codex)
command -v "$(cli_bin codex)" >/dev/null 2>&1 || skip "未偵測到 codex 執行檔"
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
config="$CODEX_HOME/config.toml"
agents="$CODEX_HOME/AGENTS.md"
notify_line="notify = [\"env\", \"JSC_CLI=codex\", \"sh\", \"$HOOKS/session-timer.sh\", \"mark\"]"
replace_block "$config" "# jsc-hooks" "# /jsc-hooks" "$notify_line" \
|| skip "無法寫入 $config"
replace_block "$agents" "<!-- jsc-hooks -->" "<!-- /jsc-hooks -->" "$(ste100_text)" \
|| skip "無法寫入 $agents"
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查"
echo "[jsc] codex:已設定 $config 的 notify 呼叫 session-timer.sh mark。"
echo "[jsc] codex:已在 $agents 寫入 STE100 規則段落(prompt 降級)。"
echo "[jsc] codex:SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 sdlc-gate.sh lock 寫入。"
exit 1 ;;
copilot)
bin=$(cli_bin copilot)
command -v "$bin" >/dev/null 2>&1 || skip "未偵測到 copilot 執行檔"
instr="${JSC_COPILOT_INSTRUCTIONS:-$HOME/.config/copilot/copilot-instructions.md}"
alias_line="alias $bin='sh \"$HERE/jsc-wrap.sh\" copilot'"
rc_files | while IFS= read -r rc; do
replace_block "$rc" "# jsc-hooks:copilot" "# /jsc-hooks:copilot" "$alias_line"
done
replace_block "$instr" "<!-- jsc-hooks -->" "<!-- /jsc-hooks -->" "$(ste100_text)" \
|| skip "無法寫入 $instr"
printf 'status=wired reason=%s\n' "wrapper 別名 + 規則檔已接線"
echo "[jsc] copilot:已在 shell rc 加上 $bin 別名,轉呼叫 tools/jsc-wrap.sh copilot。"
echo "[jsc] copilot:已在 $instr 寫入 STE100 規則段落。"
exit 0 ;;
antigravity)
bin=$(cli_bin antigravity)
command -v "$bin" >/dev/null 2>&1 || skip "未偵測到 antigravity(agy)執行檔"
rules="${JSC_ANTIGRAVITY_RULES:-$HOME/.antigravity/AGENTS.md}"
alias_line="alias $bin='sh \"$HERE/jsc-wrap.sh\" antigravity'"
rc_files | while IFS= read -r rc; do
replace_block "$rc" "# jsc-hooks:antigravity" "# /jsc-hooks:antigravity" "$alias_line"
done
replace_block "$rules" "<!-- jsc-hooks -->" "<!-- /jsc-hooks -->" "$(ste100_text)" \
|| skip "無法寫入 $rules"
printf 'status=wired reason=%s\n' "wrapper 別名 + 規則檔已接線"
echo "[jsc] antigravity:已在 shell rc 加上 $bin 別名,轉呼叫 tools/jsc-wrap.sh antigravity。"
echo "[jsc] antigravity:已在 $rules 寫入 STE100 規則段落。"
exit 0 ;;
kiro)
command -v "$(cli_bin kiro)" >/dev/null 2>&1 || skip "未偵測到 kiro-cli 執行檔"
hookdir="./.kiro/hooks"
hookfile="$hookdir/jsc-hooks.json"
mkdir -p "$hookdir" 2>/dev/null || skip "無法建立 $hookdir"
cat > "$hookfile" 2>/dev/null <<EOF || skip "無法寫入 $hookfile"
{
"name": "jsc-hooks",
"description": "jsc session timer + STE100 guard bridge (auto-generated by jsc-hooks:hooks-install, do not edit by hand)",
"on": ["sessionStart", "sessionEnd", "userPromptSubmit"],
"env": { "JSC_CLI": "kiro" },
"run": "sh \\"$HOOKS/session-timer.sh\\" mark; sh \\"$HOOKS/ste100-guard.sh\\""
}
EOF
printf 'status=degraded reason=%s\n' "SDLC 模型鎖降級為技能步驟檢查"
echo "[jsc] kiro:已建立 $hookfile(JSC_CLI=kiro)。"
echo "[jsc] kiro:SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 sdlc-gate.sh lock 寫入。"
exit 1 ;;
esac