diff --git a/CHANGELOG.md b/CHANGELOG.md index 717a648..76005ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/). +## [0.76.2] - 2026-08-31 + +### Fixed +- **新規プラグインの追加確認が、質問を表示しないまま入力待ちで止まる問題を修正**: 対話更新でカタログに追加されたプラグインを 1 件ずつ確認する `_offer_new_plugins_interactive` が、`read -r -p "..." reply < /dev/tty 2>/dev/null` の形で質問していた。`read -p` はプロンプトを stderr に書くため、端末を開けなかった場合に備えて付けていた `2>/dev/null` が質問文そのものを破棄していた。画面にはプラグイン一覧だけが出て入力待ちで停止するため停止しているように見え、先に進めるために押した Enter は空回答として `DISMISSED_PLUGINS` に記録される。既定は No で、一度断った項目は再提示されない設計のため、質問を一度も見ないまま該当プラグインが恒久的に拒否されていた。キットの他の対話プロンプト(`lib/deploy.sh` / `lib/merge.sh`)と同じ形に揃え、`printf ... >&2` で質問を出力してから `-p` なしの `read` を実行するようにした + - **既に拒否記録が残っている環境の復旧**: `~/.claude-starter-kit.conf` の `DISMISSED_PLUGINS` と `KNOWN_PLUGINS` の両方から該当エントリを削除すると、次回の対話更新で再度提示される。`_compute_new_plugins` は SELECTED / DISMISSED / KNOWN のいずれかに含まれる項目を除外するため、`DISMISSED_PLUGINS` だけを消しても再提示されない。Claude Code 内で `/plugin install @` を直接実行する方法も従来どおり使える +- **プラグイン一覧に `\033[1m` が制御文字列のまま表示される問題を修正**: 同じ関数がプラグイン名を `printf " %s%s%s%s\n" "${BOLD:-}" ...` と出力していた。`BOLD` / `NC` はエスケープシーケンスをバックスラッシュ表記の文字列として保持しており、printf が解釈するのは書式文字列に含まれる場合だけなので、`%s` の引数として渡すと画面にそのまま出る。色出力が有効な端末で発生する(`lib/colors.sh` は stdout が端末のときのみ色を設定するため、リダイレクト時は空文字列になり影響しない)。`lib/colors.sh` の他の出力と同じく書式文字列側に置くよう変更した +- 上記 2 点の回帰テストを `tests/unit/test-plugin-adoption.sh` に追加した。修正前のコードでは 2 件とも失敗する + ## [0.76.1] - 2026-08-31 ### Fixed diff --git a/lib/plugin-adoption.sh b/lib/plugin-adoption.sh index 9fb537a..fec617c 100644 --- a/lib/plugin-adoption.sh +++ b/lib/plugin-adoption.sh @@ -414,10 +414,21 @@ _offer_new_plugins_interactive() { [[ -n "$entry" ]] || continue local desc desc="$(_plugin_description "$entry")" - printf " %s%s%s%s\n" "${BOLD:-}" "$entry" "${NC:-}" "${desc:+ — $desc}" + # BOLD/NC carry escape sequences as literal backslash text, so they only + # render when printf interprets them in the FORMAT string. Passing them as + # %s arguments printed a raw "\033[1m" instead of bold. + # shellcheck disable=SC2059 # BOLD/NC are kit-owned escape constants + printf " ${BOLD:-}%s${NC:-}%s\n" "$entry" "${desc:+ — $desc}" + # Print the question separately: `read -p` writes its prompt to stderr, so + # the `2>/dev/null` that guards an unreadable terminal swallowed the prompt + # itself. The run then looked like a hang, and the Enter a user pressed to + # get past it was recorded as a permanent dismissal. Same shape as the + # kit's other post-wizard prompts (lib/deploy.sh, lib/merge.sh). # shellcheck disable=SC2059 # STR_* carries the %s placeholder - if ! read -r -p "$(printf "${STR_NEW_PLUGINS_ASK:-Add %s? [y/N]}" "$entry") " \ - reply < "${_TTY_INPUT:-/dev/tty}" 2>/dev/null; then + printf "${STR_NEW_PLUGINS_ASK:-Add %s? [y/N]} " "$entry" >&2 + reply="" + if ! read -r reply < "${_TTY_INPUT:-/dev/tty}" 2>/dev/null; then + printf "\n" >&2 return 2 fi case "$reply" in diff --git a/tests/unit/test-plugin-adoption.sh b/tests/unit/test-plugin-adoption.sh index 67520de..896a255 100644 --- a/tests/unit/test-plugin-adoption.sh +++ b/tests/unit/test-plugin-adoption.sh @@ -280,6 +280,42 @@ else fail "plugin-adoption: blank answer must not install (got '$_pa_out')" fi +# ── prompt visibility ────────────────────────────────────────────────────── +# +# The question is what makes an offer an offer. `read -p` writes its prompt to +# stderr, so the `2>/dev/null` that guards an unreadable terminal swallowed the +# prompt itself: the run looked like a hang, and the Enter someone pressed to +# get past it was recorded as a permanent dismissal. +_pa_out="$(_pa_run ' + _MERGE_INTERACTIVE="true" + printf "n\n" > "'"$_pa_tmp"'/answer-visible" + _TTY_INPUT="'"$_pa_tmp"'/answer-visible" + SELECTED_PLUGINS="alpha" + mkdir -p "'"$_pa_tmp"'/home7" + _detect_and_offer_new_plugins "'"$_pa_tmp"'/home7" 2>&1 >/dev/null')" +if [[ "$_pa_out" == *"gamma@other-mp"*"[y/N]"* ]]; then + pass "plugin-adoption: the offer prompt reaches the user" +else + fail "plugin-adoption: the [y/N] prompt must not be swallowed (got '$_pa_out')" +fi + +# BOLD/NC carry escape sequences as literal backslash text. printf renders them +# only from the format string; passed as %s arguments they print as raw +# "\033[1m" in the middle of the plugin list. +_pa_out="$(_pa_run ' + _MERGE_INTERACTIVE="true" + BOLD="\033[1m"; NC="\033[0m" + printf "n\n" > "'"$_pa_tmp"'/answer-bold" + _TTY_INPUT="'"$_pa_tmp"'/answer-bold" + SELECTED_PLUGINS="alpha" + mkdir -p "'"$_pa_tmp"'/home8" + _detect_and_offer_new_plugins "'"$_pa_tmp"'/home8" 2>/dev/null')" +if [[ "$_pa_out" == *"gamma@other-mp"* && "$_pa_out" != *'\033['* ]]; then + pass "plugin-adoption: the plugin line renders escapes instead of printing them" +else + fail "plugin-adoption: the plugin line must appear without literal BOLD/NC (got '$_pa_out')" +fi + # ── CSV validation ───────────────────────────────────────────────────────── # # These CSVs gate an install offer, so a corrupted or hand-edited value is