shipwright-cli 1.10.0 → 2.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 +114 -36
- package/completions/_shipwright +212 -32
- package/completions/shipwright.bash +97 -25
- package/docs/strategy/01-market-research.md +619 -0
- package/docs/strategy/02-mission-and-brand.md +587 -0
- package/docs/strategy/03-gtm-and-roadmap.md +759 -0
- package/docs/strategy/QUICK-START.txt +289 -0
- package/docs/strategy/README.md +172 -0
- package/package.json +4 -2
- package/scripts/sw +208 -1
- package/scripts/sw-activity.sh +500 -0
- package/scripts/sw-adaptive.sh +925 -0
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +613 -0
- package/scripts/sw-autonomous.sh +664 -0
- package/scripts/sw-changelog.sh +704 -0
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +602 -0
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +637 -0
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +605 -0
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +432 -130
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +540 -0
- package/scripts/sw-decompose.sh +539 -0
- package/scripts/sw-deps.sh +551 -0
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +412 -0
- package/scripts/sw-docs-agent.sh +539 -0
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +59 -1
- package/scripts/sw-dora.sh +615 -0
- package/scripts/sw-durable.sh +710 -0
- package/scripts/sw-e2e-orchestrator.sh +535 -0
- package/scripts/sw-eventbus.sh +393 -0
- package/scripts/sw-feedback.sh +471 -0
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +567 -0
- package/scripts/sw-fleet-viz.sh +404 -0
- package/scripts/sw-fleet.sh +8 -1
- package/scripts/sw-github-app.sh +596 -0
- package/scripts/sw-github-checks.sh +1 -1
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +569 -0
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +559 -0
- package/scripts/sw-incident.sh +617 -0
- package/scripts/sw-init.sh +88 -1
- package/scripts/sw-instrument.sh +699 -0
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +363 -28
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +64 -3
- package/scripts/sw-memory.sh +1 -1
- package/scripts/sw-mission-control.sh +487 -0
- package/scripts/sw-model-router.sh +545 -0
- package/scripts/sw-otel.sh +596 -0
- package/scripts/sw-oversight.sh +689 -0
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +687 -24
- package/scripts/sw-pm.sh +693 -0
- package/scripts/sw-pr-lifecycle.sh +522 -0
- package/scripts/sw-predictive.sh +1 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +798 -0
- package/scripts/sw-quality.sh +595 -0
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +573 -0
- package/scripts/sw-regression.sh +642 -0
- package/scripts/sw-release-manager.sh +736 -0
- package/scripts/sw-release.sh +706 -0
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +520 -0
- package/scripts/sw-retro.sh +691 -0
- package/scripts/sw-scale.sh +444 -0
- package/scripts/sw-security-audit.sh +505 -0
- package/scripts/sw-self-optimize.sh +1 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-standup.sh +712 -0
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +658 -0
- package/scripts/sw-stream.sh +450 -0
- package/scripts/sw-swarm.sh +583 -0
- package/scripts/sw-team-stages.sh +511 -0
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-testgen.sh +515 -0
- package/scripts/sw-tmux-pipeline.sh +554 -0
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +485 -0
- package/scripts/sw-tracker-github.sh +188 -0
- package/scripts/sw-tracker-jira.sh +172 -0
- package/scripts/sw-tracker-linear.sh +251 -0
- package/scripts/sw-tracker.sh +117 -2
- package/scripts/sw-triage.sh +603 -0
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +677 -0
- package/scripts/sw-webhook.sh +627 -0
- package/scripts/sw-widgets.sh +530 -0
- package/scripts/sw-worktree.sh +1 -1
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright swarm — Dynamic agent swarm management ║
|
|
4
|
+
# ║ Registry, spawning, scaling, health checks, performance tracking, retire ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
|
+
|
|
9
|
+
VERSION="2.0.0"
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
|
|
12
|
+
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
13
|
+
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
14
|
+
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
15
|
+
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
16
|
+
GREEN='\033[38;2;74;222;128m' # success
|
|
17
|
+
YELLOW='\033[38;2;250;204;21m' # warning
|
|
18
|
+
RED='\033[38;2;248;113;113m' # error
|
|
19
|
+
DIM='\033[2m'
|
|
20
|
+
BOLD='\033[1m'
|
|
21
|
+
RESET='\033[0m'
|
|
22
|
+
|
|
23
|
+
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
24
|
+
# shellcheck source=lib/compat.sh
|
|
25
|
+
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
26
|
+
|
|
27
|
+
# ─── Output Helpers ─────────────────────────────────────────────────────────
|
|
28
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
29
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
30
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
31
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
32
|
+
|
|
33
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
34
|
+
now_epoch() { date +%s; }
|
|
35
|
+
|
|
36
|
+
# ─── Constants ──────────────────────────────────────────────────────────────
|
|
37
|
+
SWARM_DIR="${HOME}/.shipwright/swarm"
|
|
38
|
+
REGISTRY_FILE="${SWARM_DIR}/registry.json"
|
|
39
|
+
CONFIG_FILE="${SWARM_DIR}/config.json"
|
|
40
|
+
METRICS_FILE="${SWARM_DIR}/metrics.jsonl"
|
|
41
|
+
HEALTH_LOG="${SWARM_DIR}/health.jsonl"
|
|
42
|
+
|
|
43
|
+
# ─── Ensure directories exist ──────────────────────────────────────────────
|
|
44
|
+
ensure_dirs() {
|
|
45
|
+
mkdir -p "$SWARM_DIR"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ─── Initialize config ─────────────────────────────────────────────────────
|
|
49
|
+
init_config() {
|
|
50
|
+
if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
51
|
+
local tmp_file
|
|
52
|
+
tmp_file=$(mktemp)
|
|
53
|
+
cat > "$tmp_file" << 'JSON'
|
|
54
|
+
{
|
|
55
|
+
"auto_scaling_enabled": false,
|
|
56
|
+
"min_agents": 1,
|
|
57
|
+
"max_agents": 8,
|
|
58
|
+
"target_utilization": 0.75,
|
|
59
|
+
"health_check_interval": 30,
|
|
60
|
+
"stall_detection_threshold": 300,
|
|
61
|
+
"agent_types": {
|
|
62
|
+
"fast": {"cost_multiplier": 1.0, "capability": "simple"},
|
|
63
|
+
"standard": {"cost_multiplier": 2.0, "capability": "complex"},
|
|
64
|
+
"powerful": {"cost_multiplier": 4.0, "capability": "expert"}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
JSON
|
|
68
|
+
mv "$tmp_file" "$CONFIG_FILE"
|
|
69
|
+
success "Initialized swarm config"
|
|
70
|
+
fi
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# ─── Initialize registry ───────────────────────────────────────────────────
|
|
74
|
+
init_registry() {
|
|
75
|
+
if [[ ! -f "$REGISTRY_FILE" ]]; then
|
|
76
|
+
local tmp_file
|
|
77
|
+
tmp_file=$(mktemp)
|
|
78
|
+
cat > "$tmp_file" << 'JSON'
|
|
79
|
+
{
|
|
80
|
+
"agents": [],
|
|
81
|
+
"active_count": 0,
|
|
82
|
+
"last_updated": "2025-01-01T00:00:00Z"
|
|
83
|
+
}
|
|
84
|
+
JSON
|
|
85
|
+
mv "$tmp_file" "$REGISTRY_FILE"
|
|
86
|
+
fi
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# ─── Generate unique agent ID ─────────────────────────────────────────────
|
|
90
|
+
gen_agent_id() {
|
|
91
|
+
local prefix="${1:-agent}"
|
|
92
|
+
echo "${prefix}-$(date +%s)-$((RANDOM % 10000))"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# ─── Record metric ────────────────────────────────────────────────────────
|
|
96
|
+
record_metric() {
|
|
97
|
+
local agent_id="$1"
|
|
98
|
+
local metric_type="$2" # spawn, complete, fail, retire, stall
|
|
99
|
+
local value="${3:-1}"
|
|
100
|
+
local context="${4:-}"
|
|
101
|
+
|
|
102
|
+
local metric
|
|
103
|
+
metric=$(jq -c -n \
|
|
104
|
+
--arg ts "$(now_iso)" \
|
|
105
|
+
--arg agent_id "$agent_id" \
|
|
106
|
+
--arg metric_type "$metric_type" \
|
|
107
|
+
--arg value "$value" \
|
|
108
|
+
--arg context "$context" \
|
|
109
|
+
'{ts: $ts, agent_id: $agent_id, metric_type: $metric_type, value: $value, context: $context}')
|
|
110
|
+
|
|
111
|
+
echo "$metric" >> "$METRICS_FILE"
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# ─── Spawn a new agent ────────────────────────────────────────────────────
|
|
115
|
+
cmd_spawn() {
|
|
116
|
+
local agent_type="${1:-standard}"
|
|
117
|
+
shift 2>/dev/null || true
|
|
118
|
+
|
|
119
|
+
ensure_dirs
|
|
120
|
+
init_registry
|
|
121
|
+
init_config
|
|
122
|
+
|
|
123
|
+
local agent_id
|
|
124
|
+
agent_id=$(gen_agent_id)
|
|
125
|
+
|
|
126
|
+
# Validate agent type
|
|
127
|
+
if ! jq -e ".agent_types | has(\"$agent_type\")" "$CONFIG_FILE" >/dev/null 2>&1; then
|
|
128
|
+
error "Invalid agent type: $agent_type"
|
|
129
|
+
return 1
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# Check max agents
|
|
133
|
+
local max_agents
|
|
134
|
+
max_agents=$(jq -r '.max_agents // 8' "$CONFIG_FILE")
|
|
135
|
+
local active_count
|
|
136
|
+
active_count=$(jq -r '.active_count // 0' "$REGISTRY_FILE")
|
|
137
|
+
|
|
138
|
+
if [[ $active_count -ge $max_agents ]]; then
|
|
139
|
+
error "Max agents reached ($max_agents). Retire an agent first."
|
|
140
|
+
return 1
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Add agent to registry
|
|
144
|
+
local tmp_file
|
|
145
|
+
tmp_file=$(mktemp)
|
|
146
|
+
|
|
147
|
+
jq --arg agent_id "$agent_id" \
|
|
148
|
+
--arg agent_type "$agent_type" \
|
|
149
|
+
'.agents += [{
|
|
150
|
+
id: $agent_id,
|
|
151
|
+
type: $agent_type,
|
|
152
|
+
status: "active",
|
|
153
|
+
spawned_at: "'$(now_iso)'",
|
|
154
|
+
current_task: null,
|
|
155
|
+
task_started_at: null,
|
|
156
|
+
success_count: 0,
|
|
157
|
+
failure_count: 0,
|
|
158
|
+
avg_completion_time: 0,
|
|
159
|
+
quality_score: 100,
|
|
160
|
+
resource_usage: {cpu: 0, memory: 0},
|
|
161
|
+
last_heartbeat: "'$(now_iso)'"
|
|
162
|
+
}] | .active_count += 1 | .last_updated = "'$(now_iso)'"' \
|
|
163
|
+
"$REGISTRY_FILE" > "$tmp_file"
|
|
164
|
+
|
|
165
|
+
mv "$tmp_file" "$REGISTRY_FILE"
|
|
166
|
+
record_metric "$agent_id" "spawn" "1" "$agent_type"
|
|
167
|
+
|
|
168
|
+
success "Spawned agent: ${CYAN}${agent_id}${RESET} (type: ${agent_type})"
|
|
169
|
+
echo ""
|
|
170
|
+
echo -e " Agent ID: ${CYAN}${agent_id}${RESET}"
|
|
171
|
+
echo -e " Type: ${CYAN}${agent_type}${RESET}"
|
|
172
|
+
echo -e " Status: ${GREEN}active${RESET}"
|
|
173
|
+
echo -e " Spawned: $(now_iso)"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# ─── Retire an agent ──────────────────────────────────────────────────────
|
|
177
|
+
cmd_retire() {
|
|
178
|
+
local agent_id="${1:-}"
|
|
179
|
+
shift 2>/dev/null || true
|
|
180
|
+
|
|
181
|
+
if [[ -z "$agent_id" ]]; then
|
|
182
|
+
error "Usage: shipwright swarm retire <agent-id>"
|
|
183
|
+
return 1
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
ensure_dirs
|
|
187
|
+
init_registry
|
|
188
|
+
|
|
189
|
+
# Find agent
|
|
190
|
+
local agent
|
|
191
|
+
agent=$(jq --arg aid "$agent_id" '.agents[] | select(.id == $aid)' "$REGISTRY_FILE" 2>/dev/null)
|
|
192
|
+
|
|
193
|
+
if [[ -z "$agent" ]]; then
|
|
194
|
+
error "Agent not found: $agent_id"
|
|
195
|
+
return 1
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# Check if agent has active tasks
|
|
199
|
+
local current_task
|
|
200
|
+
current_task=$(echo "$agent" | jq -r '.current_task // "null"')
|
|
201
|
+
|
|
202
|
+
if [[ "$current_task" != "null" ]]; then
|
|
203
|
+
warn "Agent has active task: $current_task"
|
|
204
|
+
echo " Waiting for task completion before retirement..."
|
|
205
|
+
echo " (In production, implement drain timeout)"
|
|
206
|
+
echo ""
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
# Mark as retiring
|
|
210
|
+
local tmp_file
|
|
211
|
+
tmp_file=$(mktemp)
|
|
212
|
+
|
|
213
|
+
jq --arg aid "$agent_id" \
|
|
214
|
+
'.agents |= map(if .id == $aid then .status = "retiring" else . end) | .last_updated = "'$(now_iso)'"' \
|
|
215
|
+
"$REGISTRY_FILE" > "$tmp_file"
|
|
216
|
+
|
|
217
|
+
mv "$tmp_file" "$REGISTRY_FILE"
|
|
218
|
+
record_metric "$agent_id" "retire" "1" "graceful_shutdown"
|
|
219
|
+
|
|
220
|
+
success "Retiring agent: ${CYAN}${agent_id}${RESET}"
|
|
221
|
+
echo ""
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
# ─── Health check ────────────────────────────────────────────────────────
|
|
225
|
+
cmd_health() {
|
|
226
|
+
local agent_id="${1:-all}"
|
|
227
|
+
shift 2>/dev/null || true
|
|
228
|
+
|
|
229
|
+
ensure_dirs
|
|
230
|
+
init_registry
|
|
231
|
+
init_config
|
|
232
|
+
|
|
233
|
+
local stall_threshold
|
|
234
|
+
stall_threshold=$(jq -r '.stall_detection_threshold // 300' "$CONFIG_FILE")
|
|
235
|
+
|
|
236
|
+
local now
|
|
237
|
+
now=$(now_epoch)
|
|
238
|
+
|
|
239
|
+
if [[ "$agent_id" == "all" ]]; then
|
|
240
|
+
# Health check all agents
|
|
241
|
+
info "Agent Health Status"
|
|
242
|
+
echo ""
|
|
243
|
+
|
|
244
|
+
local agents_array
|
|
245
|
+
agents_array=$(jq -r '.agents | length' "$REGISTRY_FILE")
|
|
246
|
+
|
|
247
|
+
if [[ $agents_array -eq 0 ]]; then
|
|
248
|
+
echo -e " ${DIM}No agents in swarm${RESET}"
|
|
249
|
+
return 0
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
jq -r '.agents[] | @base64' "$REGISTRY_FILE" | while read -r line; do
|
|
253
|
+
local agent
|
|
254
|
+
agent=$(echo "$line" | base64 -d 2>/dev/null || echo "$line")
|
|
255
|
+
|
|
256
|
+
local id status last_hb
|
|
257
|
+
id=$(echo "$agent" | jq -r '.id')
|
|
258
|
+
status=$(echo "$agent" | jq -r '.status')
|
|
259
|
+
last_hb=$(echo "$agent" | jq -r '.last_heartbeat')
|
|
260
|
+
|
|
261
|
+
# Convert ISO to epoch
|
|
262
|
+
local last_hb_epoch
|
|
263
|
+
last_hb_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$last_hb" +%s 2>/dev/null || echo "0")
|
|
264
|
+
local elapsed=$((now - last_hb_epoch))
|
|
265
|
+
|
|
266
|
+
if [[ $elapsed -gt $stall_threshold ]]; then
|
|
267
|
+
echo -e " ${RED}✗${RESET} ${id} (${YELLOW}stalled${RESET}, ${elapsed}s inactive)"
|
|
268
|
+
elif [[ "$status" == "active" ]]; then
|
|
269
|
+
echo -e " ${GREEN}✓${RESET} ${id} (healthy)"
|
|
270
|
+
else
|
|
271
|
+
echo -e " ${DIM}◦${RESET} ${id} (${status})"
|
|
272
|
+
fi
|
|
273
|
+
done
|
|
274
|
+
|
|
275
|
+
local healthy inactive
|
|
276
|
+
healthy=$(jq -r '[.agents[] | select(.status == "active")] | length' "$REGISTRY_FILE")
|
|
277
|
+
inactive=$(jq -r '[.agents[] | select(.status != "active")] | length' "$REGISTRY_FILE")
|
|
278
|
+
|
|
279
|
+
echo ""
|
|
280
|
+
echo -e " Summary: ${GREEN}${healthy}${RESET} healthy, ${DIM}${inactive}${RESET} inactive"
|
|
281
|
+
else
|
|
282
|
+
# Health check single agent
|
|
283
|
+
local agent
|
|
284
|
+
agent=$(jq --arg aid "$agent_id" '.agents[] | select(.id == $aid)' "$REGISTRY_FILE" 2>/dev/null)
|
|
285
|
+
|
|
286
|
+
if [[ -z "$agent" ]]; then
|
|
287
|
+
error "Agent not found: $agent_id"
|
|
288
|
+
return 1
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
info "Health Check: ${agent_id}"
|
|
292
|
+
echo ""
|
|
293
|
+
echo "$agent" | jq '.' | sed 's/^/ /'
|
|
294
|
+
fi
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# ─── Auto-scale logic ────────────────────────────────────────────────────
|
|
298
|
+
cmd_scale() {
|
|
299
|
+
ensure_dirs
|
|
300
|
+
init_registry
|
|
301
|
+
init_config
|
|
302
|
+
|
|
303
|
+
local auto_scale_enabled
|
|
304
|
+
auto_scale_enabled=$(jq -r '.auto_scaling_enabled' "$CONFIG_FILE")
|
|
305
|
+
|
|
306
|
+
if [[ "$auto_scale_enabled" != "true" ]]; then
|
|
307
|
+
warn "Auto-scaling is disabled"
|
|
308
|
+
return 0
|
|
309
|
+
fi
|
|
310
|
+
|
|
311
|
+
local min_agents max_agents target_util
|
|
312
|
+
min_agents=$(jq -r '.min_agents // 1' "$CONFIG_FILE")
|
|
313
|
+
max_agents=$(jq -r '.max_agents // 8' "$CONFIG_FILE")
|
|
314
|
+
target_util=$(jq -r '.target_utilization // 0.75' "$CONFIG_FILE")
|
|
315
|
+
|
|
316
|
+
local active_count
|
|
317
|
+
active_count=$(jq -r '.active_count // 0' "$REGISTRY_FILE")
|
|
318
|
+
|
|
319
|
+
# TODO: Implement queue depth and resource monitoring
|
|
320
|
+
# For now, just show current state
|
|
321
|
+
info "Auto-Scaling Analysis"
|
|
322
|
+
echo ""
|
|
323
|
+
echo -e " Current agents: ${CYAN}${active_count}/${max_agents}${RESET}"
|
|
324
|
+
echo -e " Min agents: ${CYAN}${min_agents}${RESET}"
|
|
325
|
+
echo -e " Target utilization: ${CYAN}${target_util}${RESET}"
|
|
326
|
+
echo ""
|
|
327
|
+
echo -e " ${DIM}Queue depth monitoring and scaling recommendations require active pipeline${RESET}"
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
# ─── Performance leaderboard ──────────────────────────────────────────────
|
|
331
|
+
cmd_top() {
|
|
332
|
+
local limit="${1:-10}"
|
|
333
|
+
shift 2>/dev/null || true
|
|
334
|
+
|
|
335
|
+
ensure_dirs
|
|
336
|
+
init_registry
|
|
337
|
+
|
|
338
|
+
info "Agent Performance Leaderboard (Top ${limit})"
|
|
339
|
+
echo ""
|
|
340
|
+
|
|
341
|
+
jq -r --arg limit "$limit" '.agents | sort_by(-.quality_score) | .[0:($limit|tonumber)] | .[] |
|
|
342
|
+
" \(.id) — Score: \(.quality_score) | Success: \(.success_count) | Avg time: \(.avg_completion_time)s | Status: \(.status)"' \
|
|
343
|
+
"$REGISTRY_FILE"
|
|
344
|
+
|
|
345
|
+
echo ""
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
# ─── Swarm topology visualization ─────────────────────────────────────────
|
|
349
|
+
cmd_topology() {
|
|
350
|
+
ensure_dirs
|
|
351
|
+
init_registry
|
|
352
|
+
init_config
|
|
353
|
+
|
|
354
|
+
info "Swarm Topology"
|
|
355
|
+
echo ""
|
|
356
|
+
|
|
357
|
+
local active_count total_agents
|
|
358
|
+
active_count=$(jq -r '.active_count // 0' "$REGISTRY_FILE")
|
|
359
|
+
total_agents=$(jq -r '.agents | length' "$REGISTRY_FILE")
|
|
360
|
+
|
|
361
|
+
echo -e " ${CYAN}┌─ Agent Swarm ─┐${RESET}"
|
|
362
|
+
echo -e " ${CYAN}│${RESET} Active: ${GREEN}${active_count}${RESET}/${YELLOW}${total_agents}${RESET}"
|
|
363
|
+
echo ""
|
|
364
|
+
|
|
365
|
+
# Group by type
|
|
366
|
+
echo -e " Agent Types:"
|
|
367
|
+
jq -r '.agents | group_by(.type) | .[] | " ◇ \(.[0].type): \(length)"' "$REGISTRY_FILE" | \
|
|
368
|
+
sed "s/◇/${CYAN}◇${RESET}/g"
|
|
369
|
+
|
|
370
|
+
echo ""
|
|
371
|
+
echo -e " Resource Allocation:"
|
|
372
|
+
|
|
373
|
+
jq -r '.agents | .[] |
|
|
374
|
+
" ▪ \(.id): CPU=\(.resource_usage.cpu)% MEM=\(.resource_usage.memory)MB Task: \(.current_task // "idle")"' \
|
|
375
|
+
"$REGISTRY_FILE" | head -5 | sed "s/▪/${PURPLE}▪${RESET}/g"
|
|
376
|
+
|
|
377
|
+
local remaining
|
|
378
|
+
remaining=$((total_agents - 5))
|
|
379
|
+
if [[ $remaining -gt 0 ]]; then
|
|
380
|
+
echo -e " ${DIM}... and ${remaining} more${RESET}"
|
|
381
|
+
fi
|
|
382
|
+
|
|
383
|
+
echo ""
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
# ─── Show swarm status ────────────────────────────────────────────────────
|
|
387
|
+
cmd_status() {
|
|
388
|
+
ensure_dirs
|
|
389
|
+
init_registry
|
|
390
|
+
init_config
|
|
391
|
+
|
|
392
|
+
info "Swarm Status"
|
|
393
|
+
echo ""
|
|
394
|
+
|
|
395
|
+
local active_count total_agents
|
|
396
|
+
active_count=$(jq -r '.active_count // 0' "$REGISTRY_FILE")
|
|
397
|
+
total_agents=$(jq -r '.agents | length' "$REGISTRY_FILE")
|
|
398
|
+
|
|
399
|
+
local avg_quality=0
|
|
400
|
+
if [[ $total_agents -gt 0 ]]; then
|
|
401
|
+
avg_quality=$(jq '[.agents[].quality_score] | add / length' "$REGISTRY_FILE" 2>/dev/null || echo "0")
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
echo -e " Total agents: ${CYAN}${total_agents}${RESET}"
|
|
405
|
+
echo -e " Active agents: ${GREEN}${active_count}${RESET}"
|
|
406
|
+
echo -e " Avg quality score: ${CYAN}${avg_quality}${RESET}"
|
|
407
|
+
echo -e " Config file: ${DIM}${CONFIG_FILE}${RESET}"
|
|
408
|
+
echo ""
|
|
409
|
+
|
|
410
|
+
if [[ $total_agents -eq 0 ]]; then
|
|
411
|
+
echo -e " ${DIM}No agents in swarm. Spawn one with: shipwright swarm spawn${RESET}"
|
|
412
|
+
fi
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
# ─── Configure scaling parameters ────────────────────────────────────────
|
|
416
|
+
cmd_config() {
|
|
417
|
+
local subcmd="${1:-show}"
|
|
418
|
+
shift 2>/dev/null || true
|
|
419
|
+
|
|
420
|
+
ensure_dirs
|
|
421
|
+
init_config
|
|
422
|
+
|
|
423
|
+
case "$subcmd" in
|
|
424
|
+
show)
|
|
425
|
+
info "Swarm Configuration"
|
|
426
|
+
echo ""
|
|
427
|
+
cat "$CONFIG_FILE" | jq '.' | sed 's/^/ /'
|
|
428
|
+
echo ""
|
|
429
|
+
;;
|
|
430
|
+
set)
|
|
431
|
+
local key="${1:-}"
|
|
432
|
+
local value="${2:-}"
|
|
433
|
+
|
|
434
|
+
if [[ -z "$key" || -z "$value" ]]; then
|
|
435
|
+
error "Usage: shipwright swarm config set <key> <value>"
|
|
436
|
+
return 1
|
|
437
|
+
fi
|
|
438
|
+
|
|
439
|
+
local tmp_file
|
|
440
|
+
tmp_file=$(mktemp)
|
|
441
|
+
|
|
442
|
+
jq --arg key "$key" --arg value "$value" \
|
|
443
|
+
'if ($value | test("^[0-9]+$")) then
|
|
444
|
+
.[$key] = ($value | tonumber)
|
|
445
|
+
elif ($value == "true" or $value == "false") then
|
|
446
|
+
.[$key] = ($value | fromjson)
|
|
447
|
+
else
|
|
448
|
+
.[$key] = $value
|
|
449
|
+
end' "$CONFIG_FILE" > "$tmp_file"
|
|
450
|
+
|
|
451
|
+
mv "$tmp_file" "$CONFIG_FILE"
|
|
452
|
+
success "Updated: ${key} = ${value}"
|
|
453
|
+
;;
|
|
454
|
+
reset)
|
|
455
|
+
rm -f "$CONFIG_FILE"
|
|
456
|
+
init_config
|
|
457
|
+
success "Config reset to defaults"
|
|
458
|
+
;;
|
|
459
|
+
*)
|
|
460
|
+
error "Unknown subcommand: $subcmd"
|
|
461
|
+
echo -e " Valid: ${CYAN}show${RESET}, ${CYAN}set${RESET}, ${CYAN}reset${RESET}"
|
|
462
|
+
return 1
|
|
463
|
+
;;
|
|
464
|
+
esac
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
# ─── Help message ─────────────────────────────────────────────────────────
|
|
468
|
+
cmd_help() {
|
|
469
|
+
cat << 'EOF'
|
|
470
|
+
shipwright swarm — Dynamic agent swarm management
|
|
471
|
+
|
|
472
|
+
USAGE
|
|
473
|
+
shipwright swarm <command> [options]
|
|
474
|
+
|
|
475
|
+
COMMANDS
|
|
476
|
+
spawn [type] Spawn new agent (type: fast/standard/powerful, default: standard)
|
|
477
|
+
retire <agent-id> Gracefully retire an agent (drain tasks first)
|
|
478
|
+
health [agent-id] Check agent health (all agents or specific one)
|
|
479
|
+
scale Show auto-scaling status and recommendations
|
|
480
|
+
top [N] Show top N performing agents (default: 10)
|
|
481
|
+
topology Visualize swarm structure and resource allocation
|
|
482
|
+
status Show swarm overview (active agents, utilization, queue)
|
|
483
|
+
config Manage configuration parameters
|
|
484
|
+
help Show this help message
|
|
485
|
+
|
|
486
|
+
CONFIG SUBCOMMANDS
|
|
487
|
+
config show Display current swarm configuration
|
|
488
|
+
config set <k> <v> Update a config parameter
|
|
489
|
+
config reset Reset to default configuration
|
|
490
|
+
|
|
491
|
+
CONFIGURATION (stored in ~/.shipwright/swarm/config.json)
|
|
492
|
+
auto_scaling_enabled Enable/disable automatic scaling (default: false)
|
|
493
|
+
min_agents Minimum agents to maintain (default: 1)
|
|
494
|
+
max_agents Maximum agents allowed (default: 8)
|
|
495
|
+
target_utilization Target CPU/task utilization (default: 0.75)
|
|
496
|
+
health_check_interval Interval between health checks in seconds (default: 30)
|
|
497
|
+
stall_detection_threshold Seconds before marking agent as stalled (default: 300)
|
|
498
|
+
|
|
499
|
+
AGENT TYPES
|
|
500
|
+
fast Low cost, optimized for simple tasks (1x cost multiplier)
|
|
501
|
+
standard Balanced cost/capability for complex tasks (2x cost multiplier)
|
|
502
|
+
powerful High capability for expert/difficult tasks (4x cost multiplier)
|
|
503
|
+
|
|
504
|
+
EXAMPLES
|
|
505
|
+
# Spawn a standard agent
|
|
506
|
+
shipwright swarm spawn
|
|
507
|
+
|
|
508
|
+
# Spawn a fast/cheap agent
|
|
509
|
+
shipwright swarm spawn fast
|
|
510
|
+
|
|
511
|
+
# Show health of all agents
|
|
512
|
+
shipwright swarm health
|
|
513
|
+
|
|
514
|
+
# Check specific agent health
|
|
515
|
+
shipwright swarm health agent-123
|
|
516
|
+
|
|
517
|
+
# View performance leaderboard
|
|
518
|
+
shipwright swarm top 20
|
|
519
|
+
|
|
520
|
+
# Show swarm topology
|
|
521
|
+
shipwright swarm topology
|
|
522
|
+
|
|
523
|
+
# Enable auto-scaling
|
|
524
|
+
shipwright swarm config set auto_scaling_enabled true
|
|
525
|
+
|
|
526
|
+
# Set max agents to 12
|
|
527
|
+
shipwright swarm config set max_agents 12
|
|
528
|
+
|
|
529
|
+
STATE FILES
|
|
530
|
+
Registry: ~/.shipwright/swarm/registry.json
|
|
531
|
+
Configuration: ~/.shipwright/swarm/config.json
|
|
532
|
+
Metrics: ~/.shipwright/swarm/metrics.jsonl
|
|
533
|
+
Health Log: ~/.shipwright/swarm/health.jsonl
|
|
534
|
+
|
|
535
|
+
EOF
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
# ─── Main router ──────────────────────────────────────────────────────────
|
|
539
|
+
main() {
|
|
540
|
+
local cmd="${1:-help}"
|
|
541
|
+
shift 2>/dev/null || true
|
|
542
|
+
|
|
543
|
+
case "$cmd" in
|
|
544
|
+
spawn)
|
|
545
|
+
cmd_spawn "$@"
|
|
546
|
+
;;
|
|
547
|
+
retire)
|
|
548
|
+
cmd_retire "$@"
|
|
549
|
+
;;
|
|
550
|
+
health)
|
|
551
|
+
cmd_health "$@"
|
|
552
|
+
;;
|
|
553
|
+
scale)
|
|
554
|
+
cmd_scale "$@"
|
|
555
|
+
;;
|
|
556
|
+
top)
|
|
557
|
+
cmd_top "$@"
|
|
558
|
+
;;
|
|
559
|
+
topology)
|
|
560
|
+
cmd_topology "$@"
|
|
561
|
+
;;
|
|
562
|
+
status)
|
|
563
|
+
cmd_status "$@"
|
|
564
|
+
;;
|
|
565
|
+
config)
|
|
566
|
+
cmd_config "$@"
|
|
567
|
+
;;
|
|
568
|
+
help|--help|-h)
|
|
569
|
+
cmd_help
|
|
570
|
+
;;
|
|
571
|
+
*)
|
|
572
|
+
error "Unknown command: $cmd"
|
|
573
|
+
echo ""
|
|
574
|
+
cmd_help
|
|
575
|
+
exit 1
|
|
576
|
+
;;
|
|
577
|
+
esac
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
# Only run main if this script is executed directly (not sourced)
|
|
581
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
582
|
+
main "$@"
|
|
583
|
+
fi
|