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
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright review-rerun — Canonical Rerun Comment Writer ║
4
+ # ║ SHA-deduped rerun requests · Single writer · No duplicate bot comments ║
5
+ # ║ Part of the Code Factory pattern for deterministic agent review loops ║
6
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
7
+ set -euo pipefail
8
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
9
+
10
+ VERSION="3.0.0"
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
13
+
14
+ # shellcheck source=lib/compat.sh
15
+ [[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
16
+ # shellcheck source=lib/helpers.sh
17
+ [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
18
+ [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
19
+ [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
20
+ [[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
21
+ [[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
22
+ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
23
+ emit_event() {
24
+ local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
25
+ local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
26
+ while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
27
+ echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
28
+ }
29
+ fi
30
+
31
+ # Load marker from policy or use default
32
+ get_rerun_marker() {
33
+ local policy="${REPO_DIR}/config/policy.json"
34
+ if [[ -f "$policy" ]]; then
35
+ jq -r '.codeReviewAgent.rerunMarker // "<!-- shipwright-review-rerun -->"' "$policy" 2>/dev/null
36
+ else
37
+ echo "<!-- shipwright-review-rerun -->"
38
+ fi
39
+ }
40
+
41
+ # Check if a rerun was already requested for this SHA on this PR
42
+ rerun_already_requested() {
43
+ local pr_number="$1"
44
+ local head_sha="$2"
45
+ local marker
46
+ marker=$(get_rerun_marker)
47
+ local trigger="sha:${head_sha}"
48
+
49
+ local comments
50
+ comments=$(gh pr view "$pr_number" --json comments --jq '.comments[].body' 2>/dev/null || echo "")
51
+
52
+ if echo "$comments" | grep -qF "$marker" && echo "$comments" | grep -qF "$trigger"; then
53
+ return 0
54
+ fi
55
+ return 1
56
+ }
57
+
58
+ # Post a SHA-deduped rerun comment to a PR
59
+ request_rerun() {
60
+ local pr_number="$1"
61
+ local head_sha="$2"
62
+ local review_agent="${3:-shipwright}"
63
+
64
+ if [[ -z "$pr_number" || -z "$head_sha" ]]; then
65
+ error "Usage: sw-review-rerun.sh request <pr_number> <head_sha> [review_agent]"
66
+ return 1
67
+ fi
68
+
69
+ local marker
70
+ marker=$(get_rerun_marker)
71
+ local trigger="sha:${head_sha}"
72
+ local short_sha="${head_sha:0:7}"
73
+
74
+ if rerun_already_requested "$pr_number" "$head_sha"; then
75
+ info "Rerun already requested for PR #${pr_number} at SHA ${short_sha} — skipping"
76
+ return 0
77
+ fi
78
+
79
+ local body="${marker}
80
+ **Review Rerun Requested** (${short_sha})
81
+
82
+ @${review_agent} please re-review this PR at the current head.
83
+
84
+ ${trigger}
85
+ ---
86
+ *Canonical rerun request by Shipwright Code Factory. One writer, SHA-deduped.*"
87
+
88
+ if gh pr comment "$pr_number" --body "$body" 2>/dev/null; then
89
+ success "Rerun requested for PR #${pr_number} at SHA ${short_sha}"
90
+ emit_event "review.rerun_requested" "pr=${pr_number}" "head_sha=${short_sha}" "agent=${review_agent}"
91
+ return 0
92
+ else
93
+ error "Failed to post rerun comment on PR #${pr_number}"
94
+ return 1
95
+ fi
96
+ }
97
+
98
+ # Check current rerun state for a PR
99
+ check_rerun_state() {
100
+ local pr_number="$1"
101
+
102
+ local head_sha
103
+ head_sha=$(gh pr view "$pr_number" --json headRefOid --jq '.headRefOid' 2>/dev/null || echo "")
104
+
105
+ if [[ -z "$head_sha" ]]; then
106
+ error "Could not get head SHA for PR #${pr_number}"
107
+ return 1
108
+ fi
109
+
110
+ local short_sha="${head_sha:0:7}"
111
+
112
+ if rerun_already_requested "$pr_number" "$head_sha"; then
113
+ info "Rerun already requested for current head ${short_sha}"
114
+ else
115
+ info "No rerun requested for current head ${short_sha}"
116
+ fi
117
+
118
+ echo "head_sha=${head_sha}"
119
+ }
120
+
121
+ # Wait for a review agent check to complete on the current head
122
+ wait_for_review() {
123
+ local pr_number="$1"
124
+ local head_sha="$2"
125
+ local timeout_minutes="${3:-20}"
126
+
127
+ local policy="${REPO_DIR}/config/policy.json"
128
+ if [[ -f "$policy" ]]; then
129
+ timeout_minutes=$(jq -r ".codeReviewAgent.timeoutMinutes // ${timeout_minutes}" "$policy" 2>/dev/null || echo "$timeout_minutes")
130
+ fi
131
+
132
+ local short_sha="${head_sha:0:7}"
133
+ local deadline=$(($(date +%s) + timeout_minutes * 60))
134
+
135
+ info "Waiting for review completion on ${short_sha} (timeout: ${timeout_minutes}m)..."
136
+
137
+ while [[ $(date +%s) -lt "$deadline" ]]; do
138
+ local owner_repo
139
+ owner_repo=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || echo "")
140
+ [[ -z "$owner_repo" ]] && { warn "Cannot detect repo"; return 1; }
141
+
142
+ local review_checks
143
+ review_checks=$(gh api "repos/${owner_repo}/commits/${head_sha}/check-runs" \
144
+ --jq '.check_runs[] | select(.name | test("review|code.review"; "i")) | {name: .name, status: .status, conclusion: .conclusion}' 2>/dev/null || echo "")
145
+
146
+ if [[ -n "$review_checks" ]]; then
147
+ local all_complete="true"
148
+ local any_failure="false"
149
+ while IFS= read -r check; do
150
+ [[ -z "$check" ]] && continue
151
+ local status conclusion
152
+ status=$(echo "$check" | jq -r '.status' 2>/dev/null || echo "")
153
+ conclusion=$(echo "$check" | jq -r '.conclusion' 2>/dev/null || echo "")
154
+ if [[ "$status" != "completed" ]]; then
155
+ all_complete="false"
156
+ fi
157
+ if [[ "$conclusion" == "failure" || "$conclusion" == "action_required" ]]; then
158
+ any_failure="true"
159
+ fi
160
+ done <<< "$review_checks"
161
+
162
+ if [[ "$all_complete" == "true" ]]; then
163
+ if [[ "$any_failure" == "true" ]]; then
164
+ error "Review check failed for SHA ${short_sha}"
165
+ return 1
166
+ fi
167
+ success "Review check passed for SHA ${short_sha}"
168
+ return 0
169
+ fi
170
+ fi
171
+
172
+ sleep 30
173
+ done
174
+
175
+ error "Review timed out after ${timeout_minutes}m for SHA ${short_sha}"
176
+ return 1
177
+ }
178
+
179
+ show_help() {
180
+ cat << 'EOF'
181
+ Usage: shipwright review-rerun <command> [args]
182
+
183
+ Commands:
184
+ request <pr#> <sha> [agent] Post SHA-deduped rerun comment
185
+ check <pr#> Check rerun state for current head
186
+ wait <pr#> <sha> [timeout] Wait for review completion on SHA
187
+
188
+ Part of the Code Factory pattern — single canonical rerun writer
189
+ with SHA deduplication to prevent duplicate bot comments.
190
+ EOF
191
+ }
192
+
193
+ main() {
194
+ local subcommand="${1:-help}"
195
+ shift || true
196
+
197
+ case "$subcommand" in
198
+ request)
199
+ request_rerun "$@"
200
+ ;;
201
+ check)
202
+ check_rerun_state "$@"
203
+ ;;
204
+ wait)
205
+ wait_for_review "$@"
206
+ ;;
207
+ help|--help|-h)
208
+ show_help
209
+ ;;
210
+ *)
211
+ error "Unknown subcommand: $subcommand"
212
+ show_help
213
+ return 1
214
+ ;;
215
+ esac
216
+ }
217
+
218
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
219
+ main "$@"
220
+ fi
@@ -6,11 +6,11 @@
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
  # ─── Dependency check ─────────────────────────────────────────────────────────
13
- if ! command -v jq &>/dev/null; then
13
+ if ! command -v jq >/dev/null 2>&1; then
14
14
  echo "ERROR: sw-scale.sh requires 'jq'. Install with: brew install jq (macOS) or apt install jq (Linux)" >&2
15
15
  exit 1
16
16
  fi
@@ -39,16 +39,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
39
39
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
40
40
  }
41
41
  fi
42
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
43
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
44
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
45
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
46
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
47
- RED="${RED:-\033[38;2;248;113;113m}"
48
- DIM="${DIM:-\033[2m}"
49
- BOLD="${BOLD:-\033[1m}"
50
- RESET="${RESET:-\033[0m}"
51
-
52
42
  # ─── Constants ──────────────────────────────────────────────────────────────
53
43
  SCALE_RULES_FILE="${HOME}/.shipwright/scale-rules.json"
54
44
  SCALE_EVENTS_FILE="${HOME}/.shipwright/scale-events.jsonl"
@@ -148,13 +138,19 @@ emit_scale_event() {
148
138
  '{ts: $ts, action: $action, role: $role, reason: $reason, context: $context}')
149
139
 
150
140
  echo "$event" >> "$SCALE_EVENTS_FILE"
151
- type rotate_jsonl &>/dev/null 2>&1 && rotate_jsonl "$SCALE_EVENTS_FILE" 5000
141
+ type rotate_jsonl >/dev/null 2>&1 && rotate_jsonl "$SCALE_EVENTS_FILE" 5000
152
142
  }
153
143
 
154
144
  # ─── Scale Up: spawn new agent ───────────────────────────────────────────
155
145
  cmd_up() {
156
- local role="${1:-builder}"
157
- shift 2>/dev/null || true
146
+ local count="${1:-1}"
147
+ local role="${2:-builder}"
148
+
149
+ # Parse: "up builder" -> count=1 role=builder; "up 2 tester" -> count=2 role=tester
150
+ if ! [[ "$count" =~ ^[0-9]+$ ]]; then
151
+ role="$count"
152
+ count=1
153
+ fi
158
154
 
159
155
  ensure_dirs
160
156
  init_rules
@@ -178,43 +174,124 @@ cmd_up() {
178
174
  local max_size
179
175
  max_size=$(jq -r '.max_team_size // 8' "$SCALE_RULES_FILE")
180
176
 
181
- info "Scaling up team with ${role} agent"
177
+ info "Scaling up team with ${count} ${role} agent(s)"
182
178
  echo -e " Max team size: ${CYAN}${max_size}${RESET}"
183
179
  echo -e " Role: ${CYAN}${role}${RESET}"
184
180
  echo ""
185
181
 
186
- # TODO: Integrate with tmux/SendMessage to spawn agent
187
- # For now, emit event and log
188
- emit_scale_event "up" "$role" "manual" "$*"
182
+ local repo_root
183
+ repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || repo_root="$(cd "$SCRIPT_DIR/.." 2>/dev/null && pwd)"
184
+
185
+ if ! command -v tmux &>/dev/null; then
186
+ warn "tmux not available - cannot spawn agents"
187
+ echo "Install tmux to enable agent scaling: brew install tmux"
188
+ emit_scale_event "up" "$role" "manual" "tmux_unavailable"
189
+ update_scale_state
190
+ success "Scale-up event recorded (role: ${role})"
191
+ echo ""
192
+ echo -e " ${DIM}Note: Actual agent spawn requires tmux (brew install tmux)${RESET}"
193
+ return 1
194
+ fi
195
+
196
+ for i in $(seq 1 "$count"); do
197
+ local agent_name="sw-agent-${role}-$(date +%s)-${i}"
198
+ local session_name="shipwright-${agent_name}"
199
+
200
+ # Spawn a real agent in a tmux session
201
+ tmux new-session -d -s "$session_name" \
202
+ "cd \"${repo_root}\" && SW_AGENT_ROLE=$role SW_AGENT_NAME=$agent_name bash scripts/sw-daemon.sh start --role $role 2>&1 | tee /tmp/sw-agent-${agent_name}.log" 2>/dev/null && {
203
+ emit_scale_event "up" "$role" "agent_started" "agent=$agent_name"
204
+ echo "Started agent $agent_name in tmux session $session_name"
205
+ } || {
206
+ warn "Failed to spawn agent $agent_name in tmux"
207
+ }
208
+ done
209
+
210
+ emit_scale_event "up" "$role" "manual" "count=$count"
189
211
  update_scale_state
190
212
 
191
- success "Scale-up event recorded (role: ${role})"
213
+ success "Scale-up event recorded (role: ${role}, count: ${count})"
192
214
  echo ""
193
- echo -e " ${DIM}Note: Actual agent spawn requires tmux/claude integration${RESET}"
194
215
  }
195
216
 
196
217
  # ─── Scale Down: send shutdown to agent ──────────────────────────────────
197
218
  cmd_down() {
198
- local agent_id="${1:-}"
199
- shift 2>/dev/null || true
219
+ local first_arg="${1:-}"
220
+ local second_arg="${2:-}"
200
221
 
201
- if [[ -z "$agent_id" ]]; then
202
- error "Usage: shipwright scale down <agent-id>"
222
+ # Require at least one argument for backward compat (down <agent-id> or down <count> [role])
223
+ if [[ -z "$first_arg" ]]; then
224
+ error "Usage: shipwright scale down <agent-id|count> [role]"
203
225
  return 1
204
226
  fi
205
227
 
228
+ local count="$first_arg"
229
+ local role="$second_arg"
230
+
231
+ # Backward compat: "down agent-42" -> treat as session/agent identifier
232
+ if [[ -n "$count" ]] && [[ "$count" != *[0-9]* ]] || [[ "$count" == agent-* ]]; then
233
+ # Specific agent/session id
234
+ local session_pattern="*${count}*"
235
+ local sessions
236
+ sessions=$(tmux list-sessions -F '#{session_name}' 2>/dev/null | grep -E "shipwright|swarm" | grep -i "$count" || true)
237
+ if [[ -z "$sessions" ]]; then
238
+ emit_scale_event "down" "unknown" "manual" "agent_id=$count"
239
+ update_scale_state
240
+ success "Scale-down event recorded (agent: ${count})"
241
+ return 0
242
+ fi
243
+ local session
244
+ session=$(echo "$sessions" | head -1)
245
+ tmux kill-session -t "$session" 2>/dev/null && {
246
+ emit_scale_event "down" "unknown" "agent_stopped" "session=$session"
247
+ echo "Stopped agent session: $session"
248
+ }
249
+ emit_scale_event "down" "unknown" "manual" "agent_id=$count"
250
+ update_scale_state
251
+ success "Scale-down event recorded (agent: ${count})"
252
+ return 0
253
+ fi
254
+
255
+ # Numeric count
256
+ if ! [[ "$count" =~ ^[0-9]+$ ]]; then
257
+ count=1
258
+ fi
259
+
206
260
  ensure_dirs
207
261
  init_rules
208
262
 
209
- info "Scaling down agent: ${agent_id}"
210
- echo ""
263
+ # Find running agent sessions
264
+ local sessions
265
+ sessions=$(tmux list-sessions -F '#{session_name}' 2>/dev/null | grep '^shipwright-sw-agent\|^swarm-' || true)
211
266
 
212
- # TODO: Integrate with SendMessage to shut down agent
213
- emit_scale_event "down" "unknown" "manual" "agent_id=$agent_id"
214
- update_scale_state
267
+ if [[ -z "$sessions" ]]; then
268
+ echo "No running agents to stop"
269
+ emit_scale_event "down" "unknown" "manual" "none_running"
270
+ update_scale_state
271
+ return 0
272
+ fi
215
273
 
216
- success "Scale-down event recorded (agent: ${agent_id})"
217
- echo -e " ${DIM}Note: Agent shutdown requires SendMessage integration${RESET}"
274
+ local stopped=0
275
+ while IFS= read -r session; do
276
+ [[ "$stopped" -ge "$count" ]] && break
277
+ [[ -z "$session" ]] && continue
278
+
279
+ # Filter by role if specified
280
+ if [[ -n "$role" ]] && ! echo "$session" | grep -q "$role"; then
281
+ continue
282
+ fi
283
+
284
+ if tmux kill-session -t "$session" 2>/dev/null; then
285
+ stopped=$((stopped + 1))
286
+ emit_scale_event "down" "unknown" "agent_stopped" "session=$session"
287
+ echo "Stopped agent session: $session"
288
+ fi
289
+ done <<< "$sessions"
290
+
291
+ emit_scale_event "down" "unknown" "manual" "count=$stopped"
292
+ update_scale_state
293
+ echo "Stopped $stopped agent(s)"
294
+ success "Scale-down event recorded"
218
295
  }
219
296
 
220
297
  # ─── Manage scaling rules ────────────────────────────────────────────────
@@ -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
 
@@ -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
  # ─── Audit State ───────────────────────────────────────────────────────────
47
37
  FINDINGS=()
48
38
  CRITICAL_COUNT=0
@@ -60,10 +50,10 @@ add_finding() {
60
50
 
61
51
  local color=""
62
52
  case "$priority" in
63
- CRITICAL) color="$RED"; ((CRITICAL_COUNT++)) ;;
64
- HIGH) color="$RED"; ((HIGH_COUNT++)) ;;
65
- MEDIUM) color="$YELLOW"; ((MEDIUM_COUNT++)) ;;
66
- LOW) color="$BLUE"; ((LOW_COUNT++)) ;;
53
+ CRITICAL) color="$RED"; CRITICAL_COUNT=$((CRITICAL_COUNT + 1)) ;;
54
+ HIGH) color="$RED"; HIGH_COUNT=$((HIGH_COUNT + 1)) ;;
55
+ MEDIUM) color="$YELLOW"; MEDIUM_COUNT=$((MEDIUM_COUNT + 1)) ;;
56
+ LOW) color="$BLUE"; LOW_COUNT=$((LOW_COUNT + 1)) ;;
67
57
  esac
68
58
 
69
59
  FINDINGS+=("${priority}|${category}|${title}|${description}|${remediation}")
@@ -133,7 +123,7 @@ scan_licenses() {
133
123
  [[ -f "$REPO_DIR/Cargo.toml" ]] && has_cargo=true
134
124
 
135
125
  # Check npm licenses
136
- if $has_npm && command -v npm &>/dev/null; then
126
+ if $has_npm && command -v npm >/dev/null 2>&1; then
137
127
  while IFS= read -r line; do
138
128
  [[ "$line" =~ GPL|AGPL ]] && [[ ! "$line" =~ MIT|Apache|BSD ]] && \
139
129
  add_finding "MEDIUM" "licenses" "GPL/AGPL dependency in npm project" \
@@ -173,10 +163,10 @@ scan_vulnerabilities() {
173
163
  local vuln_count=0
174
164
 
175
165
  # Check npm vulnerabilities
176
- if [[ -f "$REPO_DIR/package.json" ]] && command -v npm &>/dev/null; then
166
+ if [[ -f "$REPO_DIR/package.json" ]] && command -v npm >/dev/null 2>&1; then
177
167
  while IFS= read -r line; do
178
168
  [[ -z "$line" ]] && continue
179
- ((vuln_count++))
169
+ vuln_count=$((vuln_count + 1))
180
170
  add_finding "HIGH" "vulnerabilities" "npm security vulnerability" \
181
171
  "Found npm audit issue: $line" \
182
172
  "Run 'npm audit fix' to remediate. Update vulnerable dependencies. Re-test after updates."
@@ -184,11 +174,11 @@ scan_vulnerabilities() {
184
174
  fi
185
175
 
186
176
  # Check pip vulnerabilities
187
- if [[ -f "$REPO_DIR/requirements.txt" ]] && command -v pip &>/dev/null; then
188
- if command -v safety &>/dev/null; then
177
+ if [[ -f "$REPO_DIR/requirements.txt" ]] && command -v pip >/dev/null 2>&1; then
178
+ if command -v safety >/dev/null 2>&1; then
189
179
  while IFS= read -r line; do
190
180
  [[ -z "$line" ]] && continue
191
- ((vuln_count++))
181
+ vuln_count=$((vuln_count + 1))
192
182
  add_finding "HIGH" "vulnerabilities" "Python package vulnerability" \
193
183
  "Found via safety: $line" \
194
184
  "Update vulnerable package. Test compatibility. Run safety check after updates."
@@ -210,7 +200,7 @@ generate_sbom() {
210
200
  local sbom='{"bomFormat":"CycloneDX","specVersion":"1.4","version":1,"components":[]}'
211
201
 
212
202
  # Add npm packages
213
- if [[ -f "$REPO_DIR/package.json" ]] && command -v npm &>/dev/null; then
203
+ if [[ -f "$REPO_DIR/package.json" ]] && command -v npm >/dev/null 2>&1; then
214
204
  local npm_list
215
205
  npm_list=$(npm list --json 2>/dev/null || echo '{"dependencies":{}}')
216
206
  while IFS='=' read -r name version; do