shipwright-cli 1.10.0 → 2.1.0

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