Skip to content

Pipeline Design 1

ezigus edited this page Feb 21, 2026 · 2 revisions

Design: Add shell completion installation to shipwright init

Context

shipwright init is the one-command bootstrap for new users. Prior to this change, the completions/ directory shipped three completion scripts (_shipwright for zsh, shipwright.bash, shipwright.fish) but nothing installed them during init. Users had to manually copy files and configure their shell rc files — a friction point that caused completions to go unused. The goal is to make tab completion work out-of-the-box as part of the standard setup flow.

Constraints from the codebase:

  • All scripts must be Bash 3.2 compatible (set -euo pipefail; no declare -A, no readarray)
  • sw-init.sh runs in bash regardless of the user's login shell, so $BASH_VERSION is always set — $SHELL is the only reliable signal for the user's actual shell
  • Init is designed to be idempotent (safe to run multiple times); rc file mutations must check before appending
  • The test harness in sw-init-test.sh sandboxes $HOME via $TEMP_DIR/home — completion install logic must respect $HOME to remain testable
  • The completions/ source directory is located relative to $REPO_DIR, not the install target path

Decision

Install completions as a discrete section in sw-init.sh immediately after the pipeline templates block (line 387), before the Claude Code settings block. Shell type is detected exclusively from $SHELL (with $ZSH_VERSION/$BASH_VERSION as last-resort fallbacks). Per-shell installation follows the XDG/conventional locations for each shell. All rc file mutations are guarded by grep -q idempotency checks. An unknown or unset shell silently warns and skips rather than failing init.

Data flow:

$SHELL env var
  → SHELL_TYPE (zsh|bash|fish|"")
  → copy completions/$SOURCE → $HOME/SHELL_SPECIFIC_PATH
  → grep -q guard → append to rc file if absent
  → print reload hint

Error handling:

  • $SHELL unset or unrecognized → warn + skip (install_completions stays 0)
  • Source file missing in completions/ → silently skip that shell's block (inner [[ -f ]] guard)
  • ~/.zshrc absent → create minimal file with fpath + compinit rather than failing

Alternatives Considered

  1. Detect shell via ps / $PPID — Pros: works even when $SHELL is wrong. Cons: fragile on macOS (different ps flags), adds subprocess overhead, Bash 3.2 incompatibility risk, harder to override in tests. Rejected in favor of $SHELL.

  2. System-wide completion install (e.g., /usr/local/share/zsh/site-functions/) — Pros: available to all users. Cons: requires sudo, not appropriate for a developer tool installer, breaks sandboxed test harness. Rejected; user-local paths are correct for this audience.

  3. Delegate to a separate shipwright completions install subcommand — Pros: composable, skippable. Cons: users must know to run a second command; defeats the "one-command setup" goal. Rejected; inline in init is the right place.

  4. Overwrite rc files entirely — Pros: simpler code. Cons: destroys user customizations; catastrophic for idempotency. Rejected unconditionally.

Implementation Plan

  • Files to create: none (completion scripts already exist in completions/)
  • Files to modify:
    • scripts/sw-init.sh — Shell Completions section, lines 387–489 (already implemented on this branch)
    • scripts/sw-init-test.sh — Tests 22–26 (already implemented on this branch)
  • Dependencies: none (pure bash, no new external tools)
  • Risk areas:
    • rc file corruption under pipefail: The grep -c || echo "0" anti-pattern (documented in CLAUDE.md) must not appear; current implementation uses grep -q ... || { append; } which is safe
    • $HOME expansion in rc lines: The bash completion source line uses $HOME in a heredoc-style append — must be verified it resolves at write time, not at source time (current code uses literal $HOME in the echo string, which will write the variable name, not the expanded path — this is a known edge case worth verifying)
    • compinit placement: Appending compinit after user customizations in .zshrc can conflict if a user already loads it conditionally; the guard grep -q "compinit" mitigates but cannot handle all cases
    • Fish auto-discovery: Fish reads ~/.config/fish/completions/ natively with no rc change needed — this is correctly leveraged; no rc mutation for fish

Validation Criteria

  • Running SHELL=/bin/zsh bash scripts/sw-init.sh creates ~/.zsh/completions/_shipwright and appends fpath+=~/.zsh/completions to ~/.zshrc exactly once
  • Running SHELL=/bin/bash bash scripts/sw-init.sh creates ~/.local/share/bash-completion/completions/shipwright
  • Running SHELL=/usr/local/bin/fish bash scripts/sw-init.sh creates ~/.config/fish/completions/shipwright.fish
  • Running init twice with SHELL=/bin/zsh results in exactly one fpath line in ~/.zshrc (no duplicates)
  • Running init with SHELL="" or SHELL=/bin/sh exits 0 and prints a warn message, no completion files created
  • All 26 tests in scripts/sw-init-test.sh pass
  • npm test (all 102 suites) passes with no regressions
  • The source line written to ~/.bashrc resolves $HOME to the actual path at shell load time (not the literal string $HOME)

Clone this wiki locally