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.
- package/README.md +95 -28
- package/completions/_shipwright +1 -1
- package/completions/shipwright.bash +3 -8
- package/completions/shipwright.fish +1 -1
- package/config/defaults.json +111 -0
- package/config/event-schema.json +81 -0
- package/config/policy.json +155 -2
- package/config/policy.schema.json +162 -1
- package/dashboard/coverage/coverage-summary.json +14 -0
- package/dashboard/public/index.html +1 -1
- package/dashboard/server.ts +306 -17
- package/dashboard/src/components/charts/bar.test.ts +79 -0
- package/dashboard/src/components/charts/donut.test.ts +68 -0
- package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
- package/dashboard/src/components/charts/sparkline.test.ts +125 -0
- package/dashboard/src/core/api.test.ts +309 -0
- package/dashboard/src/core/helpers.test.ts +301 -0
- package/dashboard/src/core/router.test.ts +307 -0
- package/dashboard/src/core/router.ts +7 -0
- package/dashboard/src/core/sse.test.ts +144 -0
- package/dashboard/src/views/metrics.test.ts +186 -0
- package/dashboard/src/views/overview.test.ts +173 -0
- package/dashboard/src/views/pipelines.test.ts +183 -0
- package/dashboard/src/views/team.test.ts +253 -0
- package/dashboard/vitest.config.ts +14 -5
- package/docs/TIPS.md +1 -1
- package/docs/patterns/README.md +1 -1
- package/package.json +15 -5
- package/scripts/adapters/docker-deploy.sh +1 -1
- package/scripts/adapters/tmux-adapter.sh +11 -1
- package/scripts/adapters/wezterm-adapter.sh +1 -1
- package/scripts/check-version-consistency.sh +1 -1
- package/scripts/lib/architecture.sh +126 -0
- package/scripts/lib/bootstrap.sh +75 -0
- package/scripts/lib/compat.sh +89 -6
- package/scripts/lib/config.sh +91 -0
- package/scripts/lib/daemon-adaptive.sh +3 -3
- package/scripts/lib/daemon-dispatch.sh +39 -16
- package/scripts/lib/daemon-health.sh +1 -1
- package/scripts/lib/daemon-patrol.sh +24 -12
- package/scripts/lib/daemon-poll.sh +37 -25
- package/scripts/lib/daemon-state.sh +115 -23
- package/scripts/lib/daemon-triage.sh +30 -8
- package/scripts/lib/fleet-failover.sh +63 -0
- package/scripts/lib/helpers.sh +30 -6
- package/scripts/lib/pipeline-detection.sh +2 -2
- package/scripts/lib/pipeline-github.sh +9 -9
- package/scripts/lib/pipeline-intelligence.sh +85 -35
- package/scripts/lib/pipeline-quality-checks.sh +16 -16
- package/scripts/lib/pipeline-quality.sh +1 -1
- package/scripts/lib/pipeline-stages.sh +242 -28
- package/scripts/lib/pipeline-state.sh +40 -4
- package/scripts/lib/test-helpers.sh +247 -0
- package/scripts/postinstall.mjs +3 -11
- package/scripts/sw +10 -4
- package/scripts/sw-activity.sh +1 -11
- package/scripts/sw-adaptive.sh +109 -85
- package/scripts/sw-adversarial.sh +4 -14
- package/scripts/sw-architecture-enforcer.sh +1 -11
- package/scripts/sw-auth.sh +8 -17
- package/scripts/sw-autonomous.sh +111 -49
- package/scripts/sw-changelog.sh +1 -11
- package/scripts/sw-checkpoint.sh +144 -20
- package/scripts/sw-ci.sh +2 -12
- package/scripts/sw-cleanup.sh +13 -17
- package/scripts/sw-code-review.sh +16 -36
- package/scripts/sw-connect.sh +5 -12
- package/scripts/sw-context.sh +9 -26
- package/scripts/sw-cost.sh +6 -16
- package/scripts/sw-daemon.sh +75 -70
- package/scripts/sw-dashboard.sh +57 -17
- package/scripts/sw-db.sh +506 -15
- package/scripts/sw-decompose.sh +1 -11
- package/scripts/sw-deps.sh +15 -25
- package/scripts/sw-developer-simulation.sh +1 -11
- package/scripts/sw-discovery.sh +112 -30
- package/scripts/sw-doc-fleet.sh +7 -17
- package/scripts/sw-docs-agent.sh +6 -16
- package/scripts/sw-docs.sh +4 -12
- package/scripts/sw-doctor.sh +134 -43
- package/scripts/sw-dora.sh +11 -19
- package/scripts/sw-durable.sh +35 -52
- package/scripts/sw-e2e-orchestrator.sh +11 -27
- package/scripts/sw-eventbus.sh +115 -115
- package/scripts/sw-evidence.sh +748 -0
- package/scripts/sw-feedback.sh +3 -13
- package/scripts/sw-fix.sh +2 -20
- package/scripts/sw-fleet-discover.sh +1 -11
- package/scripts/sw-fleet-viz.sh +10 -18
- package/scripts/sw-fleet.sh +13 -17
- package/scripts/sw-github-app.sh +6 -16
- package/scripts/sw-github-checks.sh +1 -11
- package/scripts/sw-github-deploy.sh +1 -11
- package/scripts/sw-github-graphql.sh +2 -12
- package/scripts/sw-guild.sh +1 -11
- package/scripts/sw-heartbeat.sh +49 -12
- package/scripts/sw-hygiene.sh +45 -43
- package/scripts/sw-incident.sh +284 -67
- package/scripts/sw-init.sh +35 -37
- package/scripts/sw-instrument.sh +1 -11
- package/scripts/sw-intelligence.sh +362 -51
- package/scripts/sw-jira.sh +5 -14
- package/scripts/sw-launchd.sh +2 -12
- package/scripts/sw-linear.sh +8 -17
- package/scripts/sw-logs.sh +4 -12
- package/scripts/sw-loop.sh +641 -90
- package/scripts/sw-memory.sh +243 -17
- package/scripts/sw-mission-control.sh +2 -12
- package/scripts/sw-model-router.sh +73 -34
- package/scripts/sw-otel.sh +11 -21
- package/scripts/sw-oversight.sh +1 -11
- package/scripts/sw-patrol-meta.sh +5 -11
- package/scripts/sw-pipeline-composer.sh +7 -17
- package/scripts/sw-pipeline-vitals.sh +1 -11
- package/scripts/sw-pipeline.sh +478 -122
- package/scripts/sw-pm.sh +2 -12
- package/scripts/sw-pr-lifecycle.sh +203 -29
- package/scripts/sw-predictive.sh +16 -22
- package/scripts/sw-prep.sh +6 -16
- package/scripts/sw-ps.sh +1 -11
- package/scripts/sw-public-dashboard.sh +2 -12
- package/scripts/sw-quality.sh +77 -10
- package/scripts/sw-reaper.sh +1 -11
- package/scripts/sw-recruit.sh +15 -25
- package/scripts/sw-regression.sh +11 -21
- package/scripts/sw-release-manager.sh +19 -28
- package/scripts/sw-release.sh +8 -16
- package/scripts/sw-remote.sh +1 -11
- package/scripts/sw-replay.sh +48 -44
- package/scripts/sw-retro.sh +70 -92
- package/scripts/sw-review-rerun.sh +220 -0
- package/scripts/sw-scale.sh +109 -32
- package/scripts/sw-security-audit.sh +12 -22
- package/scripts/sw-self-optimize.sh +239 -23
- package/scripts/sw-session.sh +3 -13
- package/scripts/sw-setup.sh +8 -18
- package/scripts/sw-standup.sh +5 -15
- package/scripts/sw-status.sh +32 -23
- package/scripts/sw-strategic.sh +129 -13
- package/scripts/sw-stream.sh +1 -11
- package/scripts/sw-swarm.sh +76 -36
- package/scripts/sw-team-stages.sh +10 -20
- package/scripts/sw-templates.sh +4 -14
- package/scripts/sw-testgen.sh +3 -13
- package/scripts/sw-tmux-pipeline.sh +1 -19
- package/scripts/sw-tmux-role-color.sh +0 -10
- package/scripts/sw-tmux-status.sh +3 -11
- package/scripts/sw-tmux.sh +2 -20
- package/scripts/sw-trace.sh +1 -19
- package/scripts/sw-tracker-github.sh +0 -10
- package/scripts/sw-tracker-jira.sh +1 -11
- package/scripts/sw-tracker-linear.sh +1 -11
- package/scripts/sw-tracker.sh +7 -24
- package/scripts/sw-triage.sh +24 -34
- package/scripts/sw-upgrade.sh +5 -23
- package/scripts/sw-ux.sh +1 -19
- package/scripts/sw-webhook.sh +18 -32
- package/scripts/sw-widgets.sh +3 -21
- package/scripts/sw-worktree.sh +11 -27
- package/scripts/update-homebrew-sha.sh +67 -0
- package/templates/pipelines/tdd.json +72 -0
- 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
|
package/scripts/sw-scale.sh
CHANGED
|
@@ -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="
|
|
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
|
|
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
|
|
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
|
|
157
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
199
|
-
|
|
219
|
+
local first_arg="${1:-}"
|
|
220
|
+
local second_arg="${2:-}"
|
|
200
221
|
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
210
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
217
|
-
|
|
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="
|
|
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
|
|
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
|
|
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
|
|
188
|
-
if command -v safety
|
|
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
|
|
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
|