shipwright-cli 2.4.0 → 3.1.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 (169) hide show
  1. package/README.md +16 -11
  2. package/completions/_shipwright +248 -94
  3. package/completions/shipwright.bash +68 -19
  4. package/completions/shipwright.fish +310 -42
  5. package/config/decision-tiers.json +55 -0
  6. package/config/defaults.json +111 -0
  7. package/config/event-schema.json +218 -0
  8. package/config/policy.json +21 -18
  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 +7 -9
  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 +127 -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 +63 -17
  39. package/scripts/lib/daemon-failure.sh +0 -0
  40. package/scripts/lib/daemon-health.sh +1 -1
  41. package/scripts/lib/daemon-patrol.sh +64 -17
  42. package/scripts/lib/daemon-poll.sh +54 -25
  43. package/scripts/lib/daemon-state.sh +125 -23
  44. package/scripts/lib/daemon-triage.sh +31 -9
  45. package/scripts/lib/decide-autonomy.sh +295 -0
  46. package/scripts/lib/decide-scoring.sh +228 -0
  47. package/scripts/lib/decide-signals.sh +462 -0
  48. package/scripts/lib/fleet-failover.sh +63 -0
  49. package/scripts/lib/helpers.sh +29 -6
  50. package/scripts/lib/pipeline-detection.sh +2 -2
  51. package/scripts/lib/pipeline-github.sh +9 -9
  52. package/scripts/lib/pipeline-intelligence.sh +105 -38
  53. package/scripts/lib/pipeline-quality-checks.sh +17 -16
  54. package/scripts/lib/pipeline-quality.sh +1 -1
  55. package/scripts/lib/pipeline-stages.sh +440 -59
  56. package/scripts/lib/pipeline-state.sh +54 -4
  57. package/scripts/lib/policy.sh +0 -0
  58. package/scripts/lib/test-helpers.sh +247 -0
  59. package/scripts/postinstall.mjs +78 -12
  60. package/scripts/signals/example-collector.sh +36 -0
  61. package/scripts/sw +17 -7
  62. package/scripts/sw-activity.sh +1 -11
  63. package/scripts/sw-adaptive.sh +109 -85
  64. package/scripts/sw-adversarial.sh +4 -14
  65. package/scripts/sw-architecture-enforcer.sh +1 -11
  66. package/scripts/sw-auth.sh +8 -17
  67. package/scripts/sw-autonomous.sh +111 -49
  68. package/scripts/sw-changelog.sh +1 -11
  69. package/scripts/sw-checkpoint.sh +144 -20
  70. package/scripts/sw-ci.sh +2 -12
  71. package/scripts/sw-cleanup.sh +13 -17
  72. package/scripts/sw-code-review.sh +16 -36
  73. package/scripts/sw-connect.sh +5 -12
  74. package/scripts/sw-context.sh +9 -26
  75. package/scripts/sw-cost.sh +17 -18
  76. package/scripts/sw-daemon.sh +76 -71
  77. package/scripts/sw-dashboard.sh +57 -17
  78. package/scripts/sw-db.sh +524 -26
  79. package/scripts/sw-decide.sh +685 -0
  80. package/scripts/sw-decompose.sh +1 -11
  81. package/scripts/sw-deps.sh +15 -25
  82. package/scripts/sw-developer-simulation.sh +1 -11
  83. package/scripts/sw-discovery.sh +138 -30
  84. package/scripts/sw-doc-fleet.sh +7 -17
  85. package/scripts/sw-docs-agent.sh +6 -16
  86. package/scripts/sw-docs.sh +4 -12
  87. package/scripts/sw-doctor.sh +134 -43
  88. package/scripts/sw-dora.sh +11 -19
  89. package/scripts/sw-durable.sh +35 -52
  90. package/scripts/sw-e2e-orchestrator.sh +11 -27
  91. package/scripts/sw-eventbus.sh +115 -115
  92. package/scripts/sw-evidence.sh +114 -30
  93. package/scripts/sw-feedback.sh +3 -13
  94. package/scripts/sw-fix.sh +2 -20
  95. package/scripts/sw-fleet-discover.sh +1 -11
  96. package/scripts/sw-fleet-viz.sh +10 -18
  97. package/scripts/sw-fleet.sh +13 -17
  98. package/scripts/sw-github-app.sh +6 -16
  99. package/scripts/sw-github-checks.sh +1 -11
  100. package/scripts/sw-github-deploy.sh +1 -11
  101. package/scripts/sw-github-graphql.sh +2 -12
  102. package/scripts/sw-guild.sh +1 -11
  103. package/scripts/sw-heartbeat.sh +49 -12
  104. package/scripts/sw-hygiene.sh +45 -43
  105. package/scripts/sw-incident.sh +48 -74
  106. package/scripts/sw-init.sh +35 -37
  107. package/scripts/sw-instrument.sh +1 -11
  108. package/scripts/sw-intelligence.sh +368 -53
  109. package/scripts/sw-jira.sh +5 -14
  110. package/scripts/sw-launchd.sh +2 -12
  111. package/scripts/sw-linear.sh +8 -17
  112. package/scripts/sw-logs.sh +4 -12
  113. package/scripts/sw-loop.sh +905 -104
  114. package/scripts/sw-memory.sh +263 -20
  115. package/scripts/sw-mission-control.sh +2 -12
  116. package/scripts/sw-model-router.sh +73 -34
  117. package/scripts/sw-otel.sh +15 -23
  118. package/scripts/sw-oversight.sh +1 -11
  119. package/scripts/sw-patrol-meta.sh +5 -11
  120. package/scripts/sw-pipeline-composer.sh +7 -17
  121. package/scripts/sw-pipeline-vitals.sh +1 -11
  122. package/scripts/sw-pipeline.sh +550 -122
  123. package/scripts/sw-pm.sh +2 -12
  124. package/scripts/sw-pr-lifecycle.sh +33 -28
  125. package/scripts/sw-predictive.sh +16 -22
  126. package/scripts/sw-prep.sh +6 -16
  127. package/scripts/sw-ps.sh +1 -11
  128. package/scripts/sw-public-dashboard.sh +2 -12
  129. package/scripts/sw-quality.sh +85 -14
  130. package/scripts/sw-reaper.sh +1 -11
  131. package/scripts/sw-recruit.sh +15 -25
  132. package/scripts/sw-regression.sh +11 -21
  133. package/scripts/sw-release-manager.sh +19 -28
  134. package/scripts/sw-release.sh +8 -16
  135. package/scripts/sw-remote.sh +1 -11
  136. package/scripts/sw-replay.sh +48 -44
  137. package/scripts/sw-retro.sh +70 -92
  138. package/scripts/sw-review-rerun.sh +1 -1
  139. package/scripts/sw-scale.sh +174 -41
  140. package/scripts/sw-security-audit.sh +12 -22
  141. package/scripts/sw-self-optimize.sh +239 -23
  142. package/scripts/sw-session.sh +5 -15
  143. package/scripts/sw-setup.sh +8 -18
  144. package/scripts/sw-standup.sh +5 -15
  145. package/scripts/sw-status.sh +32 -23
  146. package/scripts/sw-strategic.sh +129 -13
  147. package/scripts/sw-stream.sh +1 -11
  148. package/scripts/sw-swarm.sh +76 -36
  149. package/scripts/sw-team-stages.sh +10 -20
  150. package/scripts/sw-templates.sh +4 -14
  151. package/scripts/sw-testgen.sh +3 -13
  152. package/scripts/sw-tmux-pipeline.sh +1 -19
  153. package/scripts/sw-tmux-role-color.sh +0 -10
  154. package/scripts/sw-tmux-status.sh +3 -11
  155. package/scripts/sw-tmux.sh +2 -20
  156. package/scripts/sw-trace.sh +1 -19
  157. package/scripts/sw-tracker-github.sh +0 -10
  158. package/scripts/sw-tracker-jira.sh +1 -11
  159. package/scripts/sw-tracker-linear.sh +1 -11
  160. package/scripts/sw-tracker.sh +7 -24
  161. package/scripts/sw-triage.sh +29 -39
  162. package/scripts/sw-upgrade.sh +5 -23
  163. package/scripts/sw-ux.sh +1 -19
  164. package/scripts/sw-webhook.sh +18 -32
  165. package/scripts/sw-widgets.sh +3 -21
  166. package/scripts/sw-worktree.sh +11 -27
  167. package/scripts/update-homebrew-sha.sh +73 -0
  168. package/templates/pipelines/tdd.json +72 -0
  169. package/scripts/sw-pipeline.sh.mock +0 -7
@@ -14,7 +14,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
14
  RECRUIT_VERSION="3.0.0"
15
15
 
16
16
  # ─── Dependency check ─────────────────────────────────────────────────────────
17
- if ! command -v jq &>/dev/null; then
17
+ if ! command -v jq >/dev/null 2>&1; then
18
18
  echo "ERROR: sw-recruit.sh requires 'jq' (JSON processor). Install with:" >&2
19
19
  echo " macOS: brew install jq" >&2
20
20
  echo " Ubuntu: sudo apt install jq" >&2
@@ -45,16 +45,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
45
45
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
46
46
  }
47
47
  fi
48
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
49
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
50
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
51
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
52
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
53
- RED="${RED:-\033[38;2;248;113;113m}"
54
- DIM="${DIM:-\033[2m}"
55
- BOLD="${BOLD:-\033[1m}"
56
- RESET="${RESET:-\033[0m}"
57
-
58
48
  # ─── File Locking for Concurrent Safety ────────────────────────────────────
59
49
  # Usage: _recruit_locked_write <target_file> <tmp_file>
60
50
  # Acquires flock, then moves tmp_file to target atomically.
@@ -65,7 +55,7 @@ _recruit_locked_write() {
65
55
  local lock_file="${target}.lock"
66
56
 
67
57
  (
68
- if command -v flock &>/dev/null; then
58
+ if command -v flock >/dev/null 2>&1; then
69
59
  flock -w 5 200 2>/dev/null || true
70
60
  fi
71
61
  mv "$tmp_file" "$target"
@@ -90,7 +80,7 @@ POLICY_FILE="${SCRIPT_DIR}/../config/policy.json"
90
80
  _recruit_policy() {
91
81
  local key="$1"
92
82
  local default="$2"
93
- if [[ -f "$POLICY_FILE" ]] && command -v jq &>/dev/null; then
83
+ if [[ -f "$POLICY_FILE" ]] && command -v jq >/dev/null 2>&1; then
94
84
  local val
95
85
  val=$(jq -r ".recruit.${key} // empty" "$POLICY_FILE" 2>/dev/null) || true
96
86
  [[ -n "$val" ]] && echo "$val" || echo "$default"
@@ -133,7 +123,7 @@ fi
133
123
  # Set SW_RECRUIT_NO_LLM=1 to disable LLM calls (e.g., in tests)
134
124
  _recruit_has_claude() {
135
125
  [[ "${SW_RECRUIT_NO_LLM:-}" == "1" ]] && return 1
136
- command -v claude &>/dev/null
126
+ command -v claude >/dev/null 2>&1
137
127
  }
138
128
 
139
129
  # Call Claude with a prompt, return text. Falls back gracefully.
@@ -144,7 +134,7 @@ _recruit_call_claude() {
144
134
  # Honor the no-LLM flag everywhere (not just _recruit_has_claude)
145
135
  [[ "${SW_RECRUIT_NO_LLM:-}" == "1" ]] && { echo ""; return; }
146
136
 
147
- if [[ "$INTELLIGENCE_AVAILABLE" == "true" ]] && command -v _intelligence_call_claude &>/dev/null; then
137
+ if [[ "$INTELLIGENCE_AVAILABLE" == "true" ]] && command -v _intelligence_call_claude >/dev/null 2>&1; then
148
138
  _intelligence_call_claude "$prompt" 2>/dev/null || echo ""
149
139
  return
150
140
  fi
@@ -164,7 +154,7 @@ _recruit_call_claude() {
164
154
  initialize_builtin_roles() {
165
155
  ensure_recruit_dir
166
156
 
167
- if jq -e '.architect' "$ROLES_DB" &>/dev/null 2>&1; then
157
+ if jq -e '.architect' "$ROLES_DB" >/dev/null 2>&1; then
168
158
  return 0
169
159
  fi
170
160
 
@@ -385,7 +375,7 @@ Return JSON only, no markdown fences."
385
375
  local result
386
376
  result=$(_recruit_call_claude "$prompt")
387
377
 
388
- if [[ -n "$result" ]] && echo "$result" | jq -e '.primary_role' &>/dev/null 2>&1; then
378
+ if [[ -n "$result" ]] && echo "$result" | jq -e '.primary_role' >/dev/null 2>&1; then
389
379
  echo "$result"
390
380
  return 0
391
381
  fi
@@ -483,7 +473,7 @@ Return JSON only."
483
473
  local result
484
474
  result=$(_recruit_call_claude "$prompt")
485
475
 
486
- if [[ -n "$result" ]] && echo "$result" | jq -e '.key' &>/dev/null 2>&1; then
476
+ if [[ -n "$result" ]] && echo "$result" | jq -e '.key' >/dev/null 2>&1; then
487
477
  role_key=$(echo "$result" | jq -r '.key')
488
478
  role_title=$(echo "$result" | jq -r '.title')
489
479
  role_desc=$(echo "$result" | jq -r '.description')
@@ -1042,7 +1032,7 @@ cmd_team() {
1042
1032
 
1043
1033
  # Gather codebase context if in a git repo
1044
1034
  local codebase_context=""
1045
- if command -v git &>/dev/null && git rev-parse --git-dir &>/dev/null 2>&1; then
1035
+ if command -v git >/dev/null 2>&1 && git rev-parse --git-dir >/dev/null 2>&1; then
1046
1036
  local file_count lang_summary
1047
1037
  file_count=$(git ls-files 2>/dev/null | wc -l | tr -d ' ')
1048
1038
  lang_summary=$(git ls-files 2>/dev/null | grep -oE '\.[^.]+$' | sort | uniq -c | sort -rn | head -5 | tr '\n' ';' || echo "unknown")
@@ -1069,7 +1059,7 @@ Return JSON only."
1069
1059
  local result
1070
1060
  result=$(_recruit_call_claude "$prompt")
1071
1061
 
1072
- if [[ -n "$result" ]] && echo "$result" | jq -e '.team' &>/dev/null 2>&1; then
1062
+ if [[ -n "$result" ]] && echo "$result" | jq -e '.team' >/dev/null 2>&1; then
1073
1063
  while IFS= read -r role; do
1074
1064
  [[ -z "$role" || "$role" == "null" ]] && continue
1075
1065
  recommended_team+=("$role")
@@ -1439,7 +1429,7 @@ Return JSON only."
1439
1429
  local result
1440
1430
  result=$(_recruit_call_claude "$prompt")
1441
1431
 
1442
- if [[ -n "$result" ]] && echo "$result" | jq -e '.roles | length > 0' &>/dev/null 2>&1; then
1432
+ if [[ -n "$result" ]] && echo "$result" | jq -e '.roles | length > 0' >/dev/null 2>&1; then
1443
1433
  local new_count
1444
1434
  new_count=$(echo "$result" | jq '.roles | length')
1445
1435
 
@@ -1596,7 +1586,7 @@ Return JSON only."
1596
1586
  local result
1597
1587
  result=$(_recruit_call_claude "$prompt")
1598
1588
 
1599
- if [[ -n "$result" ]] && echo "$result" | jq -e '.working_style' &>/dev/null 2>&1; then
1589
+ if [[ -n "$result" ]] && echo "$result" | jq -e '.working_style' >/dev/null 2>&1; then
1600
1590
  # Save the LLM-generated mind profile
1601
1591
  local tmp_file
1602
1592
  tmp_file=$(mktemp)
@@ -1700,7 +1690,7 @@ Return JSON only."
1700
1690
  local result
1701
1691
  result=$(_recruit_call_claude "$prompt")
1702
1692
 
1703
- if [[ -n "$result" ]] && echo "$result" | jq -e '.sub_tasks' &>/dev/null 2>&1; then
1693
+ if [[ -n "$result" ]] && echo "$result" | jq -e '.sub_tasks' >/dev/null 2>&1; then
1704
1694
  local restated_goal
1705
1695
  restated_goal=$(echo "$result" | jq -r '.goal // ""')
1706
1696
  [[ -n "$restated_goal" ]] && echo -e " ${DIM}Interpreted as: ${restated_goal}${RESET}"
@@ -1926,7 +1916,7 @@ cmd_match() {
1926
1916
  local llm_result
1927
1917
  llm_result=$(_recruit_llm_match "$task_description" "$available_roles")
1928
1918
 
1929
- if [[ -n "$llm_result" ]] && echo "$llm_result" | jq -e '.primary_role' &>/dev/null 2>&1; then
1919
+ if [[ -n "$llm_result" ]] && echo "$llm_result" | jq -e '.primary_role' >/dev/null 2>&1; then
1930
1920
  primary_role=$(echo "$llm_result" | jq -r '.primary_role')
1931
1921
  secondary_roles=$(echo "$llm_result" | jq -r '.secondary_roles // [] | join(", ")')
1932
1922
  confidence=$(echo "$llm_result" | jq -r '.confidence // 0.8')
@@ -1961,7 +1951,7 @@ cmd_match() {
1961
1951
  fi
1962
1952
 
1963
1953
  # Validate role exists
1964
- if ! jq -e ".\"${primary_role}\"" "$ROLES_DB" &>/dev/null 2>&1; then
1954
+ if ! jq -e ".\"${primary_role}\"" "$ROLES_DB" >/dev/null 2>&1; then
1965
1955
  primary_role="builder"
1966
1956
  fi
1967
1957
 
@@ -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.1.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
  format_duration() {
48
38
  local secs="$1"
49
39
  if [[ "$secs" -ge 3600 ]]; then
@@ -134,7 +124,7 @@ collect_script_metrics() {
134
124
  # Check for syntax errors
135
125
  while IFS= read -r script; do
136
126
  if ! bash -n "$script" 2>/dev/null; then
137
- ((syntax_errors++))
127
+ syntax_errors=$((syntax_errors + 1))
138
128
  fi
139
129
  done < <(find "$REPO_DIR/scripts" -maxdepth 1 -name "*.sh" -type f 2>/dev/null)
140
130
 
@@ -291,14 +281,14 @@ cmd_check() {
291
281
  # Metric should not decrease
292
282
  if (( $(echo "$current_val_num < $baseline_val_num" | bc -l 2>/dev/null || echo "0") )); then
293
283
  echo -e "${RED}✗ $name: $baseline_val_num → $current_val_num (${pct_diff}%)${RESET}"
294
- ((regressions++))
284
+ regressions=$((regressions + 1))
295
285
  return 1
296
286
  fi
297
287
  elif [[ "$direction" == "increase" ]]; then
298
288
  # Metric should not increase beyond threshold
299
289
  if (( $(echo "$pct_diff > $threshold_val" | bc -l 2>/dev/null || echo "0") )); then
300
290
  echo -e "${RED}✗ $name: $baseline_val_num → $current_val_num (+${pct_diff}%)${RESET}"
301
- ((regressions++))
291
+ regressions=$((regressions + 1))
302
292
  return 1
303
293
  fi
304
294
  fi
@@ -306,10 +296,10 @@ cmd_check() {
306
296
  # Improvement
307
297
  if [[ "$direction" == "decrease" ]] && (( $(echo "$current_val_num > $baseline_val_num" | bc -l 2>/dev/null || echo "0") )); then
308
298
  echo -e "${GREEN}✓ $name: $baseline_val_num → $current_val_num (improved)${RESET}"
309
- ((improvements++))
299
+ improvements=$((improvements + 1))
310
300
  elif [[ "$direction" == "increase" ]] && (( $(echo "$current_val_num < $baseline_val_num" | bc -l 2>/dev/null || echo "0") )); then
311
301
  echo -e "${GREEN}✓ $name: $baseline_val_num → $current_val_num (improved)${RESET}"
312
- ((improvements++))
302
+ improvements=$((improvements + 1))
313
303
  fi
314
304
  }
315
305
 
@@ -335,10 +325,10 @@ cmd_check() {
335
325
  pass_rate_diff=$(awk "BEGIN { printf \"%.1f\", ($base_pass_rate - $curr_pass_rate) }")
336
326
  if (( $(echo "$pass_rate_diff > $pass_rate_threshold" | bc -l 2>/dev/null || echo "0") )); then
337
327
  echo -e "${RED}✗ Pass Rate: $base_pass_rate% → $curr_pass_rate% (drop: ${pass_rate_diff}%)${RESET}"
338
- ((regressions++))
328
+ regressions=$((regressions + 1))
339
329
  elif (( $(echo "$curr_pass_rate > $base_pass_rate" | bc -l 2>/dev/null || echo "0") )); then
340
330
  echo -e "${GREEN}✓ Pass Rate: $base_pass_rate% → $curr_pass_rate%${RESET}"
341
- ((improvements++))
331
+ improvements=$((improvements + 1))
342
332
  else
343
333
  echo -e "${DIM}= Pass Rate: $base_pass_rate% → $curr_pass_rate%${RESET}"
344
334
  fi
@@ -373,10 +363,10 @@ cmd_check() {
373
363
  curr_syntax_errors=$(echo "$current" | jq -r '.syntax_errors // 0')
374
364
  if [[ "$curr_syntax_errors" -gt "$base_syntax_errors" ]]; then
375
365
  echo -e "${RED}✗ Syntax Errors: $base_syntax_errors → $curr_syntax_errors${RESET}"
376
- ((regressions++))
366
+ regressions=$((regressions + 1))
377
367
  elif [[ "$curr_syntax_errors" -lt "$base_syntax_errors" ]]; then
378
368
  echo -e "${GREEN}✓ Syntax Errors: $base_syntax_errors → $curr_syntax_errors${RESET}"
379
- ((improvements++))
369
+ improvements=$((improvements + 1))
380
370
  else
381
371
  echo -e "${DIM}= Syntax Errors: $base_syntax_errors → $curr_syntax_errors${RESET}"
382
372
  fi
@@ -510,7 +500,7 @@ cmd_history() {
510
500
 
511
501
  local count=0
512
502
  while IFS= read -r baseline_file; do
513
- ((count++))
503
+ count=$((count + 1))
514
504
  if [[ "$count" -gt 10 ]]; then
515
505
  break
516
506
  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.4.0"
9
+ VERSION="3.1.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
  # ─── Structured Event Log ──────────────────────────────────────────────────
48
30
  EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
49
31
 
@@ -133,7 +115,9 @@ check_tests_passing() {
133
115
  return 0
134
116
  fi
135
117
 
136
- if ! npm test 2>&1 | tee /tmp/test-output.log | tail -20; then
118
+ local _test_log
119
+ _test_log=$(mktemp "${TMPDIR:-/tmp}/sw-test-output.XXXXXX")
120
+ if ! npm test 2>&1 | tee "$_test_log" | tail -20; then
137
121
  error "Tests are not passing"
138
122
  return 1
139
123
  fi
@@ -171,7 +155,7 @@ check_coverage_threshold() {
171
155
  check_no_open_blockers() {
172
156
  info "Checking for open blockers..."
173
157
 
174
- if ! command -v gh &>/dev/null; then
158
+ if ! command -v gh >/dev/null 2>&1; then
175
159
  warn "GitHub CLI not available — skipping blocker check"
176
160
  return 0
177
161
  fi
@@ -191,13 +175,20 @@ check_no_open_blockers() {
191
175
  check_security_scan() {
192
176
  info "Checking security scan status..."
193
177
 
194
- if ! command -v gh &>/dev/null; then
178
+ if ! command -v gh >/dev/null 2>&1; then
195
179
  warn "GitHub CLI not available — skipping security check"
196
180
  return 0
197
181
  fi
198
182
 
183
+ local repo_nwo
184
+ repo_nwo=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || echo "")
185
+ if [[ -z "$repo_nwo" ]]; then
186
+ warn "Could not determine repository — skipping security check"
187
+ return 0
188
+ fi
189
+
199
190
  local vulns
200
- vulns=$(gh api repos/{owner}/{repo}/dependabot/alerts --jq 'length' 2>/dev/null || echo "0")
191
+ vulns=$(gh api "repos/${repo_nwo}/dependabot/alerts" --jq 'length' 2>/dev/null || echo "0")
201
192
 
202
193
  if [[ $vulns -gt 0 ]]; then
203
194
  error "Found $vulns security vulnerabilities"
@@ -319,7 +310,7 @@ publish_release() {
319
310
  fi
320
311
 
321
312
  # Create GitHub release
322
- if command -v gh &>/dev/null; then
313
+ if command -v gh >/dev/null 2>&1; then
323
314
  if gh release create "$next_version" --title "$next_version" --generate-notes; then
324
315
  success "GitHub release created"
325
316
  else
@@ -371,7 +362,7 @@ create_rc() {
371
362
  fi
372
363
 
373
364
  # Create GitHub pre-release
374
- if command -v gh &>/dev/null; then
365
+ if command -v gh >/dev/null 2>&1; then
375
366
  if gh release create "$rc_version" --title "RC: $rc_version" --prerelease --generate-notes; then
376
367
  success "GitHub pre-release created"
377
368
  else
@@ -424,7 +415,7 @@ promote_rc() {
424
415
  fi
425
416
 
426
417
  # Create GitHub release (not pre-release)
427
- if command -v gh &>/dev/null; then
418
+ if command -v gh >/dev/null 2>&1; then
428
419
  if gh release create "$stable_version" --title "$stable_version" --generate-notes; then
429
420
  success "GitHub release created"
430
421
  else
@@ -472,7 +463,7 @@ rollback_release() {
472
463
  fi
473
464
 
474
465
  # Delete GitHub release
475
- if command -v gh &>/dev/null; then
466
+ if command -v gh >/dev/null 2>&1; then
476
467
  if gh release delete "$version" --yes 2>/dev/null; then
477
468
  success "GitHub release deleted"
478
469
  else
@@ -534,7 +525,7 @@ show_history() {
534
525
  info "Release History"
535
526
  echo ""
536
527
 
537
- if ! command -v gh &>/dev/null; then
528
+ if ! command -v gh >/dev/null 2>&1; then
538
529
  error "GitHub CLI required for history"
539
530
  return 1
540
531
  fi
@@ -5,9 +5,9 @@
5
5
  # ╚═══════════════════════════════════════════════════════════════════════════╝
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
- trap 'rm -f "${tmp_file:-}"' EXIT
8
+ trap 'rm -f "${tmp_file:-}" "${tmp_changelog:-}"' EXIT
9
9
 
10
- VERSION="2.4.0"
10
+ VERSION="3.1.0"
11
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
12
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
13
13
 
@@ -18,6 +18,7 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
18
18
  # Canonical helpers (colors, output, events)
19
19
  # shellcheck source=lib/helpers.sh
20
20
  [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
21
+ [[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
21
22
  # Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
22
23
  [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
23
24
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
@@ -35,16 +36,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
35
36
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
36
37
  }
37
38
  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
39
  # ─── Parse flags ───────────────────────────────────────────────────────────
49
40
  DRY_RUN=false
50
41
  VERSION_TYPE=""
@@ -510,7 +501,8 @@ cmd_publish() {
510
501
  info "Step 2/5: Generating changelog..."
511
502
  local changelog
512
503
  changelog="$(generate_changelog_md "$next_version" "$current_version" "HEAD")"
513
- echo "$changelog" > /tmp/release-changelog.md
504
+ tmp_changelog="$(mktemp)"
505
+ echo "$changelog" > "$tmp_changelog"
514
506
  success "Changelog generated"
515
507
  echo ""
516
508
 
@@ -531,15 +523,15 @@ cmd_publish() {
531
523
 
532
524
  # Step 5: Create GitHub release
533
525
  info "Step 5/5: Creating GitHub release..."
534
- if command -v gh &>/dev/null; then
535
- if gh release create "$next_version" --title "$next_version" --notes-file /tmp/release-changelog.md; then
526
+ if command -v gh >/dev/null 2>&1; then
527
+ if gh release create "$next_version" --title "$next_version" --notes-file "$tmp_changelog"; then
536
528
  success "GitHub release created"
537
529
  else
538
530
  warn "Failed to create GitHub release (may already exist)"
539
531
  fi
540
532
  else
541
533
  warn "gh CLI not installed — skipping GitHub release creation"
542
- echo -e " Manual create: ${DIM}gh release create $next_version --title '$next_version' --notes-file /tmp/release-changelog.md${RESET}"
534
+ echo -e " Manual create: ${DIM}gh release create $next_version --title '$next_version' --notes-file <changelog_file>${RESET}"
543
535
  fi
544
536
  echo ""
545
537
 
@@ -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.1.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
  # ─── Defaults ───────────────────────────────────────────────────────────────
48
38
  MACHINES_FILE="$HOME/.shipwright/machines.json"
49
39
  SSH_OPTS="-o ConnectTimeout=5 -o BatchMode=yes -o StrictHostKeyChecking=accept-new"