#!/usr/bin/env python3 """isep-doctor — 這台機器的 Claude Code 到底有沒有把 ISEP 載進來(inkstone/ISEP#150) 為什麼要有這支: plugin 載入失敗時,ISEP 的 59 支閘**一支都不會跑**,而且**沒有任何閘能講出來**—— 講話的 isep-presence-beacon 本身就在那個沒載入的 plugin 裡。 2026-09-05 起 `~/.claude/settings.json` 的 `extraKnownMarketplaces.inkstone.source` 多了一個 `path`(本機 checkout 路徑),Claude Code 把它當成 git 來源的抓取欄位 (debug log:「its network source differs from the one declared for it in settings (… path …)」),整個 marketplace 被忽略 ⇒ `isep@inkstone` 報 "Marketplace inkstone not found"。 CLI 與 Desktop 同病,當時被誤判成「Desktop 不跑 hook」。 判準(狀態,不是措辭): ① `claude plugin list --json` 裡 isep@inkstone 帶 errors ⇒ ❌ ② settings 對 inkstone 的宣告,比 known_marketplaces.json 記的多了抓取欄位 ⇒ 指出是哪一個、給修法 都沒有 ⇒ ✅,離開碼 0 離開碼:0 通|1 沒載入(附原因與一行修法)|2 查不了(找不到 claude 可執行檔等) 測試用環境變數:ISEP_DOCTOR_PLUGIN_LIST_JSON(直接給 plugin list 的輸出檔)、 ISEP_DOCTOR_CLAUDE_HOME(代替 ~/.claude)。迴歸測試:scripts/test-isep-doctor.sh """ import glob import json import os import shutil import subprocess import sys PLUGIN_ID = "isep@inkstone" MARKET = "inkstone" FETCH_FIELDS = ("path", "ref", "headers", "sparsePaths") home = os.environ.get("ISEP_DOCTOR_CLAUDE_HOME") or os.path.expanduser("~/.claude") def find_claude(): c = shutil.which("claude") if c: return c cands = sorted(glob.glob(os.path.expanduser( "~/Library/Application Support/Claude/claude-code/*/claude.app/Contents/MacOS/claude"))) return cands[-1] if cands else None def plugin_list(): f = os.environ.get("ISEP_DOCTOR_PLUGIN_LIST_JSON") if f: return json.load(open(f)) exe = find_claude() if not exe: return None out = subprocess.run([exe, "plugin", "list", "--json"], capture_output=True, text=True, timeout=60) return json.loads(out.stdout) def load(p): try: return json.load(open(p)) except Exception: return {} try: plugins = plugin_list() except Exception as e: # noqa: BLE001 print(f"◐ 查不了:claude plugin list 失敗({e})") sys.exit(2) if plugins is None: print("◐ 查不了:找不到 claude 可執行檔") sys.exit(2) entry = next((p for p in plugins if p.get("id") == PLUGIN_ID), None) declared = (load(os.path.join(home, "settings.json")).get("extraKnownMarketplaces") or {}).get(MARKET, {}).get("source") known = (load(os.path.join(home, "plugins", "known_marketplaces.json")).get(MARKET) or {}).get("source") extra = [] if isinstance(declared, dict): extra = [k for k in FETCH_FIELDS if k in declared and (not isinstance(known, dict) or declared.get(k) != known.get(k))] if entry is None: print(f"❌ {PLUGIN_ID} 沒有安裝(這台的閘全部不存在)") print(" 修法:claude plugin marketplace add https://git.uncle6.me/inkstone/ISEP.git && claude plugin install isep@inkstone") sys.exit(1) errors = entry.get("errors") or [] if not errors and not extra: print(f"✅ {PLUGIN_ID} {entry.get('version')} 已載入(enabled={entry.get('enabled')})") sys.exit(0) print(f"❌ {PLUGIN_ID} {entry.get('version')} 沒有載入——這台 Claude Code(CLI 與 Desktop 都一樣)一支 ISEP 閘都不會跑") for e in errors: print(f" Claude Code 回報:{e}") if extra: print(f" 原因:~/.claude/settings.json 的 extraKnownMarketplaces.{MARKET}.source 多了 {', '.join(extra)}," "Claude Code 把它當 git 抓取欄位,跟已安裝的 marketplace 對不上,整個 marketplace 被忽略") fields = ", ".join(repr(k) for k in extra) print(" 修法(一行,改完重開 Claude Desktop/CLI session):") print(f" python3 -c \"import json,os;p=os.path.expanduser('~/.claude/settings.json');d=json.load(open(p));" f"s=d['extraKnownMarketplaces']['{MARKET}']['source'];[s.pop(k,None) for k in ({fields},)];" "json.dump(d,open(p,'w'),ensure_ascii=False,indent=2)\"") sys.exit(1)