Skip to content

Pipeline Design inline

Seth Ford edited this page Mar 2, 2026 · 8 revisions

Design: Add a 'shipwright ping' command that prints 'pong' to stdout and exits 0

Context

Shipwright needed a lightweight, zero-dependency health-check primitive — a command that a human or machine can run to confirm the CLI is installed, on PATH, and executable without triggering any external API calls, GitHub auth, or tmux sessions. The existing 100+ commands all interact with external systems; none served as a pure local smoke-test.

Codebase constraints that shaped the design:

  • All commands are standalone Bash scripts dispatched by scripts/sw via a case statement using exec
  • Scripts must be Bash 3.2 compatible (macOS system shell) — no declare -A, readarray, ${var,,}, etc.
  • Every script must carry set -euo pipefail, an ERR trap, and a VERSION constant in sync with package.json
  • The test harness (npm test) discovers test suites registered in package.json; each command has a parallel sw-<name>-test.sh file
  • Output helpers (info, success, warn, error) are sourced from lib/helpers.sh with inline fallbacks for test environments where SCRIPT_DIR is overridden

Decision

Implement shipwright ping as a standalone Bash script (scripts/sw-ping.sh) following the established Command Script pattern, registered in the router at scripts/sw lines 605–606.

The command's single responsibility is: receive zero arguments → print pong\n to stdout → exit 0. All flag parsing (--help, -h, --version, -v, unknown flags) is handled inside main() via a single case on ${1:-}.


Alternatives Considered

  1. Inline the response in the router (scripts/sw) — Pros: Zero new files. Cons: The router becomes a mixed-concern object (dispatch + implementation); untestable in isolation; inconsistent with every other command; modifying the router for each new command violates open/closed. Rejected.

  2. Shared library function (e.g., lib/helpers.sh: ping_cmd()) — Pros: Reusable if multiple callers need pong-like output. Cons: No second caller exists or is anticipated; premature abstraction adds indirection; the library is for cross-cutting concerns (colors, output, events), not command implementations. Rejected.

  3. Alias or symlink to sw-hello.sh with env var control — Pros: Near-zero code. Cons: The two commands have different semantics (hello = greeting, ping = health probe); conflating them produces an unreadable interface; symlink resolution under set -euo pipefail has historically caused issues in this codebase. Rejected.


Implementation Plan

Files created:

Path Purpose
scripts/sw-ping.sh Command implementation (68 lines)
scripts/sw-ping-test.sh Test suite — 6 cases, self-contained (109 lines)

Files modified:

Path Change
scripts/sw Lines 605–606: ping) case dispatching to sw-ping.sh
package.json test script: bash scripts/sw-ping-test.sh appended to suite

Dependencies: None. No new packages, no new libraries, no external calls.

Risk areas:

  • set -euo pipefail + the ERR trap can produce spurious ERROR: output in test environments when the test intentionally provokes exit code 1 (invalid option). The test at line 92–93 of sw-ping-test.sh defensively captures the exit code with || local exit_code=$? rather than relying on $? after a command that might exit non-zero inside a function. Consistent with the failure patterns documented in memory/failures.json.
  • The ((PASS++)) / ((FAIL++)) arithmetic in the test file will cause set -e to abort if the counter starts at 0 — mitigated by initializing both counters to 0 before the first increment and by the fact that arithmetic ((expr)) returns exit code 1 when the result is 0. This is a known Bash pitfall; the existing test files in the repo share the same pattern.

Component Diagram

┌─────────────────────────────────────────────────────┐
│  User / CI / Health-check script                    │
└──────────────────────────┬──────────────────────────┘
                           │  shipwright ping
                           ▼
┌─────────────────────────────────────────────────────┐
│  scripts/sw  (Command Router)                       │
│  ─────────────────────────────                      │
│  case "$cmd" in                                     │
│    ping) exec sw-ping.sh "$@" ;;                   │
│  esac                                               │
└──────────────────────────┬──────────────────────────┘
                           │  exec (replaces process)
                           ▼
┌─────────────────────────────────────────────────────┐
│  scripts/sw-ping.sh  (Ping Command)                 │
│  ─────────────────────────────                      │
│  main()                                             │
│    case ${1:-}                                      │
│      ""        → echo "pong"; exit 0               │
│      --help/-h → show_help; exit 0                 │
│      --ver/-v  → echo $VERSION; exit 0             │
│      *         → error + show_help; exit 1         │
└──────────────────────────┬──────────────────────────┘
                           │  stdout: "pong"
                           ▼
                      Caller process

Test boundary (separate process):
┌─────────────────────────────────────────────────────┐
│  scripts/sw-ping-test.sh  (Test Suite)              │
│  ─────────────────────────────                      │
│  Invokes sw-ping.sh as subprocess for each case    │
│  assert_equals / assert_exit_code accumulate        │
│  PASS/FAIL counts, exits 1 if FAIL > 0             │
└─────────────────────────────────────────────────────┘

Interface Contracts

// Conceptual interface (implemented in Bash)

/**
 * @param args  Process argv after "ping" is stripped by router
 * @stdout      "pong\n"            when args is empty
 * @stdout      help text           when args[0] is --help or -h
 * @stdout      VERSION string      when args[0] is --version or -v
 * @stderr      error message       when args[0] is unknown flag
 * @exit        0                   on success (pong, help, version)
 * @exit        1                   on unknown flag
 */
function ping(args: string[]): never;

// Router contract (scripts/sw)
//   Input:  argv[0] === "ping"
//   Action: exec(sw-ping.sh, argv.slice(1))
//   Effect: current process is replaced; no return

// Test contract (sw-ping-test.sh)
//   Precondition:  sw-ping.sh is executable and on SCRIPT_DIR path
//   Postcondition: exit 0 iff FAIL === 0
//   Side effects:  writes PASS/FAIL counts to stdout

Data Flow

Request:   shipwright ping [args]
           │
           ▼
Router:    parse $1 → match "ping" → exec sw-ping.sh [args]
           │                          (process replaced, no fork overhead)
           ▼
Command:   parse ${1:-}
           ├── empty  → write "pong\n" to fd1 → _exit(0)
           ├── help   → write help heredoc to fd1 → _exit(0)
           ├── version→ write VERSION to fd1 → _exit(0)
           └── other  → write error to fd2, help to fd1 → _exit(1)
           │
           ▼
Response:  caller receives stdout stream + exit code
           (no JSON, no structured output, no side effects)

Error Boundaries

Layer Error type Handling
Router (scripts/sw) Unknown top-level subcommand Router's own *) case — prints usage, exits 1. Never reaches ping.
Ping command (sw-ping.sh) Unknown flag passed to ping error() to stderr + show_help to stdout + exit 1
Ping command ERR trap fires (unexpected) ERR trap prints ERROR: <file>:<line> exited with status <N> to stderr; process exits non-zero
Test suite Subprocess exits non-zero unexpectedly assert_exit_code captures it; FAIL incremented; suite continues; exits 1 at end
Test suite ERR trap fires inside test function Would abort the test process — mitigated by the `

Validation Criteria

  • shipwright ping prints exactly pong (no trailing whitespace, no ANSI codes) and exits 0
  • shipwright ping --help and shipwright ping -h print a USAGE block and exit 0
  • shipwright ping --version and shipwright ping -v print a semver string matching ^[0-9]+\.[0-9]+\.[0-9]+ and exit 0
  • shipwright ping --invalid exits 1 and writes an error to stderr
  • scripts/sw-ping-test.sh runs to completion with PASS: 6 / FAIL: 0
  • npm test includes sw-ping-test.sh and the suite passes end-to-end
  • No external calls, no tmux, no GitHub API, no Claude CLI invoked at any point
  • Script is Bash 3.2 compatible (verified: no bash 4+ constructs used)
  • VERSION constant in sw-ping.sh matches package.json"version"

Clone this wiki locally