diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index ec0ecdb..adb53bd 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-hooks", - "version": "0.0.8", + "version": "0.0.9", "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖", "skills": "./skills", "author": { diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index e9fdf6b..6bcf27b 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-hooks", - "version": "0.0.8", + "version": "0.0.9", "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖", "skills": "./skills" } diff --git a/README.md b/README.md index f5fae02..ebd86ae 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,10 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安 Claude 由 `hooks/hooks.json` 自動接線;其他 CLI 用 `hooks-install` 技能接線、改裝包裝啟動器,或降級為規則檔。 +> `version-guard.sh report` 是非 hook 的子指令:印出每個已安裝 jsc plugin 的 +> 「{domain} {本機} {遠端} {落後|最新|超前|查詢失敗}」,最後一行 `behind {落後個數}`。 +> `jsc-cli:deploy` 用它決定要不要把「更新」設成推薦選項。 + ## 工具 | 腳本 | 用途 | diff --git a/hooks/version-guard.sh b/hooks/version-guard.sh index 9f5dccb..5685de2 100644 --- a/hooks/version-guard.sh +++ b/hooks/version-guard.sh @@ -23,7 +23,86 @@ # # 快取:$JSC_HOME/version-cache/{domain},單行「{版本} {epoch}」, # 預設 600 秒內不重查(JSC_VERSION_TTL 可調)。 +# +# 另有一個非 hook 的子指令: +# version-guard.sh report 把每個已安裝 jsc-* plugin 的版本比對印成 TSV,每行 +# 「{domain}{本機}{遠端}{落後|最新|超前|查詢失敗}」, +# 最後一行「behind{落後個數}」。供 jsc-cli:deploy 判斷要不要 +# 把「更新」設成推薦選項。report 只讀不擋,永遠 exit 0。 HERE=$(dirname "$0"); . "$HERE/lib.sh" + +# ── report:一次比對所有已安裝的 jsc plugin(非 hook 模式,不讀 stdin) +if [ "${1:-}" = "report" ]; then + python3 - <<'PY' +import json, os, re, subprocess, sys + +home = os.path.expanduser("~") +try: + reg = json.load(open(f"{home}/.claude/plugins/installed_plugins.json")).get("plugins", {}) +except Exception: + print("behind\t0"); sys.exit(0) + +host = owner = "" +try: + mk = json.load(open(f"{home}/.claude/plugins/known_marketplaces.json")) + m = re.match(r"(https?://[^/]+)/([^/]+)/[^/]+?(?:\.git)?/?$", + (mk.get("jsc", {}).get("source", {}) or {}).get("url", "")) + if m: + host, owner = m.group(1), m.group(2) +except Exception: + pass +if not host: + host = os.environ.get("GITEA_HOST", "") + owner = os.environ.get("JSC_GITEA_OWNER", "plugins") + + +def ver(v): + parts = ((v or "").split(".") + ["0", "0", "0"])[:3] + return tuple(int(p) if p.isdigit() else 0 for p in parts) + + +behind = 0 +for key in sorted(reg): + m = re.match(r"^jsc-([^@]+)@", key) + if not m: + continue + domain = m.group(1) + # 本機版本一律以 installPath 底下那份 plugin.json 為準(實際載入版本), + # 讀不到才退回註冊欄位。 + local = "" + for e in reg[key]: + try: + local = json.load(open(os.path.join(e.get("installPath", ""), "plugin.json"))).get("version", "") + break + except Exception: + local = e.get("version", "") + remote = "" + if host: + url = f"{host}/{owner}/{domain}/raw/branch/master/plugin.json" + for _ in range(2): # 暫時性網路失敗不該誤判成版本問題,失敗重試一次 + try: + out = subprocess.run(["curl", "-sS", "--max-time", "10", url], + capture_output=True, text=True, timeout=20).stdout + mm = re.search(r'"version"\s*:\s*"([^"]+)"', out) + if mm: + remote = mm.group(1) + break + except Exception: + pass + if not remote: + state = "查詢失敗" + elif ver(local) < ver(remote): + state = "落後"; behind += 1 + elif ver(local) > ver(remote): + state = "超前" + else: + state = "最新" + print(f"{domain}\t{local or '?'}\t{remote or '?'}\t{state}") +print(f"behind\t{behind}") +PY + exit 0 +fi + read_stdin [ "${JSC_VERSION_GUARD:-}" = "off" ] && exit 0 diff --git a/plugin.json b/plugin.json index 3490e7c..780ac3c 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-hooks", - "version": "0.0.8", + "version": "0.0.9", "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖", "skills": "./skills/" }