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.
Files changed (108) hide show
  1. package/README.md +114 -36
  2. package/completions/_shipwright +212 -32
  3. package/completions/shipwright.bash +97 -25
  4. package/docs/strategy/01-market-research.md +619 -0
  5. package/docs/strategy/02-mission-and-brand.md +587 -0
  6. package/docs/strategy/03-gtm-and-roadmap.md +759 -0
  7. package/docs/strategy/QUICK-START.txt +289 -0
  8. package/docs/strategy/README.md +172 -0
  9. package/package.json +4 -2
  10. package/scripts/sw +208 -1
  11. package/scripts/sw-activity.sh +500 -0
  12. package/scripts/sw-adaptive.sh +925 -0
  13. package/scripts/sw-adversarial.sh +1 -1
  14. package/scripts/sw-architecture-enforcer.sh +1 -1
  15. package/scripts/sw-auth.sh +613 -0
  16. package/scripts/sw-autonomous.sh +664 -0
  17. package/scripts/sw-changelog.sh +704 -0
  18. package/scripts/sw-checkpoint.sh +1 -1
  19. package/scripts/sw-ci.sh +602 -0
  20. package/scripts/sw-cleanup.sh +1 -1
  21. package/scripts/sw-code-review.sh +637 -0
  22. package/scripts/sw-connect.sh +1 -1
  23. package/scripts/sw-context.sh +605 -0
  24. package/scripts/sw-cost.sh +1 -1
  25. package/scripts/sw-daemon.sh +432 -130
  26. package/scripts/sw-dashboard.sh +1 -1
  27. package/scripts/sw-db.sh +540 -0
  28. package/scripts/sw-decompose.sh +539 -0
  29. package/scripts/sw-deps.sh +551 -0
  30. package/scripts/sw-developer-simulation.sh +1 -1
  31. package/scripts/sw-discovery.sh +412 -0
  32. package/scripts/sw-docs-agent.sh +539 -0
  33. package/scripts/sw-docs.sh +1 -1
  34. package/scripts/sw-doctor.sh +59 -1
  35. package/scripts/sw-dora.sh +615 -0
  36. package/scripts/sw-durable.sh +710 -0
  37. package/scripts/sw-e2e-orchestrator.sh +535 -0
  38. package/scripts/sw-eventbus.sh +393 -0
  39. package/scripts/sw-feedback.sh +471 -0
  40. package/scripts/sw-fix.sh +1 -1
  41. package/scripts/sw-fleet-discover.sh +567 -0
  42. package/scripts/sw-fleet-viz.sh +404 -0
  43. package/scripts/sw-fleet.sh +8 -1
  44. package/scripts/sw-github-app.sh +596 -0
  45. package/scripts/sw-github-checks.sh +1 -1
  46. package/scripts/sw-github-deploy.sh +1 -1
  47. package/scripts/sw-github-graphql.sh +1 -1
  48. package/scripts/sw-guild.sh +569 -0
  49. package/scripts/sw-heartbeat.sh +1 -1
  50. package/scripts/sw-hygiene.sh +559 -0
  51. package/scripts/sw-incident.sh +617 -0
  52. package/scripts/sw-init.sh +88 -1
  53. package/scripts/sw-instrument.sh +699 -0
  54. package/scripts/sw-intelligence.sh +1 -1
  55. package/scripts/sw-jira.sh +1 -1
  56. package/scripts/sw-launchd.sh +363 -28
  57. package/scripts/sw-linear.sh +1 -1
  58. package/scripts/sw-logs.sh +1 -1
  59. package/scripts/sw-loop.sh +64 -3
  60. package/scripts/sw-memory.sh +1 -1
  61. package/scripts/sw-mission-control.sh +487 -0
  62. package/scripts/sw-model-router.sh +545 -0
  63. package/scripts/sw-otel.sh +596 -0
  64. package/scripts/sw-oversight.sh +689 -0
  65. package/scripts/sw-pipeline-composer.sh +1 -1
  66. package/scripts/sw-pipeline-vitals.sh +1 -1
  67. package/scripts/sw-pipeline.sh +687 -24
  68. package/scripts/sw-pm.sh +693 -0
  69. package/scripts/sw-pr-lifecycle.sh +522 -0
  70. package/scripts/sw-predictive.sh +1 -1
  71. package/scripts/sw-prep.sh +1 -1
  72. package/scripts/sw-ps.sh +1 -1
  73. package/scripts/sw-public-dashboard.sh +798 -0
  74. package/scripts/sw-quality.sh +595 -0
  75. package/scripts/sw-reaper.sh +1 -1
  76. package/scripts/sw-recruit.sh +573 -0
  77. package/scripts/sw-regression.sh +642 -0
  78. package/scripts/sw-release-manager.sh +736 -0
  79. package/scripts/sw-release.sh +706 -0
  80. package/scripts/sw-remote.sh +1 -1
  81. package/scripts/sw-replay.sh +520 -0
  82. package/scripts/sw-retro.sh +691 -0
  83. package/scripts/sw-scale.sh +444 -0
  84. package/scripts/sw-security-audit.sh +505 -0
  85. package/scripts/sw-self-optimize.sh +1 -1
  86. package/scripts/sw-session.sh +1 -1
  87. package/scripts/sw-setup.sh +1 -1
  88. package/scripts/sw-standup.sh +712 -0
  89. package/scripts/sw-status.sh +1 -1
  90. package/scripts/sw-strategic.sh +658 -0
  91. package/scripts/sw-stream.sh +450 -0
  92. package/scripts/sw-swarm.sh +583 -0
  93. package/scripts/sw-team-stages.sh +511 -0
  94. package/scripts/sw-templates.sh +1 -1
  95. package/scripts/sw-testgen.sh +515 -0
  96. package/scripts/sw-tmux-pipeline.sh +554 -0
  97. package/scripts/sw-tmux.sh +1 -1
  98. package/scripts/sw-trace.sh +485 -0
  99. package/scripts/sw-tracker-github.sh +188 -0
  100. package/scripts/sw-tracker-jira.sh +172 -0
  101. package/scripts/sw-tracker-linear.sh +251 -0
  102. package/scripts/sw-tracker.sh +117 -2
  103. package/scripts/sw-triage.sh +603 -0
  104. package/scripts/sw-upgrade.sh +1 -1
  105. package/scripts/sw-ux.sh +677 -0
  106. package/scripts/sw-webhook.sh +627 -0
  107. package/scripts/sw-widgets.sh +530 -0
  108. 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