From 72bd87b3f07fb8d223fa8ed63e2047f5c9bd31a7 Mon Sep 17 00:00:00 2001 From: bastien Date: Thu, 16 Apr 2026 01:08:23 +0200 Subject: [PATCH] fix(shell): resolve all shellcheck warnings across scripts - SC2088: replace ~ with $HOME in quoted strings (doctor.sh) - SC2010/SC2012: replace ls|grep with compgen -G globs (detect-plugins.sh) - SC2034: remove unused PKG and RED variables (install-plugins.sh, update-all.sh) - SC2015: convert A&&B||C to proper if/then/else (update-all.sh, install-plugins.sh, session-start.sh) - SC1090: add shellcheck source directive (statusline.sh) - SC2129: group redirects into single block (install-plugins.sh) 0 warnings remaining (3 SC1091 info-level expected). Co-Authored-By: Claude --- doctor.sh | 12 ++--- hooks/session-start.sh | 2 +- hooks/statusline.sh | 1 + install-plugins.sh | 85 +++++++++++++++++++++++------- lib/detect-plugins.sh | 8 +-- update-all.sh | 116 ++++++++++++++++++++++++++++++----------- 6 files changed, 163 insertions(+), 61 deletions(-) diff --git a/doctor.sh b/doctor.sh index 6f7697c..b408ee8 100644 --- a/doctor.sh +++ b/doctor.sh @@ -38,7 +38,7 @@ check_symlink() { local target="$HOME/.claude/$name" if [ ! -e "$target" ] && [ ! -L "$target" ]; then - fail "~/.claude/$name — MISSING" + fail "$HOME/.claude/$name — MISSING" return fi @@ -47,12 +47,12 @@ check_symlink() { local real real=$(readlink -f "$target" 2>/dev/null) || real=$(readlink "$target") if [ ! -e "$real" ]; then - fail "~/.claude/$name → $real — BROKEN SYMLINK" + fail "$HOME/.claude/$name → $real — BROKEN SYMLINK" else - pass "~/.claude/$name"; _LINK_PASS=$((_LINK_PASS + 1)) + pass "$HOME/.claude/$name"; _LINK_PASS=$((_LINK_PASS + 1)) fi else - warn "~/.claude/$name exists but is NOT a symlink (expected symlink to repo)" + warn "$HOME/.claude/$name exists but is NOT a symlink (expected symlink to repo)" fi } @@ -88,7 +88,7 @@ if [ -L "$HOME/.claude/skills/gstack" ]; then if [ -d "$real" ]; then pass "Symlink OK → $real" # Check for skills/ subdirectory (referenced by plugin-advisor PHASE 1) - gstack_skills_count=$(ls "$HOME/.claude/skills/gstack/skills/" 2>/dev/null | wc -l | tr -d ' ') + gstack_skills_count=$(find "$HOME/.claude/skills/gstack/skills/" -maxdepth 1 -mindepth 1 2>/dev/null | wc -l | tr -d ' ') if [ "${gstack_skills_count:-0}" -gt 0 ]; then pass "GStack: ${gstack_skills_count} skills available" else @@ -213,7 +213,7 @@ print(len(d.get('permissions',{}).get('deny',[]))) fi fi else - fail "~/.claude/settings.json not found" + fail "$HOME/.claude/settings.json not found" fi echo "" diff --git a/hooks/session-start.sh b/hooks/session-start.sh index eb3e20c..cf907db 100644 --- a/hooks/session-start.sh +++ b/hooks/session-start.sh @@ -161,7 +161,7 @@ printf "│ 📦 v%-45s│\n" "$CONFIG_VERSION" # Version check: compare local vs remote (non-blocking) _remote_ver="" if [ -n "$REPO_DIR" ] && [ -d "$REPO_DIR/.git" ]; then - _remote_ver=$(cd "$REPO_DIR" 2>/dev/null && git fetch origin --quiet 2>/dev/null && git show origin/master:version.txt 2>/dev/null || true) + _remote_ver=$(cd "$REPO_DIR" 2>/dev/null && git fetch origin --quiet 2>/dev/null && git show origin/master:version.txt 2>/dev/null) || _remote_ver="" fi if [ -n "$_remote_ver" ] && [ "$_remote_ver" != "$CONFIG_VERSION" ]; then printf "│ 🔄 update available: v%-27s│\n" "$_remote_ver" diff --git a/hooks/statusline.sh b/hooks/statusline.sh index 1458655..f806a27 100755 --- a/hooks/statusline.sh +++ b/hooks/statusline.sh @@ -21,6 +21,7 @@ BRANCH_STR="${BRANCH:+ ($BRANCH)}" # Plan detection (reuse shared lib) _lib="$(dirname "${BASH_SOURCE[0]}")/../lib/detect-plugins.sh" if [ -f "$_lib" ]; then + # shellcheck source=../lib/detect-plugins.sh source "$_lib" PLAN=$(detect_plan 2>/dev/null || echo "pro") else diff --git a/install-plugins.sh b/install-plugins.sh index 122998e..a17d98c 100644 --- a/install-plugins.sh +++ b/install-plugins.sh @@ -50,15 +50,14 @@ print(v) # DETECT OS # ============================================================ OS="unknown" -PKG="" if [[ "$OSTYPE" == "darwin"* ]]; then OS="macos" elif command -v apt-get &>/dev/null; then - OS="linux-apt"; PKG="apt-get" + OS="linux-apt" elif command -v dnf &>/dev/null; then - OS="linux-dnf"; PKG="dnf" + OS="linux-dnf" elif command -v pacman &>/dev/null; then - OS="linux-pacman"; PKG="pacman" + OS="linux-pacman" fi echo "" @@ -120,7 +119,11 @@ if [ "$NODE_OK" = false ]; then ;; *) warn "Cannot auto-install Node.js on $OS — install from https://nodejs.org" ;; esac - command -v node &>/dev/null && ok "Node.js $(node --version)" || err "Node.js install failed" + if command -v node &>/dev/null; then + ok "Node.js $(node --version)" + else + err "Node.js install failed" + fi fi # --- Rust + Cargo (for RTK) --- @@ -162,6 +165,34 @@ else pipx ensurepath 2>/dev/null || true fi +# --- shellcheck --- +if command -v shellcheck &>/dev/null; then + ok "shellcheck $(shellcheck --version 2>/dev/null | grep '^version:' | awk '{print $2}')" +else + info "Installing shellcheck..." + case $OS in + macos) brew install shellcheck ;; + linux-apt) sudo apt-get install -y shellcheck ;; + linux-dnf) sudo dnf install -y shellcheck ;; + linux-pacman) sudo pacman -S --noconfirm shellcheck ;; + *) + # Binary fallback for systems without package manager access + ARCH=$(uname -m) + if curl -sL "https://github.com/koalaman/shellcheck/releases/download/v0.10.0/shellcheck-v0.10.0.linux.${ARCH}.tar.xz" | tar -xJ --strip-components=1 -C "$HOME/.local/bin" "shellcheck-v0.10.0/shellcheck" 2>/dev/null; then + chmod +x "$HOME/.local/bin/shellcheck" + ok "shellcheck installed (binary fallback)" + else + warn "Cannot auto-install shellcheck on $OS" + fi + ;; + esac + if command -v shellcheck &>/dev/null; then + ok "shellcheck installed" + else + warn "shellcheck install failed" + fi +fi + # --- Claude Code CLI --- if command -v claude &>/dev/null; then ok "Claude Code $(claude --version 2>/dev/null | head -1)" @@ -206,7 +237,11 @@ if [ -d "$GSTACK_DIR" ]; then curl -fsSL "https://bun.sh/install" -o "$tmpfile" BUN_VERSION="$BUN_VERSION" bash "$tmpfile" && rm -f "$tmpfile" export PATH="$HOME/.bun/bin:$PATH" - command -v bun &>/dev/null && ok "bun $(bun --version)" || err "bun install failed" + if command -v bun &>/dev/null; then + ok "bun $(bun --version)" + else + err "bun install failed" + fi else ok "bun $(bun --version)" fi @@ -280,8 +315,11 @@ else info "Installing gsd-pi@latest (consider pinning in plugins.lock.json)..." npm install -g gsd-pi fi - command -v gsd &>/dev/null && ok "GSD v2 installed ($(gsd --version 2>/dev/null | head -1))" \ - || err "GSD v2 install failed — check npm output above" + if command -v gsd &>/dev/null; then + ok "GSD v2 installed ($(gsd --version 2>/dev/null | head -1))" + else + err "GSD v2 install failed — check npm output above" + fi fi echo "" @@ -354,8 +392,11 @@ else info "Installing ctx7@latest (consider pinning in plugins.lock.json)..." npm install -g ctx7 fi - command -v ctx7 &>/dev/null && ok "ctx7 installed ($(ctx7 --version 2>/dev/null | head -1))" \ - || err "ctx7 install failed — run manually: npm install -g ctx7" + if command -v ctx7 &>/dev/null; then + ok "ctx7 installed ($(ctx7 --version 2>/dev/null | head -1))" + else + err "ctx7 install failed — run manually: npm install -g ctx7" + fi fi # Suggest setup for Claude Code integration (optional — ctx7 also works standalone) if command -v ctx7 &>/dev/null; then @@ -373,9 +414,11 @@ if command -v graphify &>/dev/null; then ok "graphify already installed" else info "Installing graphifyy via pipx..." - pipx install graphifyy 2>/dev/null \ - && ok "graphifyy installed" \ - || err "graphifyy install failed — run manually: pipx install graphifyy" + if pipx install graphifyy 2>/dev/null; then + ok "graphifyy installed" + else + err "graphifyy install failed — run manually: pipx install graphifyy" + fi fi if command -v graphify &>/dev/null; then info "Running graphify install (dependencies)..." @@ -398,9 +441,11 @@ if [ -f "$EMIL_DIR/SKILL.md" ]; then ok "emil-design-eng already downloaded" else info "Downloading SKILL.md from emilkowalski/skill..." - curl -fsSL "$EMIL_URL" -o "$EMIL_DIR/SKILL.md" \ - && ok "emil-design-eng installed" \ - || err "emil-design-eng download failed — try: curl -fsSL $EMIL_URL -o $EMIL_DIR/SKILL.md" + if curl -fsSL "$EMIL_URL" -o "$EMIL_DIR/SKILL.md"; then + ok "emil-design-eng installed" + else + err "emil-design-eng download failed — try: curl -fsSL $EMIL_URL -o $EMIL_DIR/SKILL.md" + fi fi # Symlink handled by link.sh if [ -L "$HOME/.claude/skills/emil-design-eng" ]; then @@ -444,9 +489,11 @@ for line in "${CLAUDE_LINES[@]}"; do if grep -qF "$line" "$SHELL_PROFILE" 2>/dev/null; then ok "$line (already in $SHELL_PROFILE)" else - echo "" >> "$SHELL_PROFILE" - echo "# Claude Code — added by install-plugins.sh" >> "$SHELL_PROFILE" - echo "$line" >> "$SHELL_PROFILE" + { + echo "" + echo "# Claude Code — added by install-plugins.sh" + echo "$line" + } >> "$SHELL_PROFILE" ok "$line → $SHELL_PROFILE" ADDED=1 fi diff --git a/lib/detect-plugins.sh b/lib/detect-plugins.sh index 95d59e9..48c6467 100644 --- a/lib/detect-plugins.sh +++ b/lib/detect-plugins.sh @@ -17,7 +17,7 @@ detect_superpowers() { # Fast check: filesystem (plugin cache) local cache_dir="$HOME/.claude/plugins/cache" if [ -d "$cache_dir" ]; then - ls "$cache_dir" 2>/dev/null | grep -qi "superpowers" && return 0 + compgen -G "$cache_dir"/*superpowers* &>/dev/null && return 0 fi # Slow fallback: CLI (only if fast check fails) claude plugin list 2>/dev/null | grep -qi "superpowers" && return 0 @@ -26,7 +26,7 @@ detect_superpowers() { detect_security_guidance() { local cache_dir="$HOME/.claude/plugins/cache" - [ -d "$cache_dir" ] && ls "$cache_dir" 2>/dev/null | grep -qi "security-guidance" + [ -d "$cache_dir" ] && compgen -G "$cache_dir"/*security-guidance* &>/dev/null } @@ -45,12 +45,12 @@ detect_gsd() { detect_plugin_dev() { # plugin-dev replaces the old "skill-creator" reference local cache_dir="$HOME/.claude/plugins/cache" - [ -d "$cache_dir" ] && ls "$cache_dir" 2>/dev/null | grep -qi "plugin-dev" + [ -d "$cache_dir" ] && compgen -G "$cache_dir"/*plugin-dev* &>/dev/null } detect_uiux_pro_max() { local cache_dir="$HOME/.claude/plugins/cache" - [ -d "$cache_dir" ] && ls "$cache_dir" 2>/dev/null | grep -qi "ui-ux-pro-max" + [ -d "$cache_dir" ] && compgen -G "$cache_dir"/*ui-ux-pro-max* &>/dev/null } detect_context7() { diff --git a/update-all.sh b/update-all.sh index 2580370..306c967 100644 --- a/update-all.sh +++ b/update-all.sh @@ -6,7 +6,7 @@ # ============================================================ set -euo pipefail -RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' +GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' ok() { echo -e "${GREEN}✓${NC} $1"; } warn() { echo -e "${YELLOW}⚠${NC} $1"; } info() { echo -e "${BLUE}→${NC} $1"; } @@ -97,15 +97,19 @@ print(d.get('rtk',{}).get('version','')) if [ -n "$RTK_VERSION" ] && [ "$RTK_VERSION" != "latest" ]; then info "Pinned version: $RTK_VERSION" info "Compiling from source — this may take a few minutes..." - cargo install --git https://github.com/rtk-ai/rtk --tag "$RTK_VERSION" --force \ - && ok "RTK updated to $RTK_VERSION" \ - || warn "RTK update failed" + if cargo install --git https://github.com/rtk-ai/rtk --tag "$RTK_VERSION" --force; then + ok "RTK updated to $RTK_VERSION" + else + warn "RTK update failed" + fi else info "No pinned version — installing latest" info "Compiling from source — this may take a few minutes..." - cargo install --git https://github.com/rtk-ai/rtk --force \ - && ok "RTK updated (latest)" \ - || warn "RTK update failed" + if cargo install --git https://github.com/rtk-ai/rtk --force; then + ok "RTK updated (latest)" + else + warn "RTK update failed" + fi fi else warn "Cargo not available — skipping RTK" @@ -127,14 +131,18 @@ print(d.get('gsd',{}).get('version','')) if [ -n "$GSD_VER" ] && [ "$GSD_VER" != "latest" ]; then info "Pinned version: $GSD_VER" - npm install -g "gsd-pi@${GSD_VER}" 2>/dev/null \ - && ok "GSD v2 updated to $GSD_VER" \ - || warn "GSD v2 update failed" + if npm install -g "gsd-pi@${GSD_VER}" 2>/dev/null; then + ok "GSD v2 updated to $GSD_VER" + else + warn "GSD v2 update failed" + fi else info "No pinned version — installing latest" - npm install -g gsd-pi 2>/dev/null \ - && ok "GSD v2 updated (latest)" \ - || warn "GSD v2 update failed" + if npm install -g gsd-pi 2>/dev/null; then + ok "GSD v2 updated (latest)" + else + warn "GSD v2 update failed" + fi fi else warn "GSD v2 not installed — skipping (run: npm install -g gsd-pi)" @@ -156,13 +164,17 @@ print(d.get('ctx7',{}).get('version','')) if [ -n "$CTX7_VER" ] && [ "$CTX7_VER" != "latest" ]; then info "Pinned version: $CTX7_VER" - npm install -g "ctx7@${CTX7_VER}" 2>/dev/null \ - && ok "ctx7 updated to $CTX7_VER" \ - || warn "ctx7 update failed" + if npm install -g "ctx7@${CTX7_VER}" 2>/dev/null; then + ok "ctx7 updated to $CTX7_VER" + else + warn "ctx7 update failed" + fi else - npm install -g ctx7@latest 2>/dev/null \ - && ok "ctx7 updated (latest)" \ - || warn "ctx7 update failed" + if npm install -g ctx7@latest 2>/dev/null; then + ok "ctx7 updated (latest)" + else + warn "ctx7 update failed" + fi fi else info "ctx7 not installed — skipping" @@ -172,9 +184,11 @@ fi echo "" echo "── Updating Graphifyy..." if command -v graphify &>/dev/null; then - pipx upgrade graphifyy 2>/dev/null \ - && ok "graphifyy updated" \ - || warn "graphifyy update failed — try: pipx upgrade graphifyy" + if pipx upgrade graphifyy 2>/dev/null; then + ok "graphifyy updated" + else + warn "graphifyy update failed — try: pipx upgrade graphifyy" + fi else info "graphifyy not installed — skipping" fi @@ -186,10 +200,12 @@ EMIL_DIR="$REPO/skills-external/emil-design-eng" EMIL_URL="https://raw.githubusercontent.com/emilkowalski/skill/main/skills/emil-design-eng/SKILL.md" if [ -d "$EMIL_DIR" ]; then info "Fetching latest SKILL.md from emilkowalski/skill..." - curl -fsSL "$EMIL_URL" -o "$EMIL_DIR/SKILL.md.tmp" \ - && mv "$EMIL_DIR/SKILL.md.tmp" "$EMIL_DIR/SKILL.md" \ - && ok "emil-design-eng updated" \ - || warn "emil-design-eng update failed" + if curl -fsSL "$EMIL_URL" -o "$EMIL_DIR/SKILL.md.tmp" \ + && mv "$EMIL_DIR/SKILL.md.tmp" "$EMIL_DIR/SKILL.md"; then + ok "emil-design-eng updated" + else + warn "emil-design-eng update failed" + fi else info "emil-design-eng not installed — skipping (run: make plugin)" fi @@ -204,9 +220,11 @@ if command -v claude &>/dev/null; then while IFS= read -r _p; do _name="${_p%%@*}" info "Updating $_name..." - claude plugin update "$_name" 2>/dev/null \ - && ok "$_name updated" \ - || warn "$_name update failed" + if claude plugin update "$_name" 2>/dev/null; then + ok "$_name updated" + else + warn "$_name update failed" + fi done <<< "$_plugins" else info "No marketplace plugins installed — skipping" @@ -215,11 +233,47 @@ else warn "Claude Code not found — skipping plugin update" fi -# ── 9. Refresh symlinks ── +# ── 9. Update shellcheck ── +echo "" +echo "── Updating shellcheck..." +if command -v shellcheck &>/dev/null; then + # Detect OS for package manager update + if [[ "$OSTYPE" == "darwin"* ]]; then + if brew upgrade shellcheck 2>/dev/null; then + ok "shellcheck updated" + else + ok "shellcheck already up to date" + fi + elif command -v apt-get &>/dev/null; then + if sudo apt-get install -y --only-upgrade shellcheck 2>/dev/null; then + ok "shellcheck updated" + else + ok "shellcheck already up to date" + fi + elif command -v dnf &>/dev/null; then + if sudo dnf upgrade -y shellcheck 2>/dev/null; then + ok "shellcheck updated" + else + ok "shellcheck already up to date" + fi + elif command -v pacman &>/dev/null; then + if sudo pacman -S --noconfirm shellcheck 2>/dev/null; then + ok "shellcheck updated" + else + ok "shellcheck already up to date" + fi + else + info "shellcheck installed via binary — update manually" + fi +else + info "shellcheck not installed — skipping (run: make plugin)" +fi + +# ── 10. Refresh symlinks ── echo "" echo "── Refreshing symlinks..." bash "$REPO/link.sh" -# ── 10. Run doctor ── +# ── 11. Run doctor ── echo "" bash "$REPO/doctor.sh"