shipwright-cli 2.3.1 → 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 (162) hide show
  1. package/README.md +95 -28
  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 +155 -2
  8. package/config/policy.schema.json +162 -1
  9. package/dashboard/coverage/coverage-summary.json +14 -0
  10. package/dashboard/public/index.html +1 -1
  11. package/dashboard/server.ts +306 -17
  12. package/dashboard/src/components/charts/bar.test.ts +79 -0
  13. package/dashboard/src/components/charts/donut.test.ts +68 -0
  14. package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
  15. package/dashboard/src/components/charts/sparkline.test.ts +125 -0
  16. package/dashboard/src/core/api.test.ts +309 -0
  17. package/dashboard/src/core/helpers.test.ts +301 -0
  18. package/dashboard/src/core/router.test.ts +307 -0
  19. package/dashboard/src/core/router.ts +7 -0
  20. package/dashboard/src/core/sse.test.ts +144 -0
  21. package/dashboard/src/views/metrics.test.ts +186 -0
  22. package/dashboard/src/views/overview.test.ts +173 -0
  23. package/dashboard/src/views/pipelines.test.ts +183 -0
  24. package/dashboard/src/views/team.test.ts +253 -0
  25. package/dashboard/vitest.config.ts +14 -5
  26. package/docs/TIPS.md +1 -1
  27. package/docs/patterns/README.md +1 -1
  28. package/package.json +15 -5
  29. package/scripts/adapters/docker-deploy.sh +1 -1
  30. package/scripts/adapters/tmux-adapter.sh +11 -1
  31. package/scripts/adapters/wezterm-adapter.sh +1 -1
  32. package/scripts/check-version-consistency.sh +1 -1
  33. package/scripts/lib/architecture.sh +126 -0
  34. package/scripts/lib/bootstrap.sh +75 -0
  35. package/scripts/lib/compat.sh +89 -6
  36. package/scripts/lib/config.sh +91 -0
  37. package/scripts/lib/daemon-adaptive.sh +3 -3
  38. package/scripts/lib/daemon-dispatch.sh +39 -16
  39. package/scripts/lib/daemon-health.sh +1 -1
  40. package/scripts/lib/daemon-patrol.sh +24 -12
  41. package/scripts/lib/daemon-poll.sh +37 -25
  42. package/scripts/lib/daemon-state.sh +115 -23
  43. package/scripts/lib/daemon-triage.sh +30 -8
  44. package/scripts/lib/fleet-failover.sh +63 -0
  45. package/scripts/lib/helpers.sh +30 -6
  46. package/scripts/lib/pipeline-detection.sh +2 -2
  47. package/scripts/lib/pipeline-github.sh +9 -9
  48. package/scripts/lib/pipeline-intelligence.sh +85 -35
  49. package/scripts/lib/pipeline-quality-checks.sh +16 -16
  50. package/scripts/lib/pipeline-quality.sh +1 -1
  51. package/scripts/lib/pipeline-stages.sh +242 -28
  52. package/scripts/lib/pipeline-state.sh +40 -4
  53. package/scripts/lib/test-helpers.sh +247 -0
  54. package/scripts/postinstall.mjs +3 -11
  55. package/scripts/sw +10 -4
  56. package/scripts/sw-activity.sh +1 -11
  57. package/scripts/sw-adaptive.sh +109 -85
  58. package/scripts/sw-adversarial.sh +4 -14
  59. package/scripts/sw-architecture-enforcer.sh +1 -11
  60. package/scripts/sw-auth.sh +8 -17
  61. package/scripts/sw-autonomous.sh +111 -49
  62. package/scripts/sw-changelog.sh +1 -11
  63. package/scripts/sw-checkpoint.sh +144 -20
  64. package/scripts/sw-ci.sh +2 -12
  65. package/scripts/sw-cleanup.sh +13 -17
  66. package/scripts/sw-code-review.sh +16 -36
  67. package/scripts/sw-connect.sh +5 -12
  68. package/scripts/sw-context.sh +9 -26
  69. package/scripts/sw-cost.sh +6 -16
  70. package/scripts/sw-daemon.sh +75 -70
  71. package/scripts/sw-dashboard.sh +57 -17
  72. package/scripts/sw-db.sh +506 -15
  73. package/scripts/sw-decompose.sh +1 -11
  74. package/scripts/sw-deps.sh +15 -25
  75. package/scripts/sw-developer-simulation.sh +1 -11
  76. package/scripts/sw-discovery.sh +112 -30
  77. package/scripts/sw-doc-fleet.sh +7 -17
  78. package/scripts/sw-docs-agent.sh +6 -16
  79. package/scripts/sw-docs.sh +4 -12
  80. package/scripts/sw-doctor.sh +134 -43
  81. package/scripts/sw-dora.sh +11 -19
  82. package/scripts/sw-durable.sh +35 -52
  83. package/scripts/sw-e2e-orchestrator.sh +11 -27
  84. package/scripts/sw-eventbus.sh +115 -115
  85. package/scripts/sw-evidence.sh +748 -0
  86. package/scripts/sw-feedback.sh +3 -13
  87. package/scripts/sw-fix.sh +2 -20
  88. package/scripts/sw-fleet-discover.sh +1 -11
  89. package/scripts/sw-fleet-viz.sh +10 -18
  90. package/scripts/sw-fleet.sh +13 -17
  91. package/scripts/sw-github-app.sh +6 -16
  92. package/scripts/sw-github-checks.sh +1 -11
  93. package/scripts/sw-github-deploy.sh +1 -11
  94. package/scripts/sw-github-graphql.sh +2 -12
  95. package/scripts/sw-guild.sh +1 -11
  96. package/scripts/sw-heartbeat.sh +49 -12
  97. package/scripts/sw-hygiene.sh +45 -43
  98. package/scripts/sw-incident.sh +284 -67
  99. package/scripts/sw-init.sh +35 -37
  100. package/scripts/sw-instrument.sh +1 -11
  101. package/scripts/sw-intelligence.sh +362 -51
  102. package/scripts/sw-jira.sh +5 -14
  103. package/scripts/sw-launchd.sh +2 -12
  104. package/scripts/sw-linear.sh +8 -17
  105. package/scripts/sw-logs.sh +4 -12
  106. package/scripts/sw-loop.sh +641 -90
  107. package/scripts/sw-memory.sh +243 -17
  108. package/scripts/sw-mission-control.sh +2 -12
  109. package/scripts/sw-model-router.sh +73 -34
  110. package/scripts/sw-otel.sh +11 -21
  111. package/scripts/sw-oversight.sh +1 -11
  112. package/scripts/sw-patrol-meta.sh +5 -11
  113. package/scripts/sw-pipeline-composer.sh +7 -17
  114. package/scripts/sw-pipeline-vitals.sh +1 -11
  115. package/scripts/sw-pipeline.sh +478 -122
  116. package/scripts/sw-pm.sh +2 -12
  117. package/scripts/sw-pr-lifecycle.sh +203 -29
  118. package/scripts/sw-predictive.sh +16 -22
  119. package/scripts/sw-prep.sh +6 -16
  120. package/scripts/sw-ps.sh +1 -11
  121. package/scripts/sw-public-dashboard.sh +2 -12
  122. package/scripts/sw-quality.sh +77 -10
  123. package/scripts/sw-reaper.sh +1 -11
  124. package/scripts/sw-recruit.sh +15 -25
  125. package/scripts/sw-regression.sh +11 -21
  126. package/scripts/sw-release-manager.sh +19 -28
  127. package/scripts/sw-release.sh +8 -16
  128. package/scripts/sw-remote.sh +1 -11
  129. package/scripts/sw-replay.sh +48 -44
  130. package/scripts/sw-retro.sh +70 -92
  131. package/scripts/sw-review-rerun.sh +220 -0
  132. package/scripts/sw-scale.sh +109 -32
  133. package/scripts/sw-security-audit.sh +12 -22
  134. package/scripts/sw-self-optimize.sh +239 -23
  135. package/scripts/sw-session.sh +3 -13
  136. package/scripts/sw-setup.sh +8 -18
  137. package/scripts/sw-standup.sh +5 -15
  138. package/scripts/sw-status.sh +32 -23
  139. package/scripts/sw-strategic.sh +129 -13
  140. package/scripts/sw-stream.sh +1 -11
  141. package/scripts/sw-swarm.sh +76 -36
  142. package/scripts/sw-team-stages.sh +10 -20
  143. package/scripts/sw-templates.sh +4 -14
  144. package/scripts/sw-testgen.sh +3 -13
  145. package/scripts/sw-tmux-pipeline.sh +1 -19
  146. package/scripts/sw-tmux-role-color.sh +0 -10
  147. package/scripts/sw-tmux-status.sh +3 -11
  148. package/scripts/sw-tmux.sh +2 -20
  149. package/scripts/sw-trace.sh +1 -19
  150. package/scripts/sw-tracker-github.sh +0 -10
  151. package/scripts/sw-tracker-jira.sh +1 -11
  152. package/scripts/sw-tracker-linear.sh +1 -11
  153. package/scripts/sw-tracker.sh +7 -24
  154. package/scripts/sw-triage.sh +24 -34
  155. package/scripts/sw-upgrade.sh +5 -23
  156. package/scripts/sw-ux.sh +1 -19
  157. package/scripts/sw-webhook.sh +18 -32
  158. package/scripts/sw-widgets.sh +3 -21
  159. package/scripts/sw-worktree.sh +11 -27
  160. package/scripts/update-homebrew-sha.sh +67 -0
  161. package/templates/pipelines/tdd.json +72 -0
  162. 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.3.1"
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
  # ─── Storage Paths ──────────────────────────────────────────────────────────
48
38
  INCIDENTS_FILE="${HOME}/.shipwright/incidents.jsonl"
49
39
  ERROR_THRESHOLD=5 # Create issue if error count >= threshold
@@ -262,7 +252,7 @@ git show $regression_commit
262
252
  EOF
263
253
  )
264
254
 
265
- if ! command -v gh &>/dev/null; then
255
+ if ! command -v gh >/dev/null 2>&1; then
266
256
  error "gh CLI not found — cannot create issue"
267
257
  return 1
268
258
  fi
@@ -275,7 +265,7 @@ EOF
275
265
  --body "$issue_body" \
276
266
  --label "shipwright" \
277
267
  --label "hotfix" \
278
- 2>&1 | tail -1)
268
+ 2>&1 | tail -1) || true
279
269
 
280
270
  if [[ -n "$issue_url" ]]; then
281
271
  success "Created issue: $issue_url"
package/scripts/sw-fix.sh CHANGED
@@ -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.3.1"
9
+ VERSION="3.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -25,24 +25,6 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
25
25
  now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
26
26
  now_epoch() { date +%s; }
27
27
  fi
28
- if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
29
- emit_event() {
30
- local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
31
- local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
32
- while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
33
- echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
34
- }
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
28
  format_duration() {
47
29
  local secs="$1"
48
30
  if [[ "$secs" -ge 3600 ]]; then
@@ -341,7 +323,7 @@ fix_start() {
341
323
 
342
324
  # Spawn pipeline in subshell
343
325
  (
344
- cd "$expanded"
326
+ cd "$expanded" || exit 1
345
327
 
346
328
  # Determine base branch
347
329
  local base
@@ -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.3.1"
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
  # ─── Help ───────────────────────────────────────────────────────────────────
48
38
 
49
39
  show_help() {
@@ -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.3.1"
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
 
@@ -19,6 +19,8 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
19
19
  [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
20
20
  # Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
21
21
  [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
22
+ # Color fallbacks when helpers not loaded
23
+ : "${CYAN:=}" "${BOLD:=}" "${RESET:=}" "${DIM:=}" "${GREEN:=}" "${RED:=}" "${YELLOW:=}" "${PURPLE:=}" "${WHITE:=}" "${BLUE:=}"
22
24
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
23
25
  [[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
24
26
  [[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
@@ -34,16 +36,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
34
36
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
35
37
  }
36
38
  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
39
  format_duration() {
48
40
  local secs="$1"
49
41
  if [[ "$secs" -ge 3600 ]]; then
@@ -70,7 +62,7 @@ get_health_status() {
70
62
  # degraded (yellow) = some failures but recovering
71
63
  # failing (red) = persistent failures
72
64
 
73
- if ! command -v jq &>/dev/null; then
65
+ if ! command -v jq >/dev/null 2>&1; then
74
66
  echo "unknown"
75
67
  return
76
68
  fi
@@ -98,7 +90,7 @@ color_health() {
98
90
 
99
91
  # ─── Overview Subcommand ───────────────────────────────────────────────────
100
92
  show_overview() {
101
- if ! command -v jq &>/dev/null; then
93
+ if ! command -v jq >/dev/null 2>&1; then
102
94
  error "jq is required for fleet visualization"
103
95
  exit 1
104
96
  fi
@@ -144,7 +136,7 @@ show_overview() {
144
136
 
145
137
  # ─── Workers Subcommand ────────────────────────────────────────────────────
146
138
  show_workers() {
147
- if ! command -v jq &>/dev/null; then
139
+ if ! command -v jq >/dev/null 2>&1; then
148
140
  error "jq is required for fleet visualization"
149
141
  exit 1
150
142
  fi
@@ -197,7 +189,7 @@ show_workers() {
197
189
 
198
190
  # ─── Insights Subcommand ───────────────────────────────────────────────────
199
191
  show_insights() {
200
- if ! command -v jq &>/dev/null; then
192
+ if ! command -v jq >/dev/null 2>&1; then
201
193
  error "jq is required for fleet visualization"
202
194
  exit 1
203
195
  fi
@@ -238,7 +230,7 @@ show_insights() {
238
230
 
239
231
  # ─── Queue Subcommand ──────────────────────────────────────────────────────
240
232
  show_queue() {
241
- if ! command -v jq &>/dev/null; then
233
+ if ! command -v jq >/dev/null 2>&1; then
242
234
  error "jq is required for fleet visualization"
243
235
  exit 1
244
236
  fi
@@ -281,7 +273,7 @@ show_queue() {
281
273
 
282
274
  # ─── Costs Subcommand ──────────────────────────────────────────────────────
283
275
  show_costs() {
284
- if ! command -v jq &>/dev/null; then
276
+ if ! command -v jq >/dev/null 2>&1; then
285
277
  error "jq is required for fleet visualization"
286
278
  exit 1
287
279
  fi
@@ -323,7 +315,7 @@ show_costs() {
323
315
 
324
316
  # ─── Export Subcommand ─────────────────────────────────────────────────────
325
317
  show_export() {
326
- if ! command -v jq &>/dev/null; then
318
+ if ! command -v jq >/dev/null 2>&1; then
327
319
  error "jq is required for fleet visualization"
328
320
  exit 1
329
321
  fi
@@ -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.3.1"
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
  epoch_to_iso() {
48
38
  local epoch="$1"
49
39
  date -u -r "$epoch" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
@@ -200,7 +190,7 @@ _fleet_repo_priority() {
200
190
  local repo_path="$1"
201
191
  local priority=50 # default neutral priority
202
192
 
203
- type _gh_detect_repo &>/dev/null 2>&1 || { echo "$priority"; return 0; }
193
+ type _gh_detect_repo >/dev/null 2>&1 || { echo "$priority"; return 0; }
204
194
 
205
195
  # Detect repo from the repo path (run in subshell to avoid cd side-effects)
206
196
  local gh_priority
@@ -213,7 +203,7 @@ _fleet_repo_priority() {
213
203
  local p=50
214
204
 
215
205
  # Factor: security alerts (urgent work)
216
- if type gh_security_alerts &>/dev/null 2>&1; then
206
+ if type gh_security_alerts >/dev/null 2>&1; then
217
207
  local alerts
218
208
  alerts=$(gh_security_alerts "$owner" "$repo" 2>/dev/null | jq 'length' 2>/dev/null || echo "0")
219
209
  if [[ "${alerts:-0}" -gt 5 ]]; then
@@ -224,7 +214,7 @@ _fleet_repo_priority() {
224
214
  fi
225
215
 
226
216
  # Factor: contributor count (more contributors = more active = higher priority)
227
- if type gh_contributors &>/dev/null 2>&1; then
217
+ if type gh_contributors >/dev/null 2>&1; then
228
218
  local contribs
229
219
  contribs=$(gh_contributors "$owner" "$repo" 2>/dev/null | jq 'length' 2>/dev/null || echo "0")
230
220
  if [[ "${contribs:-0}" -gt 10 ]]; then
@@ -643,6 +633,12 @@ check_machine_health() {
643
633
  emit_event "fleet.machine_offline" "machine=$name" "host=$host"
644
634
  fi
645
635
  done
636
+
637
+ # After health checks: trigger failover to re-queue work from offline machines
638
+ if [[ -f "$SCRIPT_DIR/lib/fleet-failover.sh" ]]; then
639
+ source "$SCRIPT_DIR/lib/fleet-failover.sh" 2>/dev/null || true
640
+ fleet_failover_check 2>/dev/null || true
641
+ fi
646
642
  }
647
643
 
648
644
  # ─── Cross-Machine Event Aggregation ───────────────────────────────────
@@ -749,12 +745,12 @@ fleet_start() {
749
745
  echo -e "${PURPLE}${BOLD}━━━ shipwright fleet v${VERSION} — start ━━━${RESET}"
750
746
  echo ""
751
747
 
752
- if ! command -v tmux &>/dev/null; then
748
+ if ! command -v tmux >/dev/null 2>&1; then
753
749
  error "tmux is required for fleet mode"
754
750
  exit 1
755
751
  fi
756
752
 
757
- if ! command -v jq &>/dev/null; then
753
+ if ! command -v jq >/dev/null 2>&1; then
758
754
  error "jq is required. Install: brew install jq"
759
755
  exit 1
760
756
  fi
@@ -1146,7 +1142,7 @@ fleet_metrics() {
1146
1142
  exit 1
1147
1143
  fi
1148
1144
 
1149
- if ! command -v jq &>/dev/null; then
1145
+ if ! command -v jq >/dev/null 2>&1; then
1150
1146
  error "jq is required. Install: brew install jq"
1151
1147
  exit 1
1152
1148
  fi
@@ -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.3.1"
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
  # ─── Config File Locations ────────────────────────────────────────────────
48
38
  CONFIG_DIR="${HOME}/.shipwright"
49
39
  CONFIG_FILE="${CONFIG_DIR}/github-app.json"
@@ -76,16 +66,16 @@ cmd_setup() {
76
66
  info "GitHub App Configuration"
77
67
  echo ""
78
68
 
79
- read -p "App ID: " app_id
80
- read -p "Private key file path: " key_path
69
+ read -rp "App ID: " app_id
70
+ read -rp "Private key file path: " key_path
81
71
 
82
72
  if [[ ! -f "$key_path" ]]; then
83
73
  error "Private key file not found: $key_path"
84
74
  return 1
85
75
  fi
86
76
 
87
- read -p "Installation ID: " installation_id
88
- read -p "Webhook secret (optional, press Enter to skip): " webhook_secret
77
+ read -rp "Installation ID: " installation_id
78
+ read -rp "Webhook secret (optional, press Enter to skip): " webhook_secret
89
79
 
90
80
  # Create config atomically
91
81
  local tmp_config
@@ -171,7 +161,7 @@ _get_installation_token() {
171
161
  fi
172
162
 
173
163
  local response
174
- response=$(curl -s -H "Authorization: Bearer $jwt" \
164
+ response=$(curl -s --connect-timeout 10 --max-time 30 -H "Authorization: Bearer $jwt" \
175
165
  -H "Accept: application/vnd.github+json" \
176
166
  "https://api.github.com/app/installations/${installation_id}/access_tokens" \
177
167
  -d '{}' -X POST 2>/dev/null) || true
@@ -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.3.1"
9
+ VERSION="3.0.0"
10
10
  SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
11
11
  REPO_DIR="${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
  # ─── Artifacts Directory ─────────────────────────────────────────────────────
48
38
  ARTIFACTS_DIR="${REPO_DIR}/.claude/pipeline-artifacts"
49
39
 
@@ -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.3.1"
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
  # ─── Artifacts Directory ─────────────────────────────────────────────────────
48
38
  ARTIFACTS_DIR="${REPO_DIR}/.claude/pipeline-artifacts"
49
39
 
@@ -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.3.1"
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
 
@@ -87,7 +77,7 @@ _gh_cache_get() {
87
77
  # Check file age
88
78
  local file_epoch now
89
79
  if [[ "$(uname)" == "Darwin" ]]; then
90
- file_epoch=$(stat -f '%m' "$cache_file" 2>/dev/null || echo "0")
80
+ file_epoch=$(file_mtime "$cache_file")
91
81
  else
92
82
  file_epoch=$(stat -c '%Y' "$cache_file" 2>/dev/null || echo "0")
93
83
  fi
@@ -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.3.1"
9
+ VERSION="3.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -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
  # ─── Guild Storage Paths ───────────────────────────────────────────────────
47
37
  GUILD_ROOT="${HOME}/.shipwright/guilds"
48
38
  GUILD_CONFIG="${GUILD_ROOT}/config.json"
@@ -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.3.1"
9
+ VERSION="3.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -16,6 +16,10 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
16
  # Canonical helpers (colors, output, events)
17
17
  # shellcheck source=lib/helpers.sh
18
18
  [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
19
+
20
+ # SQLite persistence (DB as primary read path)
21
+ # shellcheck source=sw-db.sh
22
+ [[ -f "$SCRIPT_DIR/sw-db.sh" ]] && source "$SCRIPT_DIR/sw-db.sh"
19
23
  # Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
20
24
  [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
21
25
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
@@ -33,16 +37,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
33
37
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
34
38
  }
35
39
  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
40
  # ─── Constants ──────────────────────────────────────────────────────────────
47
41
  HEARTBEAT_DIR="$HOME/.shipwright/heartbeats"
48
42
 
@@ -167,6 +161,11 @@ cmd_write() {
167
161
 
168
162
  # Atomic write
169
163
  mv "$tmp_file" "${HEARTBEAT_DIR}/${job_id}.json"
164
+
165
+ # Dual-write to DB when available
166
+ if type db_record_heartbeat >/dev/null 2>&1 && db_available 2>/dev/null; then
167
+ db_record_heartbeat "$job_id" "$pid" "${issue:-0}" "$stage" "${iteration:-0}" "$activity" "$memory_mb" 2>/dev/null || true
168
+ fi
170
169
  }
171
170
 
172
171
  # ─── Check Heartbeat ───────────────────────────────────────────────────────
@@ -190,6 +189,30 @@ cmd_check() {
190
189
  esac
191
190
  done
192
191
 
192
+ # Try DB first when available
193
+ if type db_list_heartbeats >/dev/null 2>&1 && db_available 2>/dev/null; then
194
+ local hb_json
195
+ hb_json=$(db_list_heartbeats 2>/dev/null | jq -r --arg id "$job_id" '.[] | select(.job_id == $id) | .updated_at' 2>/dev/null || true)
196
+ if [[ -n "$hb_json" ]]; then
197
+ local updated_at="$hb_json"
198
+ local hb_epoch now_epoch age_secs
199
+ if TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$updated_at" +%s >/dev/null 2>&1; then
200
+ hb_epoch="$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$updated_at" +%s 2>/dev/null)"
201
+ else
202
+ hb_epoch="$(date -d "$updated_at" +%s 2>/dev/null || echo 0)"
203
+ fi
204
+ now_epoch="$(date +%s)"
205
+ age_secs=$((now_epoch - hb_epoch))
206
+ if [[ "$age_secs" -le "$timeout" ]]; then
207
+ success "Job ${job_id} alive (${age_secs}s ago)"
208
+ return 0
209
+ else
210
+ warn "Job ${job_id} stale (${age_secs}s ago, timeout: ${timeout}s)"
211
+ return 1
212
+ fi
213
+ fi
214
+ fi
215
+
193
216
  local hb_file="${HEARTBEAT_DIR}/${job_id}.json"
194
217
 
195
218
  if [[ ! -f "$hb_file" ]]; then
@@ -209,7 +232,7 @@ cmd_check() {
209
232
  local hb_epoch now_epoch age_secs
210
233
 
211
234
  # macOS date -j -f vs GNU date -d (TZ=UTC since timestamps are UTC)
212
- if TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$updated_at" +%s &>/dev/null; then
235
+ if TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$updated_at" +%s >/dev/null 2>&1; then
213
236
  hb_epoch="$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$updated_at" +%s 2>/dev/null)"
214
237
  else
215
238
  hb_epoch="$(date -d "$updated_at" +%s 2>/dev/null || echo 0)"
@@ -231,6 +254,16 @@ cmd_check() {
231
254
  cmd_list() {
232
255
  ensure_dir
233
256
 
257
+ # Try DB first when available
258
+ if type db_list_heartbeats >/dev/null 2>&1 && db_available 2>/dev/null; then
259
+ local db_result
260
+ db_result=$(db_list_heartbeats 2>/dev/null || echo "[]")
261
+ if [[ -n "$db_result" ]] && echo "$db_result" | jq -e 'length > 0' >/dev/null 2>&1; then
262
+ echo "$db_result" | jq -c '[.[] | . + {cpu_pct: 0}]' 2>/dev/null || echo "[]"
263
+ return 0
264
+ fi
265
+ fi
266
+
234
267
  local result="["
235
268
  local first=true
236
269
 
@@ -272,6 +305,10 @@ cmd_clear() {
272
305
 
273
306
  local hb_file="${HEARTBEAT_DIR}/${job_id}.json"
274
307
 
308
+ if type db_clear_heartbeat >/dev/null 2>&1 && db_available 2>/dev/null; then
309
+ db_clear_heartbeat "$job_id" 2>/dev/null || true
310
+ fi
311
+
275
312
  if [[ -f "$hb_file" ]]; then
276
313
  rm -f "$hb_file"
277
314
  success "Cleared heartbeat for job: ${job_id}"