Skip to content

Pipeline Design 4

Seth Ford edited this page Feb 12, 2026 · 2 revisions

Design: Add --json output flag to shipwright status command

Context

The shipwright status command (scripts/sw-status.sh, ~605 lines) renders a human-readable dashboard showing tmux windows, team configs, task lists, daemon/pipeline state, issue tracker info, heartbeats, remote machines, and connected developers. It currently interleaves data collection (tmux queries, JSON file reads, curl calls) with ANSI-colored echo output, making it impossible to consume programmatically.

Other shipwright commands already support --json output: sw-pipeline-vitals.sh, sw-cost.sh, and sw-fleet.sh all follow a pattern of flag parsing at the top, data collection into variables, and conditional rendering. The project requires Bash 3.2 compatibility (no associative arrays, no readarray, no ${var,,}), set -euo pipefail, and jq --arg for JSON construction (never string interpolation).

Decision

Refactor sw-status.sh into collect/render pairs, gated by a --json flag.

Data flow:

  1. Parse --json / --help flags at script top (after source compat.sh)
  2. Eight collect_* functions each populate a *_JSON shell variable with a jq-constructed JSON fragment (array or object). These functions perform no stdout output.
  3. When JSON_OUTPUT=false: eight render_* functions reproduce the existing human-readable output verbatim using the collected data.
  4. When JSON_OUTPUT=true: a single jq -n call assembles all eight fragments plus version and timestamp into one JSON object written to stdout, then exits.

JSON schema (top-level keys):

{
  "version": "string",
  "timestamp": "ISO-8601 UTC",
  "tmux_windows": [{"name", "session_window", "pane_count", "active"}],
  "teams": [{"name", "members", "member_names"}],
  "tasks": [{"team", "total", "completed", "in_progress", "pending", "percent_complete"}],
  "daemon": {"running", "pid", "uptime_seconds", "active_jobs", "queued", "completed", "recent_activity"},
  "tracker": {"provider", "url"},
  "heartbeats": [{"job_id", "pid", "alive", "stage", "issue", "iteration", "activity", "updated_at", "age_seconds", "memory_mb"}],
  "remote_machines": [{"name", "host", "cores", "memory_gb", "max_workers"}],
  "connected_developers": {"reachable", "dashboard_url", "total_online", "developers"}
}

Error handling: Each collect_* function initializes its variable to [] or {} before attempting data reads. Missing files, unreachable daemons, or failed tmux queries result in empty collections — never missing keys. All jq calls use --arg / --argjson for safe escaping. ANSI escape codes are never written when JSON_OUTPUT=true (color helpers short-circuit or are bypassed entirely).

Pattern alignment: Follows the same flag-parsing and jq -n assembly pattern used in sw-pipeline-vitals.sh (which the user's supermemory confirms has a --json flag) and sw-cost.sh.

Alternatives Considered

  1. Emit JSON per-section (streaming NDJSON) — Pros: simpler implementation, each section independently parseable. Cons: breaks jq . on the full output, inconsistent with sw-pipeline-vitals.sh and sw-cost.sh which emit a single JSON object. Users expect | jq .field to work on a single document.

  2. Separate sw-status-json.sh script — Pros: zero risk of regressing human-readable output. Cons: duplicates all data-collection logic, doubles maintenance surface, diverges from the --json flag convention established by other sw scripts.

  3. Template-based rendering (shared data, pluggable formatters) — Pros: cleanest separation of concerns. Cons: over-engineered for a bash script; adds abstraction layers that make the code harder to read and maintain. The collect/render pair approach achieves adequate separation without framework overhead.

Implementation Plan

Files to create:

  • scripts/sw-status-test.sh — test suite following existing harness pattern (mock binaries, PASS/FAIL counters, ERR trap, temp directory fixtures)

Files to modify:

  • scripts/sw-status.sh — add flag parsing, refactor into 8 collect/render pairs, add JSON assembly
  • scripts/sw — update help text for status subcommand to show [--json]
  • package.json — append && bash scripts/sw-status-test.sh to the npm test script

Dependencies: None new. jq is already a project dependency used throughout.

Risk areas:

  • Regression in human-readable output. The render functions must reproduce the exact current output including ANSI codes, box-drawing characters, and conditional sections. Mitigation: capture a baseline snapshot of current output before refactoring; compare after.
  • set -euo pipefail interactions. Commands like tmux list-windows or curl that may fail (no tmux session, daemon not running) must be guarded with || true or checked via return code, not allowed to exit the script. The current code already handles some of these; refactoring must preserve all guards.
  • Large JSON assembly. If a user has many heartbeats or team configs, the jq -n call receives many --argjson arguments. This is fine for practical team sizes but worth noting. No mitigation needed.
  • Bash 3.2 $() nesting. Complex jq command substitutions must avoid nested $() where possible, using temp variables instead.

Validation Criteria

  • shipwright status (no flag) produces byte-identical output to the pre-change version in all states (active daemon, no daemon, empty teams, populated teams)
  • shipwright status --json outputs valid JSON: shipwright status --json | jq empty exits 0
  • JSON output contains zero ANSI escape codes: shipwright status --json | grep -P '\x1b\[' | wc -l returns 0
  • All 10 top-level keys present in JSON output: version, timestamp, tmux_windows, teams, tasks, daemon, tracker, heartbeats, remote_machines, connected_developers
  • Empty state (no daemon, no teams, no tmux) produces empty arrays/objects for all collection keys — no null values, no missing keys
  • shipwright status --help prints usage including --json documentation
  • shipwright help / sw help text shows [--json] for the status subcommand
  • sw-status-test.sh passes all tests (valid JSON structure, key presence, empty-state handling, human-readable section headers, help output)
  • Test suite registered in package.json and runs via npm test
  • No Bash 3.2 incompatibilities: no declare -A, no readarray, no ${var,,}, no ${var^^}
  • set -euo pipefail maintained; no unguarded commands that could exit on expected failures

Clone this wiki locally