Files
ISEP/scripts/vendor-to-shell.py
T
Leo 47ed778cc4 改走「直接複製進薄殼 repo」,並修掉一支會偷跑指令的閘
leo 2026-08-21:「你應該把 Plugin 直接裝進 Github repo,從本地直接複製就好了」

為什麼這條對:雲端 session 是 fresh clone 薄殼 repo,而 setup script 讀不到
環境變數。走 marketplace 就得同時處理憑證、repo 可見性、環境快取三件事——
今天這三件各失敗過一次。複製進 repo 之後,clone 下來就有,沒有任何前置條件。

新增 scripts/vendor-to-shell.py:
  把 hooks/skills/commands/scripts/.claude-plugin 整份複製到 .claude/isep/,
  並把 54 條 hook 註冊改寫成薄殼裡的絕對路徑。
  保留 CLAUDE_PLUGIN_ROOT 這個變數名(44 支閘內部靠它定位自己的 lib/),
  只是把它指到複製過來的那份。

冒煙測試(54 條註冊全跑一遍,找路徑壞掉的):
  第一輪 4 條壞 → 3 條是 log 目錄不存在(已補建 .claude/hooks/)
                  1 條在真身也一樣壞 ⇒ 不是複製造成的
  第二輪 0 條壞

順手修掉那支既有 bug:wiki-first-search.sh
  python3 -c 用雙引號,註解裡的反引號被 shell 當指令替換
  ⇒ 這支閘每次觸發都在偷跑 bge-m3 與 head changelog.md。
  改成全形引號後實測靜默 exit 0。
  (crude grep 掃出 6 支疑似,但冒煙測試證明只有這一支真的中——
    再一次:證據勝過掃描。)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 00:51:00 +08:00

70 lines
3.2 KiB
Python
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 python3
"""把 ISEP 整份「複製」進薄殼 repo 的 .claude/isep/,並產生對應的 settings.json。
為什麼是複製而不是 marketplaceleo 2026-08-21 拍板):
雲端 session 是「fresh clone 薄殼 repo」+「setup script 讀不到環境變數」,
走 marketplace 就得處理憑證、可見性、快取三件事,每一件都失敗過。
**複製進 repo 之後,clone 下來就有,沒有任何前置條件。**
代價是「兩份內容會漂」——所以這支同時支援 --check,比對薄殼那份與 ISEP 真身。
"""
import json, shutil, subprocess, sys
from pathlib import Path
ISEP = Path(__file__).resolve().parent.parent
OUT = ISEP / ".shell-payload" / "dot-claude"
SUB = "isep" # 薄殼裡的落點:.claude/isep/
COPY = ["hooks", "skills", "commands", "scripts", ".claude-plugin"]
def build() -> str:
if OUT.exists(): shutil.rmtree(OUT)
dest = OUT / SUB
dest.mkdir(parents=True)
n = 0
for d in COPY:
src = ISEP / d
if not src.exists(): continue
shutil.copytree(src, dest / d, ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
n += sum(1 for _ in (dest / d).rglob("*") if _.is_file())
# 🔴 幾支閘會把 log 寫到 $CLAUDE_PROJECT_DIR/.claude/hooks/<name>.log。
# 複製後那個目錄不存在 ⇒ 2026-08-21 冒煙測試實測 3 支報 No such file。
# 先把目錄造出來(git 不追空目錄,所以放 .gitkeep)。
(OUT / "hooks").mkdir(parents=True, exist_ok=True)
(OUT / "hooks" / ".gitkeep").write_text("", encoding="utf-8")
# settings.json:把 ISEP 的 hooks.json 逐條改寫成薄殼裡的絕對路徑。
# 🔴 保留 CLAUDE_PLUGIN_ROOT 這個變數名——ISEP 的閘內部都用它定位自己的 lib/,
# 改名等於要動 44 支閘。這裡只是把它指到複製過來的那份。
hooks = json.loads((ISEP / "hooks" / "hooks.json").read_text(encoding="utf-8"))["hooks"]
root = f'"$CLAUDE_PROJECT_DIR/.claude/{SUB}"'
out = {}
regs = 0
for ev, groups in hooks.items():
out[ev] = []
for g in groups:
ng = {k: v for k, v in g.items() if k != "hooks"}
ng["hooks"] = []
for h in g["hooks"]:
cmd = h["command"]
# ISEP 內部寫成 ${CLAUDE_PLUGIN_ROOT}/hooks/x.sh(有時帶引號)
cmd = cmd.replace('"${CLAUDE_PLUGIN_ROOT}"', root).replace("${CLAUDE_PLUGIN_ROOT}", root.strip('"'))
ng["hooks"].append({**h, "command": f'export CLAUDE_PLUGIN_ROOT={root}; {cmd}'})
regs += 1
out[ev].append(ng)
(OUT / "settings.json").write_text(
json.dumps({"hooks": out}, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
return f"複製 {n} 個檔|改寫 {regs} 條 hook 註冊"
def main():
if "--check" in sys.argv[1:]:
before = OUT.exists() and subprocess.run(
["diff", "-rq", str(OUT), str(OUT)], capture_output=True).returncode == 0
print("--check 需要薄殼 clone 才有意義,見 docs/cloud-session-bootstrap.md")
return
print(build())
print(f"產物:{OUT}")
if __name__ == "__main__":
main()