From 7bc35e791068f694f6ff80a396a64f03b7fb8304 Mon Sep 17 00:00:00 2001 From: Justin Lecher Date: Thu, 1 Oct 2026 08:16:44 +0100 Subject: [PATCH] chore(scripts): apply shfmt and fix shellcheck warnings in scripts/bash Fix the three SC2155 warnings by declaring and assigning separately, so command substitution failures are no longer masked. Add "# shellcheck source=/dev/null" above each "source common.sh" to clear SC1091. Format all six scripts with shfmt, with switch_case_indent enabled in .editorconfig so existing case indentation is preserved. Beyond the three SC2155 splits, the scripts are unchanged: minified shfmt output of before and after differs only in those lines. Refs #4805 Assisted-by: Claude Code (model: Claude Sonnet 5.5, autonomous) Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Justin Lecher --- .editorconfig | 1 + scripts/bash/check-prerequisites.sh | 22 +++++-- scripts/bash/common.sh | 93 +++++++++++++++++++---------- scripts/bash/create-new-feature.sh | 20 +++++-- scripts/bash/resolve-template.sh | 10 +++- scripts/bash/setup-plan.sh | 10 +++- scripts/bash/setup-tasks.sh | 20 +++++-- 7 files changed, 126 insertions(+), 50 deletions(-) diff --git a/.editorconfig b/.editorconfig index 81cb478a38..a64bb01bbb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -20,6 +20,7 @@ trim_trailing_whitespace = false [*.{sh,bash}] indent_size = 4 +switch_case_indent = true [*.{ps1,psm1,psd1}] indent_size = 4 diff --git a/scripts/bash/check-prerequisites.sh b/scripts/bash/check-prerequisites.sh index 7d6dba1353..9e9cdaf4c8 100644 --- a/scripts/bash/check-prerequisites.sh +++ b/scripts/bash/check-prerequisites.sh @@ -56,8 +56,8 @@ while [[ $# -gt 0 ]]; do fi TEMPLATE_NAME="$1" ;; - --help|-h) - cat << 'EOF' + --help | -h) + cat <<'EOF' Usage: check-prerequisites.sh [OPTIONS] Consolidated prerequisite checking for Spec-Driven Development workflow. @@ -94,15 +94,22 @@ done # Source common functions SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=/dev/null source "$SCRIPT_DIR/common.sh" # Get feature paths. # In --paths-only mode this is pure resolution, so pass --no-persist to opt out # of the feature.json write side effect (issue #3025). if $PATHS_ONLY; then - _paths_output=$(get_feature_paths --no-persist) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; } + _paths_output=$(get_feature_paths --no-persist) || { + echo "ERROR: Failed to resolve feature paths" >&2 + exit 1 + } else - _paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; } + _paths_output=$(get_feature_paths) || { + echo "ERROR: Failed to resolve feature paths" >&2 + exit 1 + } fi eval "$_paths_output" unset _paths_output @@ -183,7 +190,12 @@ fi TEMPLATE_CONTENT="" if [[ -n "$TEMPLATE_NAME" ]]; then - if TEMPLATE_CONTENT=$(resolve_template_content "$TEMPLATE_NAME" "$REPO_ROOT"; status=$?; printf x; exit "$status"); then + if TEMPLATE_CONTENT=$( + resolve_template_content "$TEMPLATE_NAME" "$REPO_ROOT" + status=$? + printf x + exit "$status" + ); then TEMPLATE_CONTENT="${TEMPLATE_CONTENT%x}" else echo "ERROR: Could not resolve required $TEMPLATE_NAME from the template override stack for $REPO_ROOT" >&2 diff --git a/scripts/bash/common.sh b/scripts/bash/common.sh index 7b1516d29c..8e6425a303 100644 --- a/scripts/bash/common.sh +++ b/scripts/bash/common.sh @@ -68,7 +68,8 @@ get_repo_root() { fi # Final fallback to script location - local script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + local script_dir + script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" (cd "$script_dir/../../.." && pwd) } @@ -95,7 +96,10 @@ get_current_branch() { read_feature_json_feature_directory() { local repo_root="$1" local fj="$repo_root/.specify/feature.json" - [[ -f "$fj" ]] || { printf '%s' ''; return 0; } + [[ -f "$fj" ]] || { + printf '%s' '' + return 0 + } # Try parsers in order (jq -> python3 -> grep/sed), falling through on # failure. Selection is by *parse success*, not mere availability: on @@ -119,9 +123,9 @@ read_feature_json_feature_directory() { if [[ -z "$_fd" ]]; then # Last-resort single-line grep/sed fallback. The `|| true` guards against # grep returning 1 (no match) aborting under `set -e` / `pipefail`. - _fd=$( { grep -E '"feature_directory"[[:space:]]*:' "$fj" 2>/dev/null || true; } \ - | head -n 1 \ - | sed -E 's/^[^:]*:[[:space:]]*"([^"]*)".*$/\1/' ) + _fd=$({ grep -E '"feature_directory"[[:space:]]*:' "$fj" 2>/dev/null || true; } | + head -n 1 | + sed -E 's/^[^:]*:[[:space:]]*"([^"]*)".*$/\1/') fi printf '%s' "$_fd" @@ -154,9 +158,9 @@ _persist_feature_json() { # Write feature.json — prefer jq for safe JSON, fall back to printf if command -v jq >/dev/null 2>&1; then - jq -cn --arg fd "$feature_dir_value" '{feature_directory:$fd}' > "$fj" + jq -cn --arg fd "$feature_dir_value" '{feature_directory:$fd}' >"$fj" else - printf '{"feature_directory":"%s"}\n' "$(json_escape "$feature_dir_value")" > "$fj" + printf '{"feature_directory":"%s"}\n' "$(json_escape "$feature_dir_value")" >"$fj" fi } @@ -266,14 +270,18 @@ get_invoke_separator() { local jq_separator if jq_separator=$(jq -r '(.default_integration // .integration // "") as $k | if $k == "" then "." else (.integration_settings[$k].invoke_separator // ".") end' "$integration_json" 2>/dev/null); then case "$jq_separator" in - "."|"-") separator="$jq_separator"; parsed=1 ;; + "." | "-") + separator="$jq_separator" + parsed=1 + ;; esac fi fi if [[ "$parsed" -eq 0 ]] && command -v python3 >/dev/null 2>&1; then local py_separator - if py_separator=$(python3 - "$integration_json" <<'PY' 2>/dev/null + if py_separator=$( + python3 - "$integration_json" <<'PY' 2>/dev/null import json import sys @@ -291,9 +299,12 @@ try: except Exception: sys.exit(1) PY -); then + ); then case "$py_separator" in - "."|"-") separator="$py_separator"; parsed=1 ;; + "." | "-") + separator="$py_separator" + parsed=1 + ;; esac fi fi @@ -344,7 +355,7 @@ PY } ' "$integration_json" 2>/dev/null) case "$awk_separator" in - "."|"-") separator="$awk_separator" ;; + "." | "-") separator="$awk_separator" ;; esac fi fi @@ -391,10 +402,10 @@ json_escape() { # so multi-byte UTF-8 sequences (first byte >= 0xC0) pass through intact. local LC_ALL=C local i char code - for (( i=0; i<${#s}; i++ )); do + for ((i = 0; i < ${#s}; i++)); do char="${s:$i:1}" printf -v code '%d' "'$char" 2>/dev/null || code=256 - if (( code >= 1 && code <= 31 )); then + if ((code >= 1 && code <= 31)); then printf '\\u%04x' "$code" else printf '%s' "$char" @@ -519,7 +530,7 @@ resolve_template() { local repo_root="$2" local base="$repo_root/.specify/templates" - case "$template_name" in ""|*[!a-z0-9-]*) return 1 ;; esac + case "$template_name" in "" | *[!a-z0-9-]*) return 1 ;; esac # Priority 1: Project overrides local override="$base/overrides/${template_name}.md" @@ -566,7 +577,7 @@ except Exception: [ -f "$candidate" ] && echo "$candidate" && return 0 candidate="$presets_dir/$preset_id/${template_name}.md" [ -f "$candidate" ] && echo "$candidate" && return 0 - done <<< "$sorted_presets" + done <<<"$sorted_presets" fi # python3 succeeded but registry has no presets — nothing to search else @@ -604,7 +615,7 @@ except Exception: local candidate="$ext/templates/${template_name}.md" [ -f "$candidate" ] || candidate="$ext/${template_name}.md" [ -f "$candidate" ] && echo "$candidate" && return 0 - done <<< "$sorted_extensions" + done <<<"$sorted_extensions" fi # Priority 4: Core templates @@ -628,7 +639,7 @@ resolve_template_content() { local repo_root="$2" local base="$repo_root/.specify/templates" - case "$template_name" in ""|*[!a-z0-9-]*) return 1 ;; esac + case "$template_name" in "" | *[!a-z0-9-]*) return 1 ;; esac # Collect all layers (highest priority first) local -a layer_paths=() @@ -776,7 +787,7 @@ except Exception as exc: fi if [ -n "$result" ]; then local declaration - IFS=$'\t' read -r declaration strategy manifest_file <<< "$result" + IFS=$'\t' read -r declaration strategy manifest_file <<<"$result" [ "$declaration" = "found" ] && manifest_declared=true strategy=$(printf '%s' "$strategy" | tr '[:upper:]' '[:lower:]') fi @@ -786,7 +797,7 @@ except Exception as exc: local candidate="" if [ -n "$manifest_file" ]; then case "$manifest_file" in - /*|*../*) manifest_file="" ;; + /* | *../*) manifest_file="" ;; esac fi if [ -n "$manifest_file" ]; then @@ -809,7 +820,7 @@ except Exception as exc: break fi fi - done <<< "$sorted_presets" + done <<<"$sorted_presets" fi fi @@ -831,7 +842,7 @@ except Exception as exc: effective_base_found=true break fi - done <<< "$sorted_extensions" + done <<<"$sorted_extensions" fi # Priority 4: Core templates (always "replace") @@ -872,7 +883,7 @@ except Exception as exc: # to find the nearest replace layer. Only compose layers above that base. local base_idx=-1 local i - for (( i=0; i&2 return 2 fi content="${content%x}" - for (( i=base_idx-1; i>=0; i-- )); do + for ((i = base_idx - 1; i >= 0; i--)); do local path="${layer_paths[$i]}" local strat="${layer_strategies[$i]}" local layer_content # Preserve trailing newlines - if ! layer_content=$(cat "$path"; status=$?; printf x; exit "$status"); then + if ! layer_content=$( + cat "$path" + status=$? + printf x + exit "$status" + ); then echo "Error: failed to read template layer $path" >&2 return 2 fi @@ -906,17 +927,26 @@ except Exception as exc: case "$strat" in replace) content="$layer_content" ;; prepend) - content=$(printf '%s\n\n%s' "$layer_content" "$content"; printf x) + content=$( + printf '%s\n\n%s' "$layer_content" "$content" + printf x + ) content="${content%x}" ;; append) - content=$(printf '%s\n\n%s' "$content" "$layer_content"; printf x) + content=$( + printf '%s\n\n%s' "$content" "$layer_content" + printf x + ) content="${content%x}" ;; wrap) case "$layer_content" in *'{CORE_TEMPLATE}'*) ;; - *) echo "Error: wrap strategy missing {CORE_TEMPLATE} placeholder" >&2; return 2 ;; + *) + echo "Error: wrap strategy missing {CORE_TEMPLATE} placeholder" >&2 + return 2 + ;; esac # Consume the wrapper left to right instead of rewriting it in # place. Rewriting re-scanned the string just modified, so base @@ -933,7 +963,10 @@ except Exception as exc: done content="${wrapped}${rest}" ;; - *) echo "Error: unknown strategy '$strat'" >&2; return 2 ;; + *) + echo "Error: unknown strategy '$strat'" >&2 + return 2 + ;; esac done diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index 1367014908..65aacffa78 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -56,7 +56,7 @@ while [ $i -le $# ]; do --timestamp) USE_TIMESTAMP=true ;; - --help|-h) + --help | -h) echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name ] [--number N] [--timestamp] " echo "" echo "Options:" @@ -166,7 +166,7 @@ fit_branch_name() { local branch_name="${feature_num}-${branch_suffix}" if [ ${#branch_name} -gt $MAX_BRANCH_LENGTH ]; then - local prefix_length=$(( ${#feature_num} + 1 )) + local prefix_length=$((${#feature_num} + 1)) local max_suffix_length=$((MAX_BRANCH_LENGTH - prefix_length)) local truncated_suffix truncated_suffix=$(printf '%s' "$branch_suffix" | cut -c "1-$max_suffix_length" | sed 's/-$//') @@ -191,6 +191,7 @@ shell_quote() { # Resolve repository root using common.sh functions which prioritize .specify SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=/dev/null source "$SCRIPT_DIR/common.sh" REPO_ROOT=$(get_repo_root) || exit 1 @@ -214,7 +215,8 @@ generate_branch_name() { # acronym probe below uses ASCII word boundaries like the Python twin's # (? "$SPEC_FILE" + printf '%s' "$SPEC_TEMPLATE_CONTENT" >"$SPEC_FILE" else echo "Warning: Spec template not found; created empty spec file" >&2 touch "$SPEC_FILE" diff --git a/scripts/bash/resolve-template.sh b/scripts/bash/resolve-template.sh index da05d2df6d..4fb6795348 100644 --- a/scripts/bash/resolve-template.sh +++ b/scripts/bash/resolve-template.sh @@ -3,6 +3,7 @@ set -e SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=/dev/null source "$SCRIPT_DIR/common.sh" JSON_MODE=false @@ -11,7 +12,7 @@ TEMPLATE_NAME="" for arg in "$@"; do case "$arg" in --json) JSON_MODE=true ;; - --help|-h) + --help | -h) echo "Usage: $0 [--json]" exit 0 ;; @@ -35,7 +36,12 @@ if [[ -z "$TEMPLATE_NAME" ]]; then fi REPO_ROOT=$(get_repo_root) -if TEMPLATE_CONTENT=$(resolve_template_content "$TEMPLATE_NAME" "$REPO_ROOT"; status=$?; printf x; exit "$status"); then +if TEMPLATE_CONTENT=$( + resolve_template_content "$TEMPLATE_NAME" "$REPO_ROOT" + status=$? + printf x + exit "$status" +); then TEMPLATE_CONTENT="${TEMPLATE_CONTENT%x}" else echo "ERROR: Could not resolve required $TEMPLATE_NAME from the template override stack for $REPO_ROOT" >&2 diff --git a/scripts/bash/setup-plan.sh b/scripts/bash/setup-plan.sh index aa394183cf..90e57f2ee9 100644 --- a/scripts/bash/setup-plan.sh +++ b/scripts/bash/setup-plan.sh @@ -10,7 +10,7 @@ for arg in "$@"; do --json) JSON_MODE=true ;; - --help|-h) + --help | -h) echo "Usage: $0 [--json]" echo " --json Output results in JSON format" echo " --help Show this help message" @@ -25,10 +25,14 @@ done # Get script directory and load common functions SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=/dev/null source "$SCRIPT_DIR/common.sh" # Get all paths and variables from common functions -_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; } +_paths_output=$(get_feature_paths) || { + echo "ERROR: Failed to resolve feature paths" >&2 + exit 1 +} eval "$_paths_output" unset _paths_output @@ -43,7 +47,7 @@ if [[ -f "$IMPL_PLAN" ]]; then echo "Plan already exists at $IMPL_PLAN, skipping template copy" fi else - if resolve_template_content "plan-template" "$REPO_ROOT" > "$IMPL_PLAN"; then + if resolve_template_content "plan-template" "$REPO_ROOT" >"$IMPL_PLAN"; then if $JSON_MODE; then echo "Copied plan template to $IMPL_PLAN" >&2 else diff --git a/scripts/bash/setup-tasks.sh b/scripts/bash/setup-tasks.sh index a5a685cd0e..688b505f6f 100644 --- a/scripts/bash/setup-tasks.sh +++ b/scripts/bash/setup-tasks.sh @@ -8,22 +8,29 @@ JSON_MODE=false for arg in "$@"; do case "$arg" in --json) JSON_MODE=true ;; - --help|-h) + --help | -h) echo "Usage: $0 [--json]" echo " --json Output results in JSON format" echo " --help Show this help message" exit 0 ;; - *) echo "ERROR: Unknown option '$arg'" >&2; exit 1 ;; + *) + echo "ERROR: Unknown option '$arg'" >&2 + exit 1 + ;; esac done # Source common functions SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=/dev/null source "$SCRIPT_DIR/common.sh" # Get feature paths -_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; } +_paths_output=$(get_feature_paths) || { + echo "ERROR: Failed to resolve feature paths" >&2 + exit 1 +} eval "$_paths_output" unset _paths_output @@ -51,7 +58,12 @@ fi # Resolve tasks template through override stack TASKS_TEMPLATE=$(resolve_template "tasks-template" "$REPO_ROOT") || true -if TASKS_TEMPLATE_CONTENT=$(resolve_template_content "tasks-template" "$REPO_ROOT"; status=$?; printf x; exit "$status"); then +if TASKS_TEMPLATE_CONTENT=$( + resolve_template_content "tasks-template" "$REPO_ROOT" + status=$? + printf x + exit "$status" +); then TASKS_TEMPLATE_CONTENT="${TASKS_TEMPLATE_CONTENT%x}" else echo "ERROR: Could not resolve required tasks-template from the template override stack for $REPO_ROOT" >&2