bin/ Re-engineering Phase 1 — Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Create shared shell libraries under bin/lib/, set up BATS testing infrastructure, and write unit tests for every lib function. No existing scripts change in this phase.
Architecture: Six library files under bin/lib/ extracted from duplicated code across existing scripts. BATS (Bash Automated Testing System) via git submodules for testing. New CI job for shell tests.
Tech Stack: Bash, BATS (bats-core, bats-support, bats-assert), GitHub Actions
Task 1: Set up BATS test infrastructure
Files:
- Create:
bin/tests/test_helper/(git submodules) - Create:
bin/tests/lib/(directory)
Step 1: Add BATS git submodules
cd /Users/burak/Projects/server-maintanence
git submodule add https://github.com/bats-core/bats-core.git bin/tests/test_helper/bats-core
git submodule add https://github.com/bats-core/bats-support.git bin/tests/test_helper/bats-support
git submodule add https://github.com/bats-core/bats-assert.git bin/tests/test_helper/bats-assert
Step 2: Create test helper setup file
Create bin/tests/test_helper/common-setup.bash:
#!/usr/bin/env bash
# Common setup for all BATS tests.
# Source this in setup() of each .bats file.
_common_setup() {
# Load BATS helpers
load 'bats-support/load'
load 'bats-assert/load'
# Resolve paths
TEST_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")" && pwd)"
TESTS_ROOT="$(cd "$TEST_DIR" && while [ ! -d test_helper ]; do cd ..; done; pwd)"
BIN_DIR="$(dirname "$TESTS_ROOT")"
PROJECT_DIR="$(dirname "$BIN_DIR")"
LIB_DIR="$BIN_DIR/lib"
}
Step 3: Create directories
mkdir -p bin/tests/lib
Step 4: Verify BATS works
Create a minimal test bin/tests/lib/test_sanity.bats:
#!/usr/bin/env bats
setup() {
load '../test_helper/common-setup'
_common_setup
}
@test "BATS is working" {
run echo "hello"
assert_success
assert_output "hello"
}
@test "LIB_DIR points to bin/lib" {
[[ "$LIB_DIR" == */bin/lib ]]
}
Step 5: Run the sanity test
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/test_sanity.bats Expected: 2 tests, 2 passed
Step 6: Commit
git add bin/tests/ .gitmodules
git commit -m "chore: set up BATS testing infrastructure for bin/ scripts"
Task 2: Create bin/lib/colors.sh + tests
Files:
- Create:
bin/lib/colors.sh - Create:
bin/tests/lib/test_colors.bats
Step 1: Write the failing test
Create bin/tests/lib/test_colors.bats:
#!/usr/bin/env bats
setup() {
load '../test_helper/common-setup'
_common_setup
source "$LIB_DIR/colors.sh"
}
@test "RED is defined and non-empty" {
[ -n "$RED" ]
}
@test "GREEN is defined and non-empty" {
[ -n "$GREEN" ]
}
@test "YELLOW is defined and non-empty" {
[ -n "$YELLOW" ]
}
@test "BLUE is defined and non-empty" {
[ -n "$BLUE" ]
}
@test "CYAN is defined and non-empty" {
[ -n "$CYAN" ]
}
@test "BOLD is defined and non-empty" {
[ -n "$BOLD" ]
}
@test "NC is defined and non-empty" {
[ -n "$NC" ]
}
@test "colors contain ANSI escape sequences" {
[[ "$RED" == *$'\033['* ]]
[[ "$GREEN" == *$'\033['* ]]
[[ "$NC" == *$'\033['* ]]
}
Step 2: Run test to verify it fails
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/test_colors.bats Expected: FAIL — bin/lib/colors.sh does not exist
Step 3: Write implementation
Create bin/lib/colors.sh:
#!/usr/bin/env bash
#
# Color constants for terminal output.
# Source this file — do not execute directly.
#
# Guard against double-sourcing
[[ -n "${_LIB_COLORS_LOADED:-}" ]] && return 0
_LIB_COLORS_LOADED=1
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
Step 4: Run test to verify it passes
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/test_colors.bats Expected: 8 tests, 8 passed
Step 5: Commit
git add bin/lib/colors.sh bin/tests/lib/test_colors.bats
git commit -m "feat: add bin/lib/colors.sh with BATS tests"
Task 3: Create bin/lib/logging.sh + tests
Files:
- Create:
bin/lib/logging.sh - Create:
bin/tests/lib/test_logging.bats
Step 1: Write the failing test
Create bin/tests/lib/test_logging.bats:
#!/usr/bin/env bats
setup() {
load '../test_helper/common-setup'
_common_setup
source "$LIB_DIR/logging.sh"
}
@test "info() outputs with [INFO] label" {
run info "test message"
assert_success
assert_output --partial "[INFO]"
assert_output --partial "test message"
}
@test "success() outputs with [OK] label" {
run success "test message"
assert_success
assert_output --partial "[OK]"
assert_output --partial "test message"
}
@test "warn() outputs with [WARN] label" {
run warn "test message"
assert_success
assert_output --partial "[WARN]"
assert_output --partial "test message"
}
@test "error() outputs with [ERROR] label" {
run error "test message"
assert_success
assert_output --partial "[ERROR]"
assert_output --partial "test message"
}
@test "error() writes to stderr" {
# Capture stderr separately
run bash -c 'source "$1" && error "stderr test" 2>&1 1>/dev/null' -- "$LIB_DIR/logging.sh"
assert_output --partial "stderr test"
}
@test "info() handles multiple arguments" {
run info "hello world"
assert_success
assert_output --partial "hello world"
}
Step 2: Run test to verify it fails
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/test_logging.bats Expected: FAIL
Step 3: Write implementation
Create bin/lib/logging.sh:
#!/usr/bin/env bash
#
# Logging functions for terminal output.
# Source this file — do not execute directly.
#
[[ -n "${_LIB_LOGGING_LOADED:-}" ]] && return 0
_LIB_LOGGING_LOADED=1
# Source colors if not already loaded
_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$_LIB_DIR/colors.sh"
info() { printf "%b[INFO]%b %s\n" "$BLUE" "$NC" "$*"; }
success() { printf "%b[OK]%b %s\n" "$GREEN" "$NC" "$*"; }
warn() { printf "%b[WARN]%b %s\n" "$YELLOW" "$NC" "$*"; }
error() { printf "%b[ERROR]%b %s\n" "$RED" "$NC" "$*" >&2; }
Step 4: Run test to verify it passes
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/test_logging.bats Expected: 6 tests, 6 passed
Step 5: Commit
git add bin/lib/logging.sh bin/tests/lib/test_logging.bats
git commit -m "feat: add bin/lib/logging.sh with BATS tests"
Task 4: Create bin/lib/paths.sh + tests
Files:
- Create:
bin/lib/paths.sh - Create:
bin/tests/lib/test_paths.bats
Step 1: Write the failing test
Create bin/tests/lib/test_paths.bats:
#!/usr/bin/env bats
setup() {
load '../test_helper/common-setup'
_common_setup
source "$LIB_DIR/paths.sh"
}
@test "BIN_DIR is set and points to bin/" {
[ -n "$BIN_DIR" ]
[[ "$BIN_DIR" == */bin ]]
[ -d "$BIN_DIR" ]
}
@test "PROJECT_DIR is set and is parent of BIN_DIR" {
[ -n "$PROJECT_DIR" ]
[ -d "$PROJECT_DIR" ]
[ "$(dirname "$BIN_DIR")" = "$PROJECT_DIR" ]
}
@test "PROJECT_DIR contains pyproject.toml" {
[ -f "$PROJECT_DIR/pyproject.toml" ]
}
@test "resolve_project_dir returns correct path from nested dir" {
# Call from a subdirectory to ensure resolution works
run bash -c 'cd /tmp && source "'"$LIB_DIR/paths.sh"'" && echo "$PROJECT_DIR"'
assert_success
assert_output "$PROJECT_DIR"
}
Step 2: Run test to verify it fails
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/test_paths.bats Expected: FAIL
Step 3: Write implementation
Create bin/lib/paths.sh:
#!/usr/bin/env bash
#
# Path resolution for bin/ scripts.
# Source this file — do not execute directly.
#
[[ -n "${_LIB_PATHS_LOADED:-}" ]] && return 0
_LIB_PATHS_LOADED=1
# Resolve BIN_DIR from this file's location (bin/lib/ -> bin/)
BIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Project root is parent of bin/
PROJECT_DIR="$(dirname "$BIN_DIR")"
Step 4: Run test to verify it passes
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/test_paths.bats Expected: 4 tests, 4 passed
Step 5: Commit
git add bin/lib/paths.sh bin/tests/lib/test_paths.bats
git commit -m "feat: add bin/lib/paths.sh with BATS tests"
Task 5: Create bin/lib/checks.sh + tests
Files:
- Create:
bin/lib/checks.sh - Create:
bin/tests/lib/test_checks.bats
Step 1: Write the failing test
Create bin/tests/lib/test_checks.bats:
#!/usr/bin/env bats
setup() {
load '../test_helper/common-setup'
_common_setup
source "$LIB_DIR/checks.sh"
}
@test "command_exists returns 0 for bash" {
run command_exists bash
assert_success
}
@test "command_exists returns 1 for nonexistent command" {
run command_exists definitely_not_a_real_command_xyz
assert_failure
}
@test "command_exists returns 0 for ls" {
run command_exists ls
assert_success
}
Step 2: Run test to verify it fails
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/test_checks.bats Expected: FAIL
Step 3: Write implementation
Create bin/lib/checks.sh:
#!/usr/bin/env bash
#
# Common prerequisite checks.
# Source this file — do not execute directly.
#
[[ -n "${_LIB_CHECKS_LOADED:-}" ]] && return 0
_LIB_CHECKS_LOADED=1
_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$_LIB_DIR/logging.sh"
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check for a working Python 3.10+ binary.
# Sets PYTHON_BIN on success, exits on failure.
# Handles pyenv shims that exist but point to uninstalled versions.
check_python() {
info "Checking Python version..."
PYTHON_BIN=""
for candidate in python3.13 python3.12 python3.11 python3.10 python3; do
if command_exists "$candidate" && "$candidate" --version >/dev/null 2>&1; then
PYTHON_BIN="$candidate"
break
fi
done
if [ -z "$PYTHON_BIN" ]; then
error "Python 3 is not installed. Please install Python 3.10 or higher."
return 1
fi
local version major minor
version=$($PYTHON_BIN -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
major=$(echo "$version" | cut -d. -f1)
minor=$(echo "$version" | cut -d. -f2)
if [ "$major" -ge 3 ] && [ "$minor" -ge 10 ]; then
success "Python $version found via $PYTHON_BIN (>= 3.10 required)"
return 0
else
error "Python 3.10+ is required, but found Python $version ($PYTHON_BIN)"
return 1
fi
}
# Check for uv package manager, install if missing.
# Exits on failure.
check_uv() {
info "Checking for uv package manager..."
if command_exists uv; then
local uv_version
uv_version=$(uv --version 2>/dev/null | head -n1)
success "uv is already installed: $uv_version"
return 0
fi
warn "uv is not installed. Installing uv..."
if [[ "$OSTYPE" == "darwin"* ]] || [[ "$OSTYPE" == "linux"* ]]; then
curl -LsSf https://astral.sh/uv/install.sh | sh
if [ -f "$HOME/.cargo/env" ]; then
# shellcheck disable=SC1091
source "$HOME/.cargo/env"
fi
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
if command_exists uv; then
success "uv installed successfully"
return 0
else
error "Failed to install uv. Please install manually: https://docs.astral.sh/uv/"
return 1
fi
else
error "Unsupported OS. Please install uv manually: https://docs.astral.sh/uv/"
return 1
fi
}
Step 4: Run test to verify it passes
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/test_checks.bats Expected: 3 tests, 3 passed
Step 5: Commit
git add bin/lib/checks.sh bin/tests/lib/test_checks.bats
git commit -m "feat: add bin/lib/checks.sh with BATS tests"
Task 6: Create bin/lib/dotenv.sh + tests
Files:
- Create:
bin/lib/dotenv.sh - Create:
bin/tests/lib/test_dotenv.bats
Step 1: Write the failing test
Create bin/tests/lib/test_dotenv.bats:
#!/usr/bin/env bats
setup() {
load '../test_helper/common-setup'
_common_setup
source "$LIB_DIR/dotenv.sh"
# Create a temp directory for .env test files
TEST_TMPDIR="$(mktemp -d)"
}
teardown() {
rm -rf "$TEST_TMPDIR"
}
@test "dotenv_has_key finds existing key" {
echo "FOO=bar" > "$TEST_TMPDIR/.env"
run dotenv_has_key "$TEST_TMPDIR/.env" "FOO"
assert_success
}
@test "dotenv_has_key finds key with spaces around =" {
echo " FOO = bar" > "$TEST_TMPDIR/.env"
run dotenv_has_key "$TEST_TMPDIR/.env" "FOO"
assert_success
}
@test "dotenv_has_key returns failure for missing key" {
echo "FOO=bar" > "$TEST_TMPDIR/.env"
run dotenv_has_key "$TEST_TMPDIR/.env" "BAZ"
assert_failure
}
@test "dotenv_has_key returns failure for empty file" {
touch "$TEST_TMPDIR/.env"
run dotenv_has_key "$TEST_TMPDIR/.env" "FOO"
assert_failure
}
@test "dotenv_set_if_missing appends key when missing" {
echo "FOO=bar" > "$TEST_TMPDIR/.env"
dotenv_set_if_missing "$TEST_TMPDIR/.env" "BAZ" "qux"
run grep -c "BAZ=qux" "$TEST_TMPDIR/.env"
assert_output "1"
}
@test "dotenv_set_if_missing does not overwrite existing key" {
echo "FOO=bar" > "$TEST_TMPDIR/.env"
dotenv_set_if_missing "$TEST_TMPDIR/.env" "FOO" "new_value"
run grep "FOO" "$TEST_TMPDIR/.env"
assert_output "FOO=bar"
}
@test "dotenv_set_if_missing works on empty file" {
touch "$TEST_TMPDIR/.env"
dotenv_set_if_missing "$TEST_TMPDIR/.env" "KEY" "value"
run cat "$TEST_TMPDIR/.env"
assert_output "KEY=value"
}
@test "dotenv_ensure_file copies from sample when .env missing" {
echo "SAMPLE_KEY=sample" > "$TEST_TMPDIR/.env.sample"
# Override PROJECT_DIR for this test
PROJECT_DIR="$TEST_TMPDIR"
dotenv_ensure_file
[ -f "$TEST_TMPDIR/.env" ]
run cat "$TEST_TMPDIR/.env"
assert_output "SAMPLE_KEY=sample"
}
@test "dotenv_ensure_file does nothing when .env exists" {
echo "EXISTING=true" > "$TEST_TMPDIR/.env"
echo "SAMPLE_KEY=sample" > "$TEST_TMPDIR/.env.sample"
PROJECT_DIR="$TEST_TMPDIR"
dotenv_ensure_file
run cat "$TEST_TMPDIR/.env"
assert_output "EXISTING=true"
}
@test "dotenv_ensure_file creates empty .env when no sample exists" {
PROJECT_DIR="$TEST_TMPDIR"
dotenv_ensure_file
[ -f "$TEST_TMPDIR/.env" ]
run cat "$TEST_TMPDIR/.env"
assert_output ""
}
Step 2: Run test to verify it fails
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/test_dotenv.bats Expected: FAIL
Step 3: Write implementation
Create bin/lib/dotenv.sh:
#!/usr/bin/env bash
#
# .env file helpers.
# Source this file — do not execute directly.
#
[[ -n "${_LIB_DOTENV_LOADED:-}" ]] && return 0
_LIB_DOTENV_LOADED=1
_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$_LIB_DIR/logging.sh"
source "$_LIB_DIR/paths.sh"
# Create .env from .env.sample if it doesn't exist.
# Uses PROJECT_DIR (from paths.sh or overridden by caller).
dotenv_ensure_file() {
local env_file="$PROJECT_DIR/.env"
local sample_file="$PROJECT_DIR/.env.sample"
if [ -f "$env_file" ]; then
success ".env already exists"
return 0
fi
if [ -f "$sample_file" ]; then
cp "$sample_file" "$env_file"
success "Created .env from .env.sample"
return 0
fi
warn "No .env.sample found; creating empty .env"
touch "$env_file"
}
# Check if a key exists in a dotenv-style file.
# Usage: dotenv_has_key <file> <key>
dotenv_has_key() {
local file="$1"
local key="$2"
grep -Eq "^[[:space:]]*${key}[[:space:]]*=" "$file"
}
# Set a key=value in a file only if the key is not already present.
# Usage: dotenv_set_if_missing <file> <key> <value>
dotenv_set_if_missing() {
local file="$1"
local key="$2"
local value="$3"
if dotenv_has_key "$file" "$key"; then
return 0
fi
printf "%s=%s\n" "$key" "$value" >> "$file"
}
# Prompt user until they provide a non-empty value.
# Usage: result=$(prompt_non_empty "Enter value: ")
prompt_non_empty() {
local prompt="$1"
local value=""
while true; do
read -p "$prompt" -r value
if [ -n "$value" ]; then
echo "$value"
return 0
fi
echo "Value cannot be empty."
done
}
Step 4: Run test to verify it passes
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/test_dotenv.bats Expected: 10 tests, 10 passed
Step 5: Commit
git add bin/lib/dotenv.sh bin/tests/lib/test_dotenv.bats
git commit -m "feat: add bin/lib/dotenv.sh with BATS tests"
Task 7: Create bin/lib/docker.sh + tests
Files:
- Create:
bin/lib/docker.sh - Create:
bin/tests/lib/test_docker.bats
Step 1: Write the failing test
Create bin/tests/lib/test_docker.bats:
#!/usr/bin/env bats
setup() {
load '../test_helper/common-setup'
_common_setup
source "$LIB_DIR/docker.sh"
}
@test "parse_service_state extracts state from JSON array" {
local json='[{"Service":"web","State":"running"},{"Service":"celery","State":"exited"}]'
run parse_service_state "web" <<< "$json"
assert_success
assert_output "running"
}
@test "parse_service_state extracts state from NDJSON" {
local json=$'{"Service":"web","State":"running"}\n{"Service":"celery","State":"exited"}'
run parse_service_state "web" <<< "$json"
assert_success
assert_output "running"
}
@test "parse_service_state returns empty for missing service" {
local json='[{"Service":"web","State":"running"}]'
run parse_service_state "celery" <<< "$json"
assert_output ""
}
@test "parse_service_state handles empty input" {
run parse_service_state "web" <<< ""
assert_output ""
}
@test "docker_preflight fails without docker" {
# Override PATH to hide docker
PATH="/usr/bin:/bin"
run docker_preflight "/dev/null"
assert_failure
}
Step 2: Run test to verify it fails
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/test_docker.bats Expected: FAIL
Step 3: Write implementation
Create bin/lib/docker.sh:
#!/usr/bin/env bash
#
# Docker Compose helpers.
# Source this file — do not execute directly.
#
[[ -n "${_LIB_DOCKER_LOADED:-}" ]] && return 0
_LIB_DOCKER_LOADED=1
_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$_LIB_DIR/logging.sh"
source "$_LIB_DIR/checks.sh"
# Parse the state of a service from docker compose ps JSON output.
# Handles both JSON array (Compose v2.21+) and NDJSON (older v2).
# Reads from stdin so it can be tested without Docker.
# Usage: echo "$json" | parse_service_state <service_name>
parse_service_state() {
local service="$1"
python3 -c "
import sys, json
for line in sys.stdin:
line = line.strip()
if not line:
continue
try:
data = json.loads(line)
except (json.JSONDecodeError, ValueError):
continue
if isinstance(data, list):
for d in data:
if d.get('Service') == '$service':
print(d.get('State', ''))
sys.exit(0)
elif isinstance(data, dict):
if data.get('Service') == '$service':
print(data.get('State', ''))
sys.exit(0)
" 2>/dev/null || true
}
# Get the state of a running docker compose service.
# Usage: get_service_state <compose_file> <service_name>
get_service_state() {
local compose_file="$1"
local service="$2"
docker compose -f "$compose_file" ps --format json 2>/dev/null \
| parse_service_state "$service"
}
# Run Docker pre-flight checks.
# Usage: docker_preflight <compose_file>
# Returns 1 on failure.
docker_preflight() {
local compose_file="$1"
info "Checking for .env file..."
if [ ! -f "$(dirname "$(dirname "$compose_file")")/../.env" ] && [ ! -f ".env" ]; then
# Try to locate .env relative to compose file's project context
:
fi
info "Checking Docker daemon..."
if ! command_exists docker || ! docker info >/dev/null 2>&1; then
error "Docker is not running."
echo " Docker is required. Install it from https://docs.docker.com/get-docker/"
echo " and ensure the daemon is running."
return 1
fi
success "Docker daemon is running"
info "Checking docker compose v2..."
if ! docker compose version >/dev/null 2>&1; then
error "docker compose v2 is required but not available."
echo " Docker Compose v2 is included with Docker Desktop, or can be installed as a plugin."
echo " See: https://docs.docker.com/compose/install/"
return 1
fi
local compose_version
compose_version="$(docker compose version --short)"
success "docker compose v2 is available (v${compose_version})"
return 0
}
Step 4: Run test to verify it passes
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/test_docker.bats Expected: 5 tests, 5 passed
Step 5: Commit
git add bin/lib/docker.sh bin/tests/lib/test_docker.bats
git commit -m "feat: add bin/lib/docker.sh with BATS tests"
Task 8: Add BATS to CI workflow
Files:
- Modify:
.github/workflows/ci.yml
Step 1: Add shell-tests job to CI
Append after the security job in .github/workflows/ci.yml:
shell-tests:
name: Shell Tests (BATS)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Run BATS tests
run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/
Step 2: Verify YAML syntax
Run: python3 -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))" && echo "YAML OK" Expected: YAML OK
Step 3: Run all BATS tests locally one more time
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/ Expected: All tests pass (sanity + colors + logging + paths + checks + dotenv + docker)
Step 4: Commit
git add .github/workflows/ci.yml
git commit -m "ci: add BATS shell tests job to CI workflow"
Task 9: Clean up sanity test and final verification
Files:
- Delete:
bin/tests/lib/test_sanity.bats(was scaffolding only)
Step 1: Remove sanity test
rm bin/tests/lib/test_sanity.bats
Step 2: Run all tests
Run: ./bin/tests/test_helper/bats-core/bin/bats bin/tests/lib/ Expected: All remaining tests pass (colors: 8, logging: 6, paths: 4, checks: 3, dotenv: 10, docker: 5 = 36 total)
Step 3: Verify all lib files have load guards
Run: grep -l "_LIB_.*_LOADED" bin/lib/*.sh | wc -l Expected: 6 (all lib files)
Step 4: Commit
git add -A bin/tests/lib/
git commit -m "chore: remove sanity test scaffold, finalize Phase 1"