Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions plugins/issue-driven-dev/scripts/check-plugin-presence.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# check-plugin-presence.sh — generic Claude Code plugin presence detector
#
# Usage:
# check-plugin-presence.sh <marketplace> <plugin-name>
#
# Examples:
# check-plugin-presence.sh claude-plugins-official ralph-loop
# check-plugin-presence.sh psychquant-claude-plugins che-word-mcp
#
# Used by:
# - idd-issue Step 1 Source Type Adapter (#27 fail-fast for .docx / Telegram / Mail / Notes)
# - scripts/check-ralph-loop.sh (wrapper for backward-compat from #28)
#
# Exit:
# 0 — plugin installed (or IDD_SKIP_PLUGIN_CHECK=1)
# 1 — plugin missing
# 2 — usage error (wrong arg count)
#
# Detect path is hardcoded against Claude Code 2025-Q4 plugin cache schema.
# When schema changes upstream, see #35 (path schema watch-list).

set -u

# Usage check
if [ $# -ne 2 ]; then
cat >&2 <<EOF
Usage: $(basename "$0") <marketplace> <plugin-name>

Examples:
$(basename "$0") claude-plugins-official ralph-loop
$(basename "$0") psychquant-claude-plugins che-word-mcp
EOF
exit 2
fi

MARKETPLACE="$1"
PLUGIN="$2"

# Escape hatch — let user bypass detect (#28 risk mitigation, #27 inheritance).
# Print stderr warning so this leaves an audit trail.
if [ "${IDD_SKIP_PLUGIN_CHECK:-}" = "1" ]; then
echo "⚠ IDD_SKIP_PLUGIN_CHECK=1 set — skipping ${MARKETPLACE}/${PLUGIN} detection (advanced override)" >&2
echo " Caller will run as if ${PLUGIN} is installed." >&2
exit 0
fi

# Plugin cache layout (Claude Code 2025-Q4):
# ~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/.claude-plugin/plugin.json
# Match any installed version under the plugin dir.
#
# Use bash array + nullglob (NOT unquoted `for f in $GLOB`) to handle HOME with
# whitespace correctly (per #28 F3 verify finding).
shopt -s nullglob
files=( "${HOME}/.claude/plugins/cache/${MARKETPLACE}/${PLUGIN}"/*/.claude-plugin/plugin.json )
shopt -u nullglob

if (( ${#files[@]} > 0 )); then
exit 0
fi

cat >&2 <<EOF
✗ ${PLUGIN} plugin not found.
Searched: ~/.claude/plugins/cache/${MARKETPLACE}/${PLUGIN}/*/.claude-plugin/plugin.json

Install:
claude plugin marketplace add <owner>/${MARKETPLACE}
claude plugin install ${PLUGIN}@${MARKETPLACE}

Or bypass this check (advanced): export IDD_SKIP_PLUGIN_CHECK=1
EOF
exit 1
58 changes: 14 additions & 44 deletions plugins/issue-driven-dev/scripts/check-ralph-loop.sh
Original file line number Diff line number Diff line change
@@ -1,52 +1,22 @@
#!/usr/bin/env bash
# check-ralph-loop.sh — detect ralph-loop plugin presence
# check-ralph-loop.sh — backward-compat wrapper around check-plugin-presence.sh
#
# Used by:
# - idd-verify --loop (Step 0a fail-fast)
# - idd-all Phase 0.6 (graceful degrade gate)
# v2.54+ (#34 refactor): the actual detection logic moved to
# scripts/check-plugin-presence.sh which accepts <marketplace> <plugin>.
# This wrapper is preserved so #28's existing callers continue to work:
# - idd-verify --loop Step 0a fail-fast
# - idd-all Phase 0.6 graceful degrade gate
#
# Exit:
# 0 — ralph-loop installed (or IDD_SKIP_RALPH_CHECK=1)
# 1 — ralph-loop missing
# Both `IDD_SKIP_RALPH_CHECK=1` and `IDD_SKIP_PLUGIN_CHECK=1` are honored
# (legacy + new env var name).
#
# Detect path is hardcoded against Claude Code 2025-Q4 plugin cache schema.
# When schema changes upstream, see #35 (path schema watch-list).
# Source plugin lives at anthropics/claude-plugins-official.
# Exit codes inherited from check-plugin-presence.sh:
# 0 — installed
# 1 — missing

set -u

# Escape hatch — let user bypass detect (#28 risk mitigation).
# Print stderr warning so this leaves an audit trail (per F4 verify finding).
# Honor legacy env var name from #28 v2.53.
if [ "${IDD_SKIP_RALPH_CHECK:-}" = "1" ]; then
echo "⚠ IDD_SKIP_RALPH_CHECK=1 set — skipping ralph-loop detection (advanced override)" >&2
echo " /idd-verify --loop and /idd-all (PR, unattended) will run as if ralph-loop is installed." >&2
exit 0
export IDD_SKIP_PLUGIN_CHECK=1
fi

# Plugin cache layout (Claude Code 2025-Q4):
# ~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/.claude-plugin/plugin.json
# Match any installed version under the plugin dir.
#
# F3 fix: use bash array + nullglob, NOT unquoted `for f in $GLOB`. The latter
# word-splits when HOME contains whitespace (macOS display-name accounts,
# iCloud Mobile Documents paths) → false-negative even when ralph-loop is
# installed → silently breaks v2.40.0 backward-compat for /idd-all callers.
shopt -s nullglob
files=( "${HOME}/.claude/plugins/cache/claude-plugins-official/ralph-loop"/*/.claude-plugin/plugin.json )
shopt -u nullglob

if (( ${#files[@]} > 0 )); then
exit 0
fi

cat >&2 <<EOF
✗ ralph-loop plugin not found.
Searched: ~/.claude/plugins/cache/claude-plugins-official/ralph-loop/*/.claude-plugin/plugin.json

Install:
claude plugin marketplace add anthropics/claude-plugins-official
claude plugin install ralph-loop@claude-plugins-official

Or bypass this check (advanced): export IDD_SKIP_RALPH_CHECK=1
EOF
exit 1
exec "$(dirname "$0")/check-plugin-presence.sh" claude-plugins-official ralph-loop
50 changes: 50 additions & 0 deletions plugins/issue-driven-dev/skills/idd-issue/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,56 @@ UPSTREAM=$(echo "$REPO_JSON" | jq -r '.parent.nameWithOwner // empty')
| 直接貼文字(無附件) | argument 直接帶文字 | n/a |
| 混合(文字 + 圖片貼上) | argument 帶文字 + 使用者另外提供 path 清單 | 把使用者給的 path 全部納入 Step 4 上傳清單 |

#### MCP plugin presence pre-flight (v2.54+, #27 fail-fast)

當 source 是 `.docx` / Telegram / Apple Mail / Apple Notes(任一需要 MCP plugin 的類型),Step 1 一開始就 invoke `check-plugin-presence.sh` detect — 缺失則 **fail-fast abort** with structured error message (per #32 absorbed acceptance criteria)。

```bash
# Source-type → required plugin mapping
case "$SOURCE_TYPE" in
docx|doc)
"$CLAUDE_PLUGIN_ROOT/scripts/check-plugin-presence.sh" psychquant-claude-plugins che-word-mcp || abort_source_unsupported "che-word-mcp" ".docx" "psychquant-claude-plugins" ;;
telegram)
"$CLAUDE_PLUGIN_ROOT/scripts/check-plugin-presence.sh" psychquant-claude-plugins che-telegram-mcp || abort_source_unsupported "che-telegram-mcp" "Telegram chat" "psychquant-claude-plugins" ;;
apple-mail)
"$CLAUDE_PLUGIN_ROOT/scripts/check-plugin-presence.sh" psychquant-claude-plugins che-apple-mail-mcp || abort_source_unsupported "che-apple-mail-mcp" "Apple Mail" "psychquant-claude-plugins" ;;
apple-notes)
"$CLAUDE_PLUGIN_ROOT/scripts/check-plugin-presence.sh" psychquant-claude-plugins che-apple-notes-mcp || abort_source_unsupported "che-apple-notes-mcp" "Apple Notes" "psychquant-claude-plugins" ;;
text|md|mixed)
: ;; # no plugin needed for raw text / markdown / pasted text+paths
esac
```

`abort_source_unsupported` 印出 structured error message(format spec from #32 absorbed acceptance criteria):

```
✗ Source detected as <SOURCE_DESC> but `<PLUGIN>` MCP plugin is not installed.

該 source type 需要對應 MCP plugin 才能讀取文字 + 抽圖。

Options:
1) Install plugin (recommended):
claude plugin marketplace add PsychQuant/<MARKETPLACE>
claude plugin install <PLUGIN>@<MARKETPLACE>
# 不知道 marketplace? 跑: claude plugin marketplace list
# 詳見: plugins/issue-driven-dev/README.md#optional-per-source-type

2) Convert to another supported format:
- Save as `.md` or `.txt` then paste content directly
- Or screenshot then attach as image (如果 主要內容是 figures)

3) Manual fallback (not recommended for archives):
paste relevant text into prompt directly

Aborting /idd-issue. Run again after installing or converting.
```

**Why fail-fast not silent fallback** (per #27 + #32 absorbed):
- Silent fallback (e.g. "讓使用者手動處理") 讓使用者誤以為 IDD **不支援**該格式,而非少裝 plugin
- Explicit error + 3 options 把責任 explicit 還給使用者,各 source-type 統一格式建立 mental model

**Bypass**: `IDD_SKIP_PLUGIN_CHECK=1` env var 跳過 detect(同 #34 generic helper escape hatch)。

#### Telegram source 專屬流程(最常見且最容易漏的)

當原始描述中含 `chat_id` / Telegram URL / `@username` 引用時,**強制**走以下流程,不問:
Expand Down