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
@@ -63,6 +63,23 @@ get_stage_timing_seconds() {
63
63
  fi
64
64
  }
65
65
 
66
+ # Name of the slowest completed stage (for pipeline.completed event)
67
+ get_slowest_stage() {
68
+ local slowest="" max_sec=0
69
+ local stage_ids
70
+ stage_ids=$(echo "$STAGE_TIMINGS" | grep "_start:" | sed 's/_start:.*//' | sort -u)
71
+ for sid in $stage_ids; do
72
+ [[ -z "$sid" ]] && continue
73
+ local sec
74
+ sec=$(get_stage_timing_seconds "$sid")
75
+ if [[ -n "$sec" && "$sec" =~ ^[0-9]+$ && "$sec" -gt "$max_sec" ]]; then
76
+ max_sec="$sec"
77
+ slowest="$sid"
78
+ fi
79
+ done
80
+ echo "${slowest:-}"
81
+ }
82
+
66
83
  get_stage_description() {
67
84
  local stage_id="$1"
68
85
 
@@ -159,6 +176,13 @@ mark_stage_complete() {
159
176
  write_state
160
177
 
161
178
  record_stage_effectiveness "$stage_id" "complete"
179
+
180
+ # Record stage completion in SQLite pipeline_stages table
181
+ if type record_stage >/dev/null 2>&1; then
182
+ local _stage_secs
183
+ _stage_secs=$(get_stage_timing_seconds "$stage_id")
184
+ record_stage "${SHIPWRIGHT_PIPELINE_ID:-}" "$stage_id" "complete" "${_stage_secs:-0}" "" 2>/dev/null || true
185
+ fi
162
186
  # Update memory baselines and predictive baselines for stage durations
163
187
  if [[ "$stage_id" == "test" || "$stage_id" == "build" ]]; then
164
188
  local secs
@@ -191,7 +215,7 @@ mark_stage_complete() {
191
215
  fi
192
216
 
193
217
  # Update GitHub Check Run for this stage
194
- if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_stage_update &>/dev/null 2>&1; then
218
+ if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_stage_update >/dev/null 2>&1; then
195
219
  gh_checks_stage_update "$stage_id" "completed" "success" "Stage $stage_id: ${timing}" 2>/dev/null || true
196
220
  fi
197
221
 
@@ -215,9 +239,18 @@ mark_stage_complete() {
215
239
  fi
216
240
 
217
241
  # Durable WAL: publish stage completion event
218
- if type publish_event &>/dev/null 2>&1; then
242
+ if type publish_event >/dev/null 2>&1; then
219
243
  publish_event "stage.complete" "{\"stage\":\"${stage_id}\",\"issue\":\"${ISSUE_NUMBER:-0}\",\"timing\":\"${timing}\"}" 2>/dev/null || true
220
244
  fi
245
+
246
+ # Durable checkpoint: save to DB for pipeline resume
247
+ if type db_save_checkpoint >/dev/null 2>&1; then
248
+ local checkpoint_data
249
+ checkpoint_data=$(jq -nc --arg stage "$stage_id" --arg status "${PIPELINE_STATUS:-running}" \
250
+ --arg issue "${ISSUE_NUMBER:-}" --arg goal "${GOAL:-}" --arg template "${PIPELINE_TEMPLATE:-}" \
251
+ '{stage: $stage, status: $status, issue: $issue, goal: $goal, template: $template, ts: "'"$(now_iso)"'"}')
252
+ db_save_checkpoint "pipeline-${SHIPWRIGHT_PIPELINE_ID:-$$}" "$checkpoint_data" 2>/dev/null || true
253
+ fi
221
254
  }
222
255
 
223
256
  persist_artifacts() {
@@ -328,6 +361,13 @@ mark_stage_failed() {
328
361
  log_stage "$stage_id" "failed (${timing})"
329
362
  write_state
330
363
 
364
+ # Record stage failure in SQLite pipeline_stages table
365
+ if type record_stage >/dev/null 2>&1; then
366
+ local _stage_secs
367
+ _stage_secs=$(get_stage_timing_seconds "$stage_id")
368
+ record_stage "${SHIPWRIGHT_PIPELINE_ID:-}" "$stage_id" "failed" "${_stage_secs:-0}" "" 2>/dev/null || true
369
+ fi
370
+
331
371
  # Update GitHub progress + comment failure
332
372
  if [[ -n "$ISSUE_NUMBER" ]]; then
333
373
  local body
@@ -350,7 +390,7 @@ $(tail -5 "$ARTIFACTS_DIR/${stage_id}"*.log 2>/dev/null || echo 'No log availabl
350
390
  fi
351
391
 
352
392
  # Update GitHub Check Run for this stage
353
- if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_stage_update &>/dev/null 2>&1; then
393
+ if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_stage_update >/dev/null 2>&1; then
354
394
  local fail_summary
355
395
  fail_summary=$(tail -3 "$ARTIFACTS_DIR/${stage_id}"*.log 2>/dev/null | head -c 500 || echo "Stage $stage_id failed")
356
396
  gh_checks_stage_update "$stage_id" "completed" "failure" "$fail_summary" 2>/dev/null || true
@@ -368,7 +408,7 @@ $(tail -5 "$ARTIFACTS_DIR/${stage_id}"*.log 2>/dev/null || echo 'No log availabl
368
408
  fi
369
409
 
370
410
  # Durable WAL: publish stage failure event
371
- if type publish_event &>/dev/null 2>&1; then
411
+ if type publish_event >/dev/null 2>&1; then
372
412
  publish_event "stage.failed" "{\"stage\":\"${stage_id}\",\"issue\":\"${ISSUE_NUMBER:-0}\",\"timing\":\"${timing}\"}" 2>/dev/null || true
373
413
  fi
374
414
  }
@@ -446,6 +486,16 @@ _SW_STATE_END_
446
486
  printf '## Log\n'
447
487
  printf '%s\n' "$LOG_ENTRIES"
448
488
  } >> "$STATE_FILE"
489
+
490
+ # Update pipeline_runs in DB
491
+ if type update_pipeline_status >/dev/null 2>&1 && db_available 2>/dev/null; then
492
+ local _job_id="${SHIPWRIGHT_PIPELINE_ID:-pipeline-$$-${ISSUE_NUMBER:-0}}"
493
+ local _dur_secs=0
494
+ if [[ -n "$PIPELINE_START_EPOCH" ]]; then
495
+ _dur_secs=$(( $(now_epoch) - PIPELINE_START_EPOCH ))
496
+ fi
497
+ update_pipeline_status "$_job_id" "$PIPELINE_STATUS" "$CURRENT_STAGE" "" "$_dur_secs" 2>/dev/null || true
498
+ fi
449
499
  }
450
500
 
451
501
  resume_state() {
File without changes
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright test-helpers — Shared test harness for all unit tests ║
4
+ # ║ Source this from any *-test.sh file to get assert_*, setup, teardown ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+ #
7
+ # Usage:
8
+ # SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ # source "$SCRIPT_DIR/lib/test-helpers.sh"
10
+ #
11
+ # Provides:
12
+ # Colors, counters, assert_pass/fail/eq/contains/contains_regex/gt/json_key
13
+ # setup_test_env / cleanup_test_env (temp dir, mock PATH, mock HOME)
14
+ # print_test_header / print_test_results
15
+ # Mock helpers: mock_binary, mock_jq, mock_git, mock_gh, mock_claude
16
+
17
+ [[ -n "${_TEST_HELPERS_LOADED:-}" ]] && return 0
18
+ _TEST_HELPERS_LOADED=1
19
+
20
+ # ─── Colors ──────────────────────────────────────────────────────────────────
21
+ CYAN='\033[38;2;0;212;255m'
22
+ GREEN='\033[38;2;74;222;128m'
23
+ RED='\033[38;2;248;113;113m'
24
+ YELLOW='\033[38;2;250;204;21m'
25
+ DIM='\033[2m'
26
+ BOLD='\033[1m'
27
+ RESET='\033[0m'
28
+
29
+ # ─── Counters ────────────────────────────────────────────────────────────────
30
+ PASS=0
31
+ FAIL=0
32
+ TOTAL=0
33
+ FAILURES=()
34
+ TEST_TEMP_DIR=""
35
+
36
+ # ─── Assertions ──────────────────────────────────────────────────────────────
37
+
38
+ assert_pass() {
39
+ local desc="$1"
40
+ TOTAL=$((TOTAL + 1))
41
+ PASS=$((PASS + 1))
42
+ echo -e " ${GREEN}✓${RESET} ${desc}"
43
+ }
44
+
45
+ assert_fail() {
46
+ local desc="$1"
47
+ local detail="${2:-}"
48
+ TOTAL=$((TOTAL + 1))
49
+ FAIL=$((FAIL + 1))
50
+ FAILURES+=("$desc")
51
+ echo -e " ${RED}✗${RESET} ${desc}"
52
+ [[ -n "$detail" ]] && echo -e " ${DIM}${detail}${RESET}"
53
+ }
54
+
55
+ assert_eq() {
56
+ local desc="$1"
57
+ local expected="$2"
58
+ local actual="$3"
59
+ if [[ "$expected" == "$actual" ]]; then
60
+ assert_pass "$desc"
61
+ else
62
+ assert_fail "$desc" "expected: $expected, got: $actual"
63
+ fi
64
+ }
65
+
66
+ assert_contains() {
67
+ local desc="$1"
68
+ local haystack="$2"
69
+ local needle="$3"
70
+ if echo "$haystack" | grep -qF "$needle" 2>/dev/null; then
71
+ assert_pass "$desc"
72
+ else
73
+ assert_fail "$desc" "output missing: $needle"
74
+ fi
75
+ }
76
+
77
+ assert_contains_regex() {
78
+ local desc="$1"
79
+ local haystack="$2"
80
+ local pattern="$3"
81
+ if echo "$haystack" | grep -qE "$pattern" 2>/dev/null; then
82
+ assert_pass "$desc"
83
+ else
84
+ assert_fail "$desc" "output missing pattern: $pattern"
85
+ fi
86
+ }
87
+
88
+ assert_gt() {
89
+ local desc="$1"
90
+ local actual="$2"
91
+ local threshold="$3"
92
+ if [[ "$actual" -gt "$threshold" ]] 2>/dev/null; then
93
+ assert_pass "$desc"
94
+ else
95
+ assert_fail "$desc" "expected >$threshold, got: $actual"
96
+ fi
97
+ }
98
+
99
+ assert_json_key() {
100
+ local desc="$1"
101
+ local json="$2"
102
+ local key="$3"
103
+ local expected="$4"
104
+ local actual
105
+ actual=$(echo "$json" | jq -r "$key" 2>/dev/null)
106
+ if [[ "$actual" == "$expected" ]]; then
107
+ assert_pass "$desc"
108
+ else
109
+ assert_fail "$desc" "key $key: expected $expected, got: $actual"
110
+ fi
111
+ }
112
+
113
+ assert_exit_code() {
114
+ local desc="$1"
115
+ local expected="$2"
116
+ local actual="$3"
117
+ if [[ "$expected" == "$actual" ]]; then
118
+ assert_pass "$desc (exit $actual)"
119
+ else
120
+ assert_fail "$desc" "expected exit code: $expected, got: $actual"
121
+ fi
122
+ }
123
+
124
+ assert_file_exists() {
125
+ local desc="$1"
126
+ local filepath="$2"
127
+ if [[ -f "$filepath" ]]; then
128
+ assert_pass "$desc"
129
+ else
130
+ assert_fail "$desc" "file not found: $filepath"
131
+ fi
132
+ }
133
+
134
+ assert_file_not_exists() {
135
+ local desc="$1"
136
+ local filepath="$2"
137
+ if [[ ! -f "$filepath" ]]; then
138
+ assert_pass "$desc"
139
+ else
140
+ assert_fail "$desc" "file should not exist: $filepath"
141
+ fi
142
+ }
143
+
144
+ # ─── Test Environment ────────────────────────────────────────────────────────
145
+
146
+ setup_test_env() {
147
+ local test_name="${1:-sw-test}"
148
+ TEST_TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/${test_name}.XXXXXX")
149
+ mkdir -p "$TEST_TEMP_DIR/home/.shipwright"
150
+ mkdir -p "$TEST_TEMP_DIR/bin"
151
+ mkdir -p "$TEST_TEMP_DIR/project"
152
+ mkdir -p "$TEST_TEMP_DIR/logs"
153
+
154
+ ORIG_HOME="${HOME}"
155
+ ORIG_PATH="${PATH}"
156
+ export HOME="$TEST_TEMP_DIR/home"
157
+ export PATH="$TEST_TEMP_DIR/bin:$PATH"
158
+ export NO_GITHUB=true
159
+
160
+ # Link real jq if available
161
+ if command -v jq >/dev/null 2>&1; then
162
+ ln -sf "$(command -v jq)" "$TEST_TEMP_DIR/bin/jq"
163
+ fi
164
+ }
165
+
166
+ cleanup_test_env() {
167
+ if [[ -n "$TEST_TEMP_DIR" && -d "$TEST_TEMP_DIR" ]]; then
168
+ rm -rf "$TEST_TEMP_DIR"
169
+ fi
170
+ [[ -n "${ORIG_HOME:-}" ]] && export HOME="$ORIG_HOME"
171
+ [[ -n "${ORIG_PATH:-}" ]] && export PATH="$ORIG_PATH"
172
+ }
173
+
174
+ # ─── Mock Helpers ────────────────────────────────────────────────────────────
175
+
176
+ mock_binary() {
177
+ local name="$1"
178
+ local script="${2:-exit 0}"
179
+ cat > "$TEST_TEMP_DIR/bin/$name" <<MOCK
180
+ #!/usr/bin/env bash
181
+ $script
182
+ MOCK
183
+ chmod +x "$TEST_TEMP_DIR/bin/$name"
184
+ }
185
+
186
+ mock_git() {
187
+ mock_binary "git" 'case "${1:-}" in
188
+ rev-parse)
189
+ if [[ "${2:-}" == "--show-toplevel" ]]; then echo "/tmp/mock-repo"
190
+ elif [[ "${2:-}" == "--abbrev-ref" ]]; then echo "main"
191
+ else echo "/tmp/mock-repo"
192
+ fi ;;
193
+ remote) echo "https://github.com/testuser/testrepo.git" ;;
194
+ branch) echo "" ;;
195
+ log) echo "" ;;
196
+ *) echo "" ;;
197
+ esac
198
+ exit 0'
199
+ }
200
+
201
+ mock_gh() {
202
+ mock_binary "gh" 'case "${1:-}" in
203
+ api) echo "{}" ;;
204
+ issue) echo "[]" ;;
205
+ pr) echo "[]" ;;
206
+ *) echo "" ;;
207
+ esac
208
+ exit 0'
209
+ }
210
+
211
+ mock_claude() {
212
+ mock_binary "claude" 'echo "Mock claude response"
213
+ exit 0'
214
+ }
215
+
216
+ # ─── Output Helpers ──────────────────────────────────────────────────────────
217
+
218
+ print_test_header() {
219
+ local title="$1"
220
+ echo ""
221
+ echo -e "${CYAN}${BOLD} ${title}${RESET}"
222
+ echo -e "${DIM} ══════════════════════════════════════════${RESET}"
223
+ echo ""
224
+ }
225
+
226
+ print_test_section() {
227
+ local title="$1"
228
+ echo ""
229
+ echo -e " ${CYAN}${title}${RESET}"
230
+ }
231
+
232
+ print_test_results() {
233
+ echo ""
234
+ echo -e "${DIM} ──────────────────────────────────────────${RESET}"
235
+ echo ""
236
+ if [[ $FAIL -eq 0 ]]; then
237
+ echo -e " ${GREEN}${BOLD}All $TOTAL tests passed${RESET}"
238
+ else
239
+ echo -e " ${RED}${BOLD}$FAIL of $TOTAL tests failed${RESET}"
240
+ echo ""
241
+ for f in "${FAILURES[@]}"; do
242
+ echo -e " ${RED}✗${RESET} $f"
243
+ done
244
+ fi
245
+ echo ""
246
+ exit "$FAIL"
247
+ }
@@ -11,8 +11,11 @@ import {
11
11
  readFileSync,
12
12
  writeFileSync,
13
13
  appendFileSync,
14
+ chmodSync,
15
+ readdirSync,
14
16
  } from "fs";
15
- import { join } from "path";
17
+ import { join, basename } from "path";
18
+ import { execSync } from "child_process";
16
19
 
17
20
  const HOME = process.env.HOME || process.env.USERPROFILE;
18
21
  const PKG_DIR = join(import.meta.dirname, "..");
@@ -130,22 +133,85 @@ try {
130
133
  success("Migrated legacy config (originals preserved)");
131
134
  }
132
135
 
136
+ // Set executable bits on all scripts (npm strips them on some platforms)
137
+ const scriptsDir = join(PKG_DIR, "scripts");
138
+ if (existsSync(scriptsDir)) {
139
+ let madeExecutable = 0;
140
+ for (const file of readdirSync(scriptsDir)) {
141
+ const fp = join(scriptsDir, file);
142
+ try {
143
+ chmodSync(fp, 0o755);
144
+ madeExecutable++;
145
+ } catch (_) {
146
+ // skip non-files
147
+ }
148
+ }
149
+ const libDir = join(scriptsDir, "lib");
150
+ if (existsSync(libDir)) {
151
+ for (const file of readdirSync(libDir)) {
152
+ try {
153
+ chmodSync(join(libDir, file), 0o755);
154
+ madeExecutable++;
155
+ } catch (_) {}
156
+ }
157
+ }
158
+ success(`Set executable bits on ${madeExecutable} scripts`);
159
+ }
160
+
161
+ // Install shell completions for the user's current shell
162
+ const completionsDir = join(PKG_DIR, "completions");
163
+ if (existsSync(completionsDir)) {
164
+ const shell = basename(process.env.SHELL || "/bin/bash");
165
+ try {
166
+ if (shell === "bash") {
167
+ const dest =
168
+ process.env.BASH_COMPLETION_USER_DIR ||
169
+ join(
170
+ process.env.XDG_DATA_HOME || join(HOME, ".local", "share"),
171
+ "bash-completion",
172
+ "completions",
173
+ );
174
+ ensureDir(dest);
175
+ cpSync(
176
+ join(completionsDir, "shipwright.bash"),
177
+ join(dest, "shipwright"),
178
+ );
179
+ cpSync(join(completionsDir, "shipwright.bash"), join(dest, "sw"));
180
+ success(`Installed bash completions to ${dest}`);
181
+ } else if (shell === "zsh") {
182
+ const dest = join(HOME, ".zfunc");
183
+ ensureDir(dest);
184
+ cpSync(join(completionsDir, "_shipwright"), join(dest, "_shipwright"));
185
+ cpSync(join(completionsDir, "_shipwright"), join(dest, "_sw"));
186
+ success(`Installed zsh completions to ${dest}`);
187
+ } else if (shell === "fish") {
188
+ const dest = join(
189
+ process.env.XDG_CONFIG_HOME || join(HOME, ".config"),
190
+ "fish",
191
+ "completions",
192
+ );
193
+ ensureDir(dest);
194
+ cpSync(
195
+ join(completionsDir, "shipwright.fish"),
196
+ join(dest, "shipwright.fish"),
197
+ );
198
+ cpSync(join(completionsDir, "shipwright.fish"), join(dest, "sw.fish"));
199
+ success(`Installed fish completions to ${dest}`);
200
+ }
201
+ } catch (e) {
202
+ warn(`Could not auto-install completions: ${e.message}`);
203
+ info(`Run: shipwright init (or: bash scripts/install-completions.sh)`);
204
+ }
205
+ }
206
+
133
207
  // Print success banner
134
- const version = JSON.parse(
135
- readFileSync(join(PKG_DIR, "package.json"), "utf8"),
136
- ).version;
137
- console.log();
138
- console.log(`${CYAN}${BOLD} ⚓ Shipwright v${version} installed${RESET}`);
139
208
  console.log();
140
- console.log(` Next steps:`);
141
- console.log(
142
- ` ${DIM}$${RESET} shipwright doctor ${DIM}# Verify your setup${RESET}`,
143
- );
209
+ console.log(`${GREEN}${BOLD}Shipwright CLI installed!${RESET} Next steps:`);
144
210
  console.log(
145
- ` ${DIM}$${RESET} shipwright session ${DIM}# Launch an agent team${RESET}`,
211
+ ` ${DIM}shipwright init${RESET} ${DIM}# Set up tmux, hooks, and templates${RESET}`,
146
212
  );
147
213
  console.log(
148
- ` ${DIM}$${RESET} shipwright pipeline ${DIM}# Run a delivery pipeline${RESET}`,
214
+ ` ${DIM}shipwright doctor${RESET} ${DIM}# Verify your setup${RESET}`,
149
215
  );
150
216
  console.log();
151
217
  } catch (err) {
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ Example External Signal Collector for Shipwright Decision Engine ║
4
+ # ║ Place custom collectors in scripts/signals/ — they're auto-discovered ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+ #
7
+ # Output: one JSON candidate per line (JSONL).
8
+ # Required fields: id, signal, category, title, description, risk_score,
9
+ # confidence, dedup_key
10
+ # Optional fields: evidence (object)
11
+ #
12
+ # The decision engine collects output from all scripts/signals/*.sh files,
13
+ # validates each line as JSON, and includes valid candidates in the scoring
14
+ # pipeline.
15
+ #
16
+ # Categories (determines autonomy tier):
17
+ # auto: deps_patch, deps_minor, security_patch, test_coverage,
18
+ # doc_sync, dead_code
19
+ # propose: refactor_hotspot, architecture_drift, performance_regression,
20
+ # deps_major, security_critical, recurring_failure, dora_regression
21
+ # draft: new_feature, breaking_change, business_logic, api_change,
22
+ # data_model_change
23
+ #
24
+ # Example: detect a custom condition and emit a candidate
25
+ #
26
+
27
+ set -euo pipefail
28
+
29
+ # Example: check if a TODO count exceeds a threshold
30
+ TODO_COUNT=$(grep -r "TODO" --include="*.ts" --include="*.js" --include="*.sh" . 2>/dev/null | wc -l | tr -d ' ' || echo "0")
31
+
32
+ if [[ "${TODO_COUNT:-0}" -gt 50 ]]; then
33
+ cat <<EOF
34
+ {"id":"custom-todo-cleanup","signal":"custom","category":"dead_code","title":"Clean up ${TODO_COUNT} TODOs","description":"Codebase has ${TODO_COUNT} TODO comments — consider a cleanup sprint","evidence":{"todo_count":${TODO_COUNT}},"risk_score":15,"confidence":"0.70","dedup_key":"custom:todo:cleanup"}
35
+ EOF
36
+ fi
package/scripts/sw CHANGED
@@ -5,7 +5,7 @@
5
5
  # ╚═══════════════════════════════════════════════════════════════════════════╝
6
6
  set -euo pipefail
7
7
 
8
- VERSION="2.4.0"
8
+ VERSION="3.1.0"
9
9
 
10
10
  # Resolve symlinks (required for npm global install where bin/ symlinks to node_modules/)
11
11
  SOURCE="${BASH_SOURCE[0]}"
@@ -51,7 +51,7 @@ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
51
51
  error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
52
52
 
53
53
  check_tmux() {
54
- if ! command -v tmux &>/dev/null; then
54
+ if ! command -v tmux >/dev/null 2>&1; then
55
55
  error "tmux is not installed. Install it first:"
56
56
  echo -e " ${DIM}brew install tmux${RESET} (macOS)"
57
57
  echo -e " ${DIM}sudo apt install tmux${RESET} (Ubuntu/Debian)"
@@ -81,14 +81,14 @@ ensure_repo_setup() {
81
81
  }
82
82
 
83
83
  show_version() {
84
- echo -e "${CYAN}${BOLD}shipwright${RESET} ${DIM}v${VERSION}${RESET} — Orchestrate AI Coding Teams ${DIM}(aliases: sw, cct)${RESET}"
84
+ echo -e "${CYAN}${BOLD}shipwright${RESET} ${DIM}v${VERSION}${RESET} — Orchestrate AI Coding Teams ${DIM}(alias: sw)${RESET}"
85
85
  }
86
86
 
87
87
  show_help() {
88
88
  show_version
89
89
  echo ""
90
90
  echo -e "${BOLD}USAGE${RESET}"
91
- echo -e " ${CYAN}shipwright${RESET} <command> [options] ${DIM}(also: sw, cct)${RESET}"
91
+ echo -e " ${CYAN}shipwright${RESET} <command> [options] ${DIM}(alias: sw)${RESET}"
92
92
  echo ""
93
93
  echo -e "${BOLD}GETTING STARTED${RESET}"
94
94
  echo -e " ${CYAN}init${RESET} One-command setup — tmux, CLI, templates, hooks"
@@ -193,7 +193,8 @@ route_quality() {
193
193
  security-audit|audit) exec "$SCRIPT_DIR/sw-security-audit.sh" "$@" ;;
194
194
  testgen) exec "$SCRIPT_DIR/sw-testgen.sh" "$@" ;;
195
195
  hygiene) exec "$SCRIPT_DIR/sw-hygiene.sh" "$@" ;;
196
- help|*) echo "Usage: shipwright quality {code-review|security-audit|testgen|hygiene}"; exit 1 ;;
196
+ validate|gate) exec "$SCRIPT_DIR/sw-quality.sh" "$@" ;;
197
+ help|*) echo "Usage: shipwright quality {code-review|security-audit|testgen|hygiene|validate}"; exit 1 ;;
197
198
  esac
198
199
  }
199
200
 
@@ -467,6 +468,9 @@ main() {
467
468
  replay)
468
469
  exec "$SCRIPT_DIR/sw-replay.sh" "$@"
469
470
  ;;
471
+ review-rerun|rr)
472
+ exec "$SCRIPT_DIR/sw-review-rerun.sh" "$@"
473
+ ;;
470
474
  scale)
471
475
  exec "$SCRIPT_DIR/sw-scale.sh" "$@"
472
476
  ;;
@@ -524,14 +528,20 @@ main() {
524
528
  eventbus)
525
529
  exec "$SCRIPT_DIR/sw-eventbus.sh" "$@"
526
530
  ;;
531
+ evidence|ev)
532
+ exec "$SCRIPT_DIR/sw-evidence.sh" "$@"
533
+ ;;
534
+ decide)
535
+ exec "$SCRIPT_DIR/sw-decide.sh" "$@"
536
+ ;;
527
537
  otel)
528
538
  exec "$SCRIPT_DIR/sw-otel.sh" "$@"
529
539
  ;;
530
540
  triage)
531
541
  exec "$SCRIPT_DIR/sw-triage.sh" "$@"
532
542
  ;;
533
- quality)
534
- exec "$SCRIPT_DIR/sw-quality.sh" "$@"
543
+ pipeline-composer|composer)
544
+ exec "$SCRIPT_DIR/sw-pipeline-composer.sh" "$@"
535
545
  ;;
536
546
  oversight)
537
547
  exec "$SCRIPT_DIR/sw-oversight.sh" "$@"
@@ -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
 
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
  # ─── Event File & Filters ─────────────────────────────────────────────────────
47
37
  EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
48
38
  FILTER_TYPE=""