Files
cli/tools/check-requires.sh
T
jiantw83 e8bf4ddaaf fix(cli): 分開「模型不合格」與「腳本被叫錯」的結束碼
model-tags.sh 的 gate 原本用同一個結束碼表示兩件事:模型缺能力標籤,
以及這支腳本被叫錯。delegate 讀到用法錯誤時,會當成模型沒通過檢查,
默默把一個能用的模型丟掉。真正的錯在哪,永遠不會浮出來。現在用法錯誤
改走另一個碼,兩種「無法判定」也歸到同一個碼,呼叫端只要認碼就分得出
三種結果。model-config.sh 照同一套規則調整,兩支腳本的契約才一致。

check-requires.sh 在沒有 python3 的機器上,會直接讓 shell 回一個沒宣告過
的碼,呼叫端讀不到原因。現在先確認 python3 在不在,並印出擋下的理由,
manifest 相依檢查才是真的做得到的事。

delegate 的七個步驟原本沒有任何完成條件,引用了不存在的 plugin,也把
CLI 偵測與標籤篩選寫成文字敘述,可是這兩件事早就有腳本負責。挑模型那
一步需要模型 id,全篇卻沒有任何步驟產得出來。現在每一步都寫出完成條件,
資料一律取自腳本,模型夠不夠格由結束碼判定,不讓模型自評標籤。

setup 的環境變數重驗原本去讀目前這個 shell。可是寫進 rc 檔的值,要等新的
shell 起來才存在,所以重驗永遠回報「沒設到」,把修好的項目誤判成失敗。
現在只驗 rc 段落裡確實有那一行,環境層面交給下一次體檢。
2026-08-31 11:09:59 +08:00

135 lines
4.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env sh
# check-requires.sh — 更新單一 plugin 之前,檢查它宣告的 jsc.requires 最低版本。
# 用法:
# check-requires.sh {claude|codex|copilot|antigravity|kiro} {manifest}
# 輸出(單行,可供程式判讀):
# status=ok reason={沒有宣告相依版本|相依版本符合:...}
# status=blocked reason={缺哪一個 plugin、差哪一版,或 manifest 讀不到}
# 結束碼:0=通過(沒有宣告相依,或全部符合)
# 1=擋下(相依版本不符、缺相依 plugin、manifest 不存在或不是有效 JSON、缺 python3)
# 2=用法錯誤(參數個數不對,或 CLI 代號不在五個之內)
# deploy.sh update 在每個 domain 更新前呼叫一次;擋下就跳過該 domain,不更新到一半才失敗。
set -u
usage() {
echo "用法:check-requires.sh {claude|codex|copilot|antigravity|kiro} {manifest}" >&2
exit 2
}
[ "$#" -eq 2 ] || usage
CLI=$1
MANIFEST=$2
case "$CLI" in
claude|codex|copilot|antigravity|kiro) ;;
*) usage ;;
esac
[ -f "$MANIFEST" ] || {
printf 'status=blocked reason=找不到 manifest:%s\n' "$MANIFEST"
exit 1
}
command -v python3 >/dev/null 2>&1 || {
# 直接讓 shell 回 127 的話,呼叫端會看到一個沒宣告過的結束碼,也讀不到原因。
printf 'status=blocked reason=找不到 python3,無法解析 manifest:%s\n' "$MANIFEST"
exit 1
}
JSC_HOME_DIR="${JSC_HOME:-$HOME/.jsc}"
LOCAL_DIR="${JSC_LOCAL_PLUGINS:-$JSC_HOME_DIR/plugins}"
KIRO_SKILLS="${JSC_KIRO_SKILLS:-$HOME/.kiro/skills}"
python3 - "$CLI" "$MANIFEST" "$LOCAL_DIR" "$KIRO_SKILLS" <<'PY'
import glob
import json
import os
import re
import sys
cli, manifest, local_dir, kiro_skills = sys.argv[1:5]
try:
with open(manifest, encoding="utf-8") as fh:
data = json.load(fh)
except Exception as exc:
print(f"status=blocked reason=manifest 不是有效 JSON:{manifest}:{exc}")
sys.exit(1)
requires = ((data.get("jsc") or {}).get("requires") or {})
if not requires:
print("status=ok reason=沒有宣告相依版本")
sys.exit(0)
def version_tuple(value):
value = str(value or "").strip()
parts = value.split(".")
out = []
for part in parts[:3]:
match = re.match(r"^(\d+)", part)
out.append(int(match.group(1)) if match else 0)
while len(out) < 3:
out.append(0)
return tuple(out)
def current_version(plugin):
domain = plugin[4:] if plugin.startswith("jsc-") else plugin
candidates = []
patterns = []
if cli == "claude":
registry = os.path.expanduser("~/.claude/plugins/installed_plugins.json")
try:
with open(registry, encoding="utf-8") as fh:
entries = (json.load(fh).get("plugins") or {}).get(f"jsc-{domain}@jsc") or []
for entry in entries:
path = os.path.join(entry.get("installPath") or "", "plugin.json")
with open(path, encoding="utf-8") as fh:
version = json.load(fh).get("version", "")
if version:
return version, path
except Exception:
pass
patterns.append(os.path.expanduser(f"~/.claude/plugins/cache/jsc/jsc-{domain}/*/plugin.json"))
elif cli == "codex":
patterns.append(os.path.expanduser(f"~/.codex/plugins/cache/jsc/jsc-{domain}/*/plugin.json"))
elif cli == "copilot":
patterns.append(os.path.expanduser(f"~/.copilot/installed-plugins/jsc/jsc-{domain}/plugin.json"))
elif cli == "antigravity":
patterns.append(os.path.join(local_dir, domain, "plugin.json"))
patterns.append(os.path.expanduser(f"~/.antigravity/plugins/jsc-{domain}/plugin.json"))
elif cli == "kiro":
patterns.append(os.path.join(kiro_skills, f"jsc-{domain}", "plugin.json"))
for pattern in patterns:
for path in glob.glob(pattern):
try:
with open(path, encoding="utf-8") as fh:
version = json.load(fh).get("version", "")
except Exception:
continue
if version:
candidates.append((version_tuple(version), version, path))
if not candidates:
return "", ""
candidates.sort()
return candidates[-1][1], candidates[-1][2]
failures = []
for plugin, constraint in sorted(requires.items()):
required = str(constraint).strip()
minimum = required[2:].strip() if required.startswith(">=") else required
current, path = current_version(plugin)
if not current:
failures.append(f"{plugin} 需要 {required},目前未安裝")
continue
if version_tuple(current) < version_tuple(minimum):
failures.append(f"{plugin} 需要 {required},目前 {current}({path})")
if failures:
print("status=blocked reason=" + ";".join(failures))
sys.exit(1)
print("status=ok reason=相依版本符合:" + ", ".join(f"{k} {v}" for k, v in sorted(requires.items())))
sys.exit(0)
PY