shipwright-cli 2.4.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/README.md +16 -11
  2. package/completions/_shipwright +1 -1
  3. package/completions/shipwright.bash +3 -8
  4. package/completions/shipwright.fish +1 -1
  5. package/config/defaults.json +111 -0
  6. package/config/event-schema.json +81 -0
  7. package/config/policy.json +13 -18
  8. package/dashboard/coverage/coverage-summary.json +14 -0
  9. package/dashboard/public/index.html +1 -1
  10. package/dashboard/server.ts +306 -17
  11. package/dashboard/src/components/charts/bar.test.ts +79 -0
  12. package/dashboard/src/components/charts/donut.test.ts +68 -0
  13. package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
  14. package/dashboard/src/components/charts/sparkline.test.ts +125 -0
  15. package/dashboard/src/core/api.test.ts +309 -0
  16. package/dashboard/src/core/helpers.test.ts +301 -0
  17. package/dashboard/src/core/router.test.ts +307 -0
  18. package/dashboard/src/core/router.ts +7 -0
  19. package/dashboard/src/core/sse.test.ts +144 -0
  20. package/dashboard/src/views/metrics.test.ts +186 -0
  21. package/dashboard/src/views/overview.test.ts +173 -0
  22. package/dashboard/src/views/pipelines.test.ts +183 -0
  23. package/dashboard/src/views/team.test.ts +253 -0
  24. package/dashboard/vitest.config.ts +14 -5
  25. package/docs/TIPS.md +1 -1
  26. package/docs/patterns/README.md +1 -1
  27. package/package.json +5 -7
  28. package/scripts/adapters/docker-deploy.sh +1 -1
  29. package/scripts/adapters/tmux-adapter.sh +11 -1
  30. package/scripts/adapters/wezterm-adapter.sh +1 -1
  31. package/scripts/check-version-consistency.sh +1 -1
  32. package/scripts/lib/architecture.sh +126 -0
  33. package/scripts/lib/bootstrap.sh +75 -0
  34. package/scripts/lib/compat.sh +89 -6
  35. package/scripts/lib/config.sh +91 -0
  36. package/scripts/lib/daemon-adaptive.sh +3 -3
  37. package/scripts/lib/daemon-dispatch.sh +39 -16
  38. package/scripts/lib/daemon-health.sh +1 -1
  39. package/scripts/lib/daemon-patrol.sh +24 -12
  40. package/scripts/lib/daemon-poll.sh +37 -25
  41. package/scripts/lib/daemon-state.sh +115 -23
  42. package/scripts/lib/daemon-triage.sh +30 -8
  43. package/scripts/lib/fleet-failover.sh +63 -0
  44. package/scripts/lib/helpers.sh +30 -6
  45. package/scripts/lib/pipeline-detection.sh +2 -2
  46. package/scripts/lib/pipeline-github.sh +9 -9
  47. package/scripts/lib/pipeline-intelligence.sh +85 -35
  48. package/scripts/lib/pipeline-quality-checks.sh +16 -16
  49. package/scripts/lib/pipeline-quality.sh +1 -1
  50. package/scripts/lib/pipeline-stages.sh +242 -28
  51. package/scripts/lib/pipeline-state.sh +40 -4
  52. package/scripts/lib/test-helpers.sh +247 -0
  53. package/scripts/postinstall.mjs +3 -11
  54. package/scripts/sw +10 -4
  55. package/scripts/sw-activity.sh +1 -11
  56. package/scripts/sw-adaptive.sh +109 -85
  57. package/scripts/sw-adversarial.sh +4 -14
  58. package/scripts/sw-architecture-enforcer.sh +1 -11
  59. package/scripts/sw-auth.sh +8 -17
  60. package/scripts/sw-autonomous.sh +111 -49
  61. package/scripts/sw-changelog.sh +1 -11
  62. package/scripts/sw-checkpoint.sh +144 -20
  63. package/scripts/sw-ci.sh +2 -12
  64. package/scripts/sw-cleanup.sh +13 -17
  65. package/scripts/sw-code-review.sh +16 -36
  66. package/scripts/sw-connect.sh +5 -12
  67. package/scripts/sw-context.sh +9 -26
  68. package/scripts/sw-cost.sh +6 -16
  69. package/scripts/sw-daemon.sh +75 -70
  70. package/scripts/sw-dashboard.sh +57 -17
  71. package/scripts/sw-db.sh +506 -15
  72. package/scripts/sw-decompose.sh +1 -11
  73. package/scripts/sw-deps.sh +15 -25
  74. package/scripts/sw-developer-simulation.sh +1 -11
  75. package/scripts/sw-discovery.sh +112 -30
  76. package/scripts/sw-doc-fleet.sh +7 -17
  77. package/scripts/sw-docs-agent.sh +6 -16
  78. package/scripts/sw-docs.sh +4 -12
  79. package/scripts/sw-doctor.sh +134 -43
  80. package/scripts/sw-dora.sh +11 -19
  81. package/scripts/sw-durable.sh +35 -52
  82. package/scripts/sw-e2e-orchestrator.sh +11 -27
  83. package/scripts/sw-eventbus.sh +115 -115
  84. package/scripts/sw-evidence.sh +114 -30
  85. package/scripts/sw-feedback.sh +3 -13
  86. package/scripts/sw-fix.sh +2 -20
  87. package/scripts/sw-fleet-discover.sh +1 -11
  88. package/scripts/sw-fleet-viz.sh +10 -18
  89. package/scripts/sw-fleet.sh +13 -17
  90. package/scripts/sw-github-app.sh +6 -16
  91. package/scripts/sw-github-checks.sh +1 -11
  92. package/scripts/sw-github-deploy.sh +1 -11
  93. package/scripts/sw-github-graphql.sh +2 -12
  94. package/scripts/sw-guild.sh +1 -11
  95. package/scripts/sw-heartbeat.sh +49 -12
  96. package/scripts/sw-hygiene.sh +45 -43
  97. package/scripts/sw-incident.sh +48 -74
  98. package/scripts/sw-init.sh +35 -37
  99. package/scripts/sw-instrument.sh +1 -11
  100. package/scripts/sw-intelligence.sh +362 -51
  101. package/scripts/sw-jira.sh +5 -14
  102. package/scripts/sw-launchd.sh +2 -12
  103. package/scripts/sw-linear.sh +8 -17
  104. package/scripts/sw-logs.sh +4 -12
  105. package/scripts/sw-loop.sh +641 -90
  106. package/scripts/sw-memory.sh +243 -17
  107. package/scripts/sw-mission-control.sh +2 -12
  108. package/scripts/sw-model-router.sh +73 -34
  109. package/scripts/sw-otel.sh +11 -21
  110. package/scripts/sw-oversight.sh +1 -11
  111. package/scripts/sw-patrol-meta.sh +5 -11
  112. package/scripts/sw-pipeline-composer.sh +7 -17
  113. package/scripts/sw-pipeline-vitals.sh +1 -11
  114. package/scripts/sw-pipeline.sh +478 -122
  115. package/scripts/sw-pm.sh +2 -12
  116. package/scripts/sw-pr-lifecycle.sh +27 -25
  117. package/scripts/sw-predictive.sh +16 -22
  118. package/scripts/sw-prep.sh +6 -16
  119. package/scripts/sw-ps.sh +1 -11
  120. package/scripts/sw-public-dashboard.sh +2 -12
  121. package/scripts/sw-quality.sh +77 -10
  122. package/scripts/sw-reaper.sh +1 -11
  123. package/scripts/sw-recruit.sh +15 -25
  124. package/scripts/sw-regression.sh +11 -21
  125. package/scripts/sw-release-manager.sh +19 -28
  126. package/scripts/sw-release.sh +8 -16
  127. package/scripts/sw-remote.sh +1 -11
  128. package/scripts/sw-replay.sh +48 -44
  129. package/scripts/sw-retro.sh +70 -92
  130. package/scripts/sw-review-rerun.sh +1 -1
  131. package/scripts/sw-scale.sh +109 -32
  132. package/scripts/sw-security-audit.sh +12 -22
  133. package/scripts/sw-self-optimize.sh +239 -23
  134. package/scripts/sw-session.sh +3 -13
  135. package/scripts/sw-setup.sh +8 -18
  136. package/scripts/sw-standup.sh +5 -15
  137. package/scripts/sw-status.sh +32 -23
  138. package/scripts/sw-strategic.sh +129 -13
  139. package/scripts/sw-stream.sh +1 -11
  140. package/scripts/sw-swarm.sh +76 -36
  141. package/scripts/sw-team-stages.sh +10 -20
  142. package/scripts/sw-templates.sh +4 -14
  143. package/scripts/sw-testgen.sh +3 -13
  144. package/scripts/sw-tmux-pipeline.sh +1 -19
  145. package/scripts/sw-tmux-role-color.sh +0 -10
  146. package/scripts/sw-tmux-status.sh +3 -11
  147. package/scripts/sw-tmux.sh +2 -20
  148. package/scripts/sw-trace.sh +1 -19
  149. package/scripts/sw-tracker-github.sh +0 -10
  150. package/scripts/sw-tracker-jira.sh +1 -11
  151. package/scripts/sw-tracker-linear.sh +1 -11
  152. package/scripts/sw-tracker.sh +7 -24
  153. package/scripts/sw-triage.sh +24 -34
  154. package/scripts/sw-upgrade.sh +5 -23
  155. package/scripts/sw-ux.sh +1 -19
  156. package/scripts/sw-webhook.sh +18 -32
  157. package/scripts/sw-widgets.sh +3 -21
  158. package/scripts/sw-worktree.sh +11 -27
  159. package/scripts/update-homebrew-sha.sh +67 -0
  160. package/templates/pipelines/tdd.json +72 -0
  161. package/scripts/sw-pipeline.sh.mock +0 -7
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="2.4.0"
9
+ VERSION="3.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -33,16 +33,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
33
33
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
34
34
  }
35
35
  fi
36
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
37
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
38
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
39
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
40
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
41
- RED="${RED:-\033[38;2;248;113;113m}"
42
- DIM="${DIM:-\033[2m}"
43
- BOLD="${BOLD:-\033[1m}"
44
- RESET="${RESET:-\033[0m}"
45
-
46
36
  # ─── Constants ──────────────────────────────────────────────────────────────
47
37
  SWARM_DIR="${HOME}/.shipwright/swarm"
48
38
  REGISTRY_FILE="${SWARM_DIR}/registry.json"
@@ -197,12 +187,12 @@ cmd_spawn() {
197
187
  mv "$tmp_file" "$REGISTRY_FILE" || { rm -f "$tmp_file"; error "Failed to update registry"; return 1; }
198
188
  record_metric "$agent_id" "spawn" "1" "$agent_type"
199
189
 
200
- # Create real tmux session for the agent (so scale/loop can send commands)
201
- if command -v tmux &>/dev/null; then
190
+ # Create real tmux session for the agent (run actual daemon)
191
+ if command -v tmux >/dev/null 2>&1; then
202
192
  local session_name="swarm-${agent_id}"
203
193
  if ! tmux has-session -t "$session_name" 2>/dev/null; then
204
194
  tmux new-session -d -s "$session_name" -c "$REPO_DIR" \
205
- "echo \"Agent $agent_id ready (type: $agent_type)\"; while true; do sleep 3600; done" 2>/dev/null && \
195
+ "SW_AGENT_ROLE=builder bash scripts/sw-daemon.sh start --role builder 2>&1 | tee /tmp/sw-swarm-${agent_id}.log" 2>/dev/null && \
206
196
  info "Tmux session created: $session_name" || warn "Tmux session creation failed (agent still in registry)"
207
197
  fi
208
198
  fi
@@ -250,7 +240,7 @@ cmd_retire() {
250
240
 
251
241
  # Kill real tmux session if present
252
242
  local session_name="swarm-${agent_id}"
253
- if command -v tmux &>/dev/null && tmux has-session -t "$session_name" 2>/dev/null; then
243
+ if command -v tmux >/dev/null 2>&1 && tmux has-session -t "$session_name" 2>/dev/null; then
254
244
  tmux kill-session -t "$session_name" 2>/dev/null && info "Tmux session killed: $session_name" || warn "Tmux kill failed for $session_name"
255
245
  fi
256
246
 
@@ -342,37 +332,87 @@ cmd_health() {
342
332
  fi
343
333
  }
344
334
 
335
+ # ─── Swarm spawn helper (for cmd_scale) ───────────────────────────────────
336
+ swarm_spawn_agent() {
337
+ local role="${1:-builder}"
338
+ local count="${2:-1}"
339
+ local i
340
+ for i in $(seq 1 "$count"); do
341
+ cmd_spawn "standard" 2>/dev/null || true
342
+ done
343
+ }
344
+
345
345
  # ─── Auto-scale logic ────────────────────────────────────────────────────
346
346
  cmd_scale() {
347
+ local target="${1:-auto}"
348
+
347
349
  ensure_dirs
348
350
  init_registry
349
351
  init_config
350
352
 
351
- local auto_scale_enabled
352
- auto_scale_enabled=$(jq -r '.auto_scaling_enabled' "$CONFIG_FILE")
353
+ if [[ "$target" == "auto" ]]; then
354
+ # Auto-scale based on queue depth
355
+ local queue_depth=0
356
+ local state_file="$HOME/.shipwright/daemon-state.json"
357
+ if [[ -f "$state_file" ]]; then
358
+ queue_depth=$(jq '.queued | length' "$state_file" 2>/dev/null || echo "0")
359
+ fi
353
360
 
354
- if [[ "$auto_scale_enabled" != "true" ]]; then
355
- warn "Auto-scaling is disabled"
356
- return 0
357
- fi
361
+ local current_agents
362
+ current_agents=$(tmux list-sessions -F '#{session_name}' 2>/dev/null | grep -cE '^shipwright-sw-agent|^swarm-' || echo "0")
358
363
 
359
- local min_agents max_agents target_util
360
- min_agents=$(jq -r '.min_agents // 1' "$CONFIG_FILE")
361
- max_agents=$(jq -r '.max_agents // 8' "$CONFIG_FILE")
362
- target_util=$(jq -r '.target_utilization // 0.75' "$CONFIG_FILE")
364
+ local target_agents=1
365
+ if [[ "$queue_depth" -gt 5 ]]; then
366
+ target_agents=3
367
+ elif [[ "$queue_depth" -gt 2 ]]; then
368
+ target_agents=2
369
+ fi
363
370
 
364
- local active_count
365
- active_count=$(jq -r '.active_count // 0' "$REGISTRY_FILE")
371
+ if [[ "$current_agents" -lt "$target_agents" ]]; then
372
+ local to_spawn=$((target_agents - current_agents))
373
+ echo "Queue depth: $queue_depth, scaling up by $to_spawn"
374
+ swarm_spawn_agent "builder" "$to_spawn"
375
+ elif [[ "$current_agents" -gt "$target_agents" && "$current_agents" -gt 1 ]]; then
376
+ local to_retire=$((current_agents - target_agents))
377
+ echo "Queue depth: $queue_depth, scaling down by $to_retire"
378
+ # Retire oldest agents from registry (tmux sessions are managed by scale down)
379
+ local registry_count
380
+ registry_count=$(jq -r '.agents | length' "$REGISTRY_FILE" 2>/dev/null || echo "0")
381
+ local retired=0
382
+ if [[ "$registry_count" -gt 0 ]] && [[ "$to_retire" -gt 0 ]]; then
383
+ local agent_id
384
+ agent_id=$(jq -r '.agents[0].id // empty' "$REGISTRY_FILE" 2>/dev/null)
385
+ if [[ -n "$agent_id" ]]; then
386
+ cmd_retire "$agent_id" 2>/dev/null && retired=1 || true
387
+ fi
388
+ fi
389
+ else
390
+ echo "Queue depth: $queue_depth, agents: $current_agents (optimal)"
391
+ fi
392
+ else
393
+ # Scale to specific count - show current state
394
+ local auto_scale_enabled
395
+ auto_scale_enabled=$(jq -r '.auto_scaling_enabled' "$CONFIG_FILE")
366
396
 
367
- # TODO: Implement queue depth and resource monitoring
368
- # For now, just show current state
369
- info "Auto-Scaling Analysis"
370
- echo ""
371
- echo -e " Current agents: ${CYAN}${active_count}/${max_agents}${RESET}"
372
- echo -e " Min agents: ${CYAN}${min_agents}${RESET}"
373
- echo -e " Target utilization: ${CYAN}${target_util}${RESET}"
374
- echo ""
375
- echo -e " ${DIM}Queue depth monitoring and scaling recommendations require active pipeline${RESET}"
397
+ if [[ "$auto_scale_enabled" != "true" ]]; then
398
+ warn "Auto-scaling is disabled"
399
+ fi
400
+
401
+ local min_agents max_agents target_util
402
+ min_agents=$(jq -r '.min_agents // 1' "$CONFIG_FILE")
403
+ max_agents=$(jq -r '.max_agents // 8' "$CONFIG_FILE")
404
+ target_util=$(jq -r '.target_utilization // 0.75' "$CONFIG_FILE")
405
+
406
+ local active_count
407
+ active_count=$(jq -r '.active_count // 0' "$REGISTRY_FILE")
408
+
409
+ info "Auto-Scaling Analysis"
410
+ echo ""
411
+ echo -e " Current agents: ${CYAN}${active_count}/${max_agents}${RESET}"
412
+ echo -e " Min agents: ${CYAN}${min_agents}${RESET}"
413
+ echo -e " Target utilization: ${CYAN}${target_util}${RESET}"
414
+ echo ""
415
+ fi
376
416
  }
377
417
 
378
418
  # ─── Performance leaderboard ──────────────────────────────────────────────
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="2.4.0"
9
+ VERSION="3.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -34,16 +34,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
34
34
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
35
35
  }
36
36
  fi
37
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
38
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
39
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
40
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
41
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
42
- RED="${RED:-\033[38;2;248;113;113m}"
43
- DIM="${DIM:-\033[2m}"
44
- BOLD="${BOLD:-\033[1m}"
45
- RESET="${RESET:-\033[0m}"
46
-
47
37
  # ─── Structured Event Log ──────────────────────────────────────────────────
48
38
  EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
49
39
 
@@ -182,7 +172,7 @@ cmd_delegate() {
182
172
  result: null
183
173
  }')
184
174
  tasks=$(echo "$tasks" | jq ". += [$task_json]")
185
- ((file_count++))
175
+ file_count=$((file_count + 1))
186
176
  done < <(echo "$changed_files")
187
177
 
188
178
  # Output delegation result
@@ -216,7 +206,7 @@ cmd_status() {
216
206
  for team_file in "$TEAM_STATE_DIR"/*.json; do
217
207
  [[ -f "$team_file" ]] || continue
218
208
  local ts
219
- ts=$(stat -f %Bm "$team_file" 2>/dev/null || stat -c %Y "$team_file" 2>/dev/null || echo 0)
209
+ ts=$(file_mtime "$team_file")
220
210
  local name
221
211
  name=$(basename "$team_file" .json)
222
212
  local status
@@ -255,7 +245,7 @@ cmd_status() {
255
245
  local spec_status
256
246
  spec_status=$(echo "$team_json" | jq -r ".specialist_status[$spec_idx] // \"pending\"" 2>/dev/null || echo "pending")
257
247
  printf " ${DIM}%-3d${RESET} %-20s %-15s\n" "$((spec_idx + 1))" "$spec" "$spec_status"
258
- ((spec_idx++))
248
+ spec_idx=$((spec_idx + 1))
259
249
  done < <(echo "$specs")
260
250
  echo ""
261
251
  }
@@ -292,11 +282,11 @@ cmd_vote() {
292
282
  local verdict
293
283
  verdict=$(echo "$team_json" | jq -r ".verdicts[\"$spec\"]? // \"neutral\"" 2>/dev/null || echo "neutral")
294
284
  case "$verdict" in
295
- approve) ((approve_count++)) ;;
296
- reject) ((reject_count++)) ;;
297
- *) ((neutral_count++)) ;;
285
+ approve) approve_count=$((approve_count + 1)) ;;
286
+ reject) reject_count=$((reject_count + 1)) ;;
287
+ *) neutral_count=$((neutral_count + 1)) ;;
298
288
  esac
299
- ((total++))
289
+ total=$((total + 1))
300
290
  done < <(echo "$specs")
301
291
 
302
292
  # Consensus: majority vote with leader tiebreak
@@ -370,9 +360,9 @@ cmd_aggregate() {
370
360
 
371
361
  if [[ -n "$result" ]]; then
372
362
  if echo "$result" | jq -e '.success' >/dev/null 2>&1; then
373
- ((success_count++))
363
+ success_count=$((success_count + 1))
374
364
  else
375
- ((failure_count++))
365
+ failure_count=$((failure_count + 1))
376
366
  fi
377
367
  results=$(echo "$results" | jq ". += [$result]")
378
368
  fi
@@ -5,7 +5,7 @@
5
5
  # ║ Templates define reusable agent team configurations (roles, layout, ║
6
6
  # ║ focus areas) that shipwright session --template can use to scaffold teams. ║
7
7
  # ╚═══════════════════════════════════════════════════════════════════════════╝
8
- VERSION="2.4.0"
8
+ VERSION="3.0.0"
9
9
  set -euo pipefail
10
10
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
11
 
@@ -35,16 +35,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
35
35
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
36
36
  }
37
37
  fi
38
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
39
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
40
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
41
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
42
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
43
- RED="${RED:-\033[38;2;248;113;113m}"
44
- DIM="${DIM:-\033[2m}"
45
- BOLD="${BOLD:-\033[1m}"
46
- RESET="${RESET:-\033[0m}"
47
-
48
38
  # ─── Template Discovery ─────────────────────────────────────────────────────
49
39
  REPO_TEMPLATES_DIR="$(cd "$SCRIPT_DIR/../tmux/templates" 2>/dev/null && pwd)" || REPO_TEMPLATES_DIR=""
50
40
  USER_TEMPLATES_DIR="${HOME}/.shipwright/templates"
@@ -79,7 +69,7 @@ find_template() {
79
69
  # Extract a top-level string field from JSON
80
70
  json_field() {
81
71
  local file="$1" field="$2"
82
- if command -v jq &>/dev/null; then
72
+ if command -v jq >/dev/null 2>&1; then
83
73
  jq -r ".${field} // \"\"" "$file" 2>/dev/null
84
74
  else
85
75
  grep -o "\"${field}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "$file" | head -1 | sed 's/.*: *"//;s/"$//'
@@ -89,7 +79,7 @@ json_field() {
89
79
  # Extract agent count from JSON
90
80
  json_agent_count() {
91
81
  local file="$1"
92
- if command -v jq &>/dev/null; then
82
+ if command -v jq >/dev/null 2>&1; then
93
83
  jq -r '.agents // [] | length' "$file" 2>/dev/null
94
84
  else
95
85
  grep -c '"name"' "$file" 2>/dev/null || echo "0"
@@ -99,7 +89,7 @@ json_agent_count() {
99
89
  # Print agent details from a template
100
90
  print_agents() {
101
91
  local file="$1"
102
- if command -v jq &>/dev/null; then
92
+ if command -v jq >/dev/null 2>&1; then
103
93
  jq -r '.agents // [] | .[] | "\(.name // "?")|\(.role // "")|\(.focus // "")"' "$file" 2>/dev/null
104
94
  else
105
95
  # Best-effort grep fallback for simple cases
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="2.4.0"
9
+ VERSION="3.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Handle subcommands ───────────────────────────────────────────────────────
@@ -38,16 +38,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
38
38
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
39
39
  }
40
40
  fi
41
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
42
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
43
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
44
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
45
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
46
- RED="${RED:-\033[38;2;248;113;113m}"
47
- DIM="${DIM:-\033[2m}"
48
- BOLD="${BOLD:-\033[1m}"
49
- RESET="${RESET:-\033[0m}"
50
-
51
41
  # ─── Configuration ───────────────────────────────────────────────────────────
52
42
  PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
53
43
  SCRIPTS_DIR="$PROJECT_ROOT/scripts"
@@ -210,7 +200,7 @@ EOF
210
200
  # Generate test template; use Claude for real assertions when available
211
201
  local test_template_file="$TESTGEN_DIR/generated-tests.sh"
212
202
  local use_claude="false"
213
- command -v claude &>/dev/null && use_claude="true"
203
+ command -v claude >/dev/null 2>&1 && use_claude="true"
214
204
  [[ "${TESTGEN_USE_CLAUDE:-true}" == "false" ]] && use_claude="false"
215
205
 
216
206
  {
@@ -468,7 +458,7 @@ detect_regressions() {
468
458
  [[ "$(basename "$test_file")" == "sw-testgen-test.sh" ]] && continue
469
459
 
470
460
  local result
471
- if bash "$test_file" &>/dev/null; then
461
+ if bash "$test_file" >/dev/null 2>&1; then
472
462
  result="PASS"
473
463
  else
474
464
  result="FAIL"
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="2.4.0"
9
+ VERSION="3.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -26,24 +26,6 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
26
26
  now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
27
27
  now_epoch() { date +%s; }
28
28
  fi
29
- if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
30
- emit_event() {
31
- local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
32
- local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
33
- while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
34
- echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
35
- }
36
- fi
37
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
38
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
39
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
40
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
41
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
42
- RED="${RED:-\033[38;2;248;113;113m}"
43
- DIM="${DIM:-\033[2m}"
44
- BOLD="${BOLD:-\033[1m}"
45
- RESET="${RESET:-\033[0m}"
46
-
47
29
  # Get daemon session name
48
30
  get_daemon_session() {
49
31
  echo "sw-daemon"
@@ -39,16 +39,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
39
39
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
40
40
  }
41
41
  fi
42
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
43
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
44
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
45
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
46
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
47
- RED="${RED:-\033[38;2;248;113;113m}"
48
- DIM="${DIM:-\033[2m}"
49
- BOLD="${BOLD:-\033[1m}"
50
- RESET="${RESET:-\033[0m}"
51
-
52
42
  # Get the active pane's title
53
43
  PANE_TITLE="$(tmux display-message -p '#{pane_title}' 2>/dev/null || echo "")"
54
44
 
@@ -9,6 +9,8 @@
9
9
  set -euo pipefail
10
10
 
11
11
  SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
12
+ # shellcheck source=lib/compat.sh
13
+ [[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
12
14
  # Canonical helpers (colors, output, events)
13
15
  # shellcheck source=lib/helpers.sh
14
16
  [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
@@ -29,16 +31,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
29
31
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
30
32
  }
31
33
  fi
32
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
33
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
34
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
35
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
36
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
37
- RED="${RED:-\033[38;2;248;113;113m}"
38
- DIM="${DIM:-\033[2m}"
39
- BOLD="${BOLD:-\033[1m}"
40
- RESET="${RESET:-\033[0m}"
41
-
42
34
  # ─── Stage colors (match Shipwright brand palette) ────────────────────────
43
35
  # Each pipeline stage gets a distinct color for instant visual recognition
44
36
  stage_color() {
@@ -128,7 +120,7 @@ agent_widget() {
128
120
  # Heartbeat is alive if updated within last 60 seconds
129
121
  local mtime
130
122
  if [[ "$(uname)" == "Darwin" ]]; then
131
- mtime="$(stat -f %m "$hb" 2>/dev/null || echo 0)"
123
+ mtime="$(file_mtime "$hb")"
132
124
  else
133
125
  mtime="$(stat -c %Y "$hb" 2>/dev/null || echo 0)"
134
126
  fi
@@ -11,7 +11,7 @@
11
11
  # ║ shipwright tmux fix — Auto-fix common issues ║
12
12
  # ║ shipwright tmux reload — Reload tmux config ║
13
13
  # ╚═══════════════════════════════════════════════════════════════════════════╝
14
- VERSION="2.4.0"
14
+ VERSION="3.0.0"
15
15
  set -euo pipefail
16
16
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
17
17
 
@@ -34,24 +34,6 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
34
34
  now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
35
35
  now_epoch() { date +%s; }
36
36
  fi
37
- if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
38
- emit_event() {
39
- local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
40
- local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
41
- while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
42
- echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
43
- }
44
- fi
45
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
46
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
47
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
48
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
49
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
50
- RED="${RED:-\033[38;2;248;113;113m}"
51
- DIM="${DIM:-\033[2m}"
52
- BOLD="${BOLD:-\033[1m}"
53
- RESET="${RESET:-\033[0m}"
54
-
55
37
  PASS=0
56
38
  WARN=0
57
39
  FAIL=0
@@ -73,7 +55,7 @@ tmux_doctor() {
73
55
 
74
56
  # ─── 1. tmux installed + version ─────────────────────────────────────
75
57
  echo -e "${BOLD}1. tmux Version${RESET}"
76
- if ! command -v tmux &>/dev/null; then
58
+ if ! command -v tmux >/dev/null 2>&1; then
77
59
  check_fail "tmux not installed"
78
60
  echo -e " ${DIM}brew install tmux (macOS)${RESET}"
79
61
  echo -e " ${DIM}sudo apt install tmux (Ubuntu/Debian)${RESET}"
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="2.4.0"
9
+ VERSION="3.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -26,24 +26,6 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
26
26
  now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
27
27
  now_epoch() { date +%s; }
28
28
  fi
29
- if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
30
- emit_event() {
31
- local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
32
- local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
33
- while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
34
- echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
35
- }
36
- fi
37
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
38
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
39
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
40
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
41
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
42
- RED="${RED:-\033[38;2;248;113;113m}"
43
- DIM="${DIM:-\033[2m}"
44
- BOLD="${BOLD:-\033[1m}"
45
- RESET="${RESET:-\033[0m}"
46
-
47
29
  # ─── Data Paths ─────────────────────────────────────────────────────────────
48
30
  EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
49
31
  SHIPWRIGHT_DIR="${REPO_DIR}/.claude/pipeline-artifacts"
@@ -28,16 +28,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
28
28
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
29
29
  }
30
30
  fi
31
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
32
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
33
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
34
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
35
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
36
- RED="${RED:-\033[38;2;248;113;113m}"
37
- DIM="${DIM:-\033[2m}"
38
- BOLD="${BOLD:-\033[1m}"
39
- RESET="${RESET:-\033[0m}"
40
-
41
31
  # ─── Discovery & CRUD Interface ────────────────────────────────────────────
42
32
  # All functions output normalized JSON (or plain text where specified).
43
33
  # Input: normalized arguments (label, state, issue_id, etc.)
@@ -28,16 +28,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
28
28
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
29
29
  }
30
30
  fi
31
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
32
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
33
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
34
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
35
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
36
- RED="${RED:-\033[38;2;248;113;113m}"
37
- DIM="${DIM:-\033[2m}"
38
- BOLD="${BOLD:-\033[1m}"
39
- RESET="${RESET:-\033[0m}"
40
-
41
31
  # ─── Status Auto-Discovery ────────────────────────────────────────────────
42
32
  # Queries Jira API for project statuses and caches the transition name mapping.
43
33
  # Only fills in JIRA_TRANSITION_* values that are empty (config/env takes priority).
@@ -158,7 +148,7 @@ jira_api() {
158
148
  local auth
159
149
  auth=$(printf '%s:%s' "$JIRA_EMAIL" "$JIRA_API_TOKEN" | base64)
160
150
 
161
- local args=(-sf -X "$method" \
151
+ local args=(-sf --connect-timeout 10 --max-time 30 -X "$method" \
162
152
  -H "Authorization: Basic $auth" \
163
153
  -H "Content-Type: application/json")
164
154
 
@@ -28,16 +28,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
28
28
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
29
29
  }
30
30
  fi
31
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
32
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
33
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
34
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
35
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
36
- RED="${RED:-\033[38;2;248;113;113m}"
37
- DIM="${DIM:-\033[2m}"
38
- BOLD="${BOLD:-\033[1m}"
39
- RESET="${RESET:-\033[0m}"
40
-
41
31
  # ─── Status Auto-Discovery ────────────────────────────────────────────────
42
32
  # Queries Linear API for workflow states and caches the mapping.
43
33
  # Only fills in STATUS_* values that are empty (config/env takes priority).
@@ -175,7 +165,7 @@ linear_graphql() {
175
165
  payload=$(jq -n --arg q "$query" --argjson v "$variables" '{query: $q, variables: $v}')
176
166
 
177
167
  local response
178
- response=$(curl -sf -X POST "$LINEAR_API" \
168
+ response=$(curl -sf --connect-timeout 10 --max-time 30 -X POST "$LINEAR_API" \
179
169
  -H "Authorization: $LINEAR_API_KEY" \
180
170
  -H "Content-Type: application/json" \
181
171
  -d "$payload" 2>&1) || {