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
@@ -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
 
@@ -21,24 +21,6 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
21
21
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
22
22
  [[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
23
23
  [[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
24
- if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
25
- emit_event() {
26
- local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
27
- local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
28
- while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
29
- echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
30
- }
31
- 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
24
  # ─── Configuration ───────────────────────────────────────────────────────
43
25
  REVIEW_CONFIG="${REPO_DIR}/.claude/code-review.json"
44
26
  QUALITY_METRICS_FILE="${REPO_DIR}/.claude/pipeline-artifacts/quality-metrics.json"
@@ -335,17 +317,16 @@ auto_fix() {
335
317
  local backup="${target_file}.review-backup"
336
318
  cp "$target_file" "$backup"
337
319
 
338
- # Fix 1: Run shellcheck and capture warnings
339
- if command -v shellcheck &>/dev/null; then
340
- local shellcheck_fixes=0
320
+ # Report shellcheck issues (informational auto-fix is limited to whitespace)
321
+ if command -v shellcheck >/dev/null 2>&1; then
341
322
  local warnings_file
342
323
  warnings_file=$(mktemp)
343
324
  shellcheck -f json "$target_file" > "$warnings_file" 2>/dev/null || true
344
325
 
345
326
  if [[ -s "$warnings_file" ]]; then
346
- shellcheck_fixes=$(jq 'length' "$warnings_file" 2>/dev/null || echo "0")
347
- info "shellcheck found $shellcheck_fixes issues in $target_file"
348
- fixed=$((fixed + shellcheck_fixes))
327
+ local shellcheck_count
328
+ shellcheck_count=$(jq 'length' "$warnings_file" 2>/dev/null || echo "0")
329
+ [[ "$shellcheck_count" -gt 0 ]] && info "shellcheck found $shellcheck_count issues in $target_file (manual review recommended)"
349
330
  fi
350
331
  rm -f "$warnings_file"
351
332
  fi
@@ -381,7 +362,7 @@ run_claude_semantic_review() {
381
362
  local diff_content="$1"
382
363
  local requirements="${2:-}"
383
364
  [[ -z "$diff_content" ]] && return 0
384
- if ! command -v claude &>/dev/null; then
365
+ if ! command -v claude >/dev/null 2>&1; then
385
366
  return 0
386
367
  fi
387
368
 
@@ -437,13 +418,12 @@ review_changes() {
437
418
  local review_output="{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"scope\":\"$review_scope\",\"findings\":{}}"
438
419
  local total_issues=0
439
420
 
440
- # Get changed files
421
+ # Get changed files (Bash 3.2 compatible — no mapfile)
441
422
  local changed_files=()
442
423
  if [[ "$review_scope" == "staged" ]]; then
443
- mapfile -t changed_files < <(cd "$REPO_DIR" && git diff --cached --name-only 2>/dev/null || true)
424
+ while IFS= read -r _f; do [[ -n "$_f" ]] && changed_files+=("$_f"); done < <(cd "$REPO_DIR" && git diff --cached --name-only 2>/dev/null || true)
444
425
  else
445
- # For PR: get diff against main
446
- mapfile -t changed_files < <(cd "$REPO_DIR" && git diff main...HEAD --name-only 2>/dev/null || true)
426
+ while IFS= read -r _f; do [[ -n "$_f" ]] && changed_files+=("$_f"); done < <(cd "$REPO_DIR" && git diff main...HEAD --name-only 2>/dev/null || true)
447
427
  fi
448
428
 
449
429
  [[ ${#changed_files[@]} -eq 0 ]] && { success "No changes to review"; return 0; }
@@ -456,9 +436,9 @@ review_changes() {
456
436
  diff_content=$(cd "$REPO_DIR" && git diff main...HEAD 2>/dev/null || true)
457
437
  fi
458
438
  local semantic_issues=()
459
- if [[ -n "$diff_content" ]] && command -v claude &>/dev/null; then
439
+ if [[ -n "$diff_content" ]] && command -v claude >/dev/null 2>&1; then
460
440
  info "Running Claude semantic review (logic, race conditions, API usage)..."
461
- mapfile -t semantic_issues < <(run_claude_semantic_review "$diff_content" "${REVIEW_REQUIREMENTS:-}" || true)
441
+ while IFS= read -r _si; do [[ -n "$_si" ]] && semantic_issues+=("$_si"); done < <(run_claude_semantic_review "$diff_content" "${REVIEW_REQUIREMENTS:-}" || true)
462
442
  if [[ ${#semantic_issues[@]} -gt 0 ]]; then
463
443
  total_issues=$((total_issues + ${#semantic_issues[@]}))
464
444
  review_output=$(echo "$review_output" | jq --argjson arr "$(printf '%s\n' "${semantic_issues[@]}" | jq -R . | jq -s .)" '.semantic_findings = $arr' 2>/dev/null || echo "$review_output")
@@ -476,10 +456,10 @@ review_changes() {
476
456
  local arch_issues=()
477
457
  local style_issues=()
478
458
 
479
- mapfile -t smells < <(detect_code_smells "$file_path")
480
- mapfile -t solids < <(check_solid_principles "$file_path")
481
- mapfile -t arch_issues < <(check_architecture_boundaries "$file_path")
482
- mapfile -t style_issues < <(check_style_consistency "$file_path")
459
+ while IFS= read -r _s; do [[ -n "$_s" ]] && smells+=("$_s"); done < <(detect_code_smells "$file_path")
460
+ while IFS= read -r _s; do [[ -n "$_s" ]] && solids+=("$_s"); done < <(check_solid_principles "$file_path")
461
+ while IFS= read -r _s; do [[ -n "$_s" ]] && arch_issues+=("$_s"); done < <(check_architecture_boundaries "$file_path")
462
+ while IFS= read -r _s; do [[ -n "$_s" ]] && style_issues+=("$_s"); done < <(check_style_consistency "$file_path")
483
463
 
484
464
  local file_issues=$((${#smells[@]} + ${#solids[@]} + ${#arch_issues[@]} + ${#style_issues[@]}))
485
465
  total_issues=$((total_issues + file_issues))
@@ -8,7 +8,7 @@
8
8
  set -euo pipefail
9
9
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
 
11
- VERSION="2.4.0"
11
+ VERSION="3.1.0"
12
12
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
13
 
14
14
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -18,8 +18,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && 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 $*"; }
24
+ # Color fallbacks when helpers not loaded
25
+ : "${CYAN:=}" "${BOLD:=}" "${RESET:=}" "${DIM:=}" "${GREEN:=}" "${RED:=}" "${YELLOW:=}" "${PURPLE:=}" "${WHITE:=}" "${BLUE:=}"
23
26
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
24
27
  [[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
25
28
  [[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
@@ -35,16 +38,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
35
38
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
36
39
  }
37
40
  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
41
  # ─── Constants ──────────────────────────────────────────────────────────────
49
42
  SHIPWRIGHT_DIR="$HOME/.shipwright"
50
43
  PID_FILE="$SHIPWRIGHT_DIR/connect.pid"
@@ -53,7 +46,7 @@ DAEMON_PID_FILE="$SHIPWRIGHT_DIR/daemon.pid"
53
46
  DAEMON_STATE_FILE="$SHIPWRIGHT_DIR/daemon-state.json"
54
47
  EVENTS_FILE="$SHIPWRIGHT_DIR/events.jsonl"
55
48
  CONNECT_LOG="$SHIPWRIGHT_DIR/connect.log"
56
- DEFAULT_URL="http://localhost:8767"
49
+ DEFAULT_URL="http://localhost:$(_config_get_int "dashboard.port" 8767)"
57
50
  HEARTBEAT_INTERVAL=10
58
51
 
59
52
  ensure_dir() {
@@ -6,9 +6,9 @@
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
- REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
11
+ REPO_DIR="${SHIPWRIGHT_REPO_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
12
12
 
13
13
  # ─── Cross-platform compatibility ──────────────────────────────────────────
14
14
  # shellcheck source=lib/compat.sh
@@ -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
  # ─── Paths ────────────────────────────────────────────────────────────────
48
30
  ARTIFACTS_DIR="${REPO_DIR}/.claude/pipeline-artifacts"
49
31
  CONTEXT_BUNDLE="${ARTIFACTS_DIR}/context-bundle.md"
@@ -124,7 +106,7 @@ extract_recent_prs() {
124
106
  return
125
107
  fi
126
108
 
127
- if ! command -v gh &>/dev/null; then
109
+ if ! command -v gh >/dev/null 2>&1; then
128
110
  echo "(gh CLI not available, using git log)"
129
111
  echo ""
130
112
  local recent_commits
@@ -405,6 +387,7 @@ gather_context() {
405
387
 
406
388
  local tmp_file
407
389
  tmp_file=$(mktemp "${TMPDIR:-/tmp}/sw-context-bundle.XXXXXX")
390
+ trap "rm -f '$tmp_file'" RETURN
408
391
 
409
392
  # Write bundle header
410
393
  {
@@ -484,9 +467,9 @@ gather_context() {
484
467
  # ─── Show current bundle ───────────────────────────────────────────────────
485
468
  show_context() {
486
469
  if [[ ! -f "$CONTEXT_BUNDLE" ]]; then
487
- warn "No context bundle found at ${CONTEXT_BUNDLE}"
488
- echo "Run '${CYAN}shipwright context gather --goal \"...\" --stage plan${RESET}' first"
489
- return 1
470
+ echo "Pipeline Context — No bundle generated yet"
471
+ echo "Run '${CYAN}shipwright context gather --goal \"...\" --stage plan${RESET}' to create one"
472
+ return 0
490
473
  fi
491
474
 
492
475
  cat "$CONTEXT_BUNDLE"
@@ -498,7 +481,7 @@ clear_context() {
498
481
  rm -f "$CONTEXT_BUNDLE"
499
482
  success "Context bundle cleared"
500
483
  else
501
- warn "No context bundle to clear"
484
+ info "No context bundle to clear — already cleared"
502
485
  fi
503
486
  }
504
487
 
@@ -584,7 +567,7 @@ main() {
584
567
 
585
568
  # If issue provided, fetch from GitHub
586
569
  if [[ -n "$issue" ]]; then
587
- if [[ "${NO_GITHUB:-}" == "true" ]] || ! command -v gh &>/dev/null; then
570
+ if [[ "${NO_GITHUB:-}" == "true" ]] || ! command -v gh >/dev/null 2>&1; then
588
571
  goal="GitHub issue #$issue (fetch unavailable)"
589
572
  else
590
573
  goal=$(gh issue view "$issue" --json title,body --template '{{.title}}: {{.body}}' 2>/dev/null || echo "GitHub issue #$issue")
@@ -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
@@ -152,14 +142,14 @@ cost_record() {
152
142
  local cost_usd
153
143
  cost_usd=$(cost_calculate "$input_tokens" "$output_tokens" "$model")
154
144
 
155
- # Try SQLite first
156
- if type db_record_cost &>/dev/null; then
157
- db_record_cost "$input_tokens" "$output_tokens" "$model" "$stage" "$cost_usd" "$issue" 2>/dev/null || true
145
+ # Try SQLite first (arg order must match db_record_cost signature: tokens, tokens, model, cost, stage, issue)
146
+ if type db_record_cost >/dev/null 2>&1; then
147
+ db_record_cost "$input_tokens" "$output_tokens" "$model" "$cost_usd" "$stage" "$issue" 2>/dev/null || true
158
148
  fi
159
149
 
160
150
  # Always write to JSON (dual-write period)
161
151
  (
162
- if command -v flock &>/dev/null; then
152
+ if command -v flock >/dev/null 2>&1; then
163
153
  flock -w 10 200 2>/dev/null || { warn "Cost lock timeout"; }
164
154
  fi
165
155
  local tmp_file
@@ -202,7 +192,7 @@ cost_check_budget() {
202
192
 
203
193
  # Try DB for budget info
204
194
  local budget_enabled budget_usd
205
- if type db_get_budget &>/dev/null && type db_available &>/dev/null && db_available 2>/dev/null; then
195
+ if type db_get_budget >/dev/null 2>&1 && type db_available >/dev/null 2>&1 && db_available 2>/dev/null; then
206
196
  local db_budget
207
197
  db_budget=$(db_get_budget 2>/dev/null || true)
208
198
  if [[ -n "$db_budget" ]]; then
@@ -259,7 +249,7 @@ cost_remaining_budget() {
259
249
  ensure_cost_dir
260
250
 
261
251
  # Try DB for remaining budget (single query)
262
- if type db_remaining_budget &>/dev/null && type db_available &>/dev/null && db_available 2>/dev/null; then
252
+ if type db_remaining_budget >/dev/null 2>&1 && type db_available >/dev/null 2>&1 && db_available 2>/dev/null; then
263
253
  local db_result
264
254
  db_result=$(db_remaining_budget 2>/dev/null || true)
265
255
  if [[ -n "$db_result" ]]; then
@@ -274,6 +264,11 @@ cost_remaining_budget() {
274
264
  budget_usd=$(jq -r '.daily_budget_usd' "$BUDGET_FILE" 2>/dev/null || echo "0")
275
265
 
276
266
  if [[ "$budget_enabled" != "true" || "$budget_usd" == "0" ]]; then
267
+ if [[ -z "${_BUDGET_UNCONFIGURED_WARNED:-}" ]]; then
268
+ info "Budget not configured — unlimited. Use 'shipwright cost budget set <amount>'"
269
+ emit_event "cost.budget_unconfigured" "status=unlimited"
270
+ _BUDGET_UNCONFIGURED_WARNED=1
271
+ fi
277
272
  echo "unlimited"
278
273
  return 0
279
274
  fi
@@ -818,7 +813,7 @@ budget_set() {
818
813
  ensure_cost_dir
819
814
 
820
815
  # Write to DB if available
821
- if type db_set_budget &>/dev/null; then
816
+ if type db_set_budget >/dev/null 2>&1; then
822
817
  db_set_budget "$amount" 2>/dev/null || true
823
818
  fi
824
819
 
@@ -904,6 +899,8 @@ show_help() {
904
899
  }
905
900
 
906
901
  # ─── Command Router ─────────────────────────────────────────────────────────
902
+ # Only run CLI when executed directly (not when sourced by sw-pipeline.sh)
903
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
907
904
 
908
905
  SUBCOMMAND="${1:-help}"
909
906
  shift 2>/dev/null || true
@@ -953,3 +950,5 @@ case "$SUBCOMMAND" in
953
950
  exit 1
954
951
  ;;
955
952
  esac
953
+
954
+ fi # end source guard