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,545 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright model-router — Intelligent Model Routing & Cost Optimization ║
4
+ # ║ Route tasks to optimal Claude models based on complexity and stage ║
5
+ # ║ Escalate on failure · Track costs · A/B test configurations ║
6
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
7
+ set -euo pipefail
8
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
9
+
10
+ VERSION="2.0.0"
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
13
+
14
+ # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
15
+ CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
16
+ PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
17
+ BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
18
+ GREEN='\033[38;2;74;222;128m' # success
19
+ YELLOW='\033[38;2;250;204;21m' # warning
20
+ RED='\033[38;2;248;113;113m' # error
21
+ DIM='\033[2m'
22
+ BOLD='\033[1m'
23
+ RESET='\033[0m'
24
+
25
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
26
+ # shellcheck source=lib/compat.sh
27
+ [[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
28
+
29
+ # ─── Output Helpers ─────────────────────────────────────────────────────────
30
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
31
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
32
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
33
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
34
+
35
+ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
36
+ now_epoch() { date +%s; }
37
+
38
+ # ─── File Paths ────────────────────────────────────────────────────────────
39
+ MODEL_ROUTING_CONFIG="${HOME}/.shipwright/model-routing.json"
40
+ MODEL_USAGE_LOG="${HOME}/.shipwright/model-usage.jsonl"
41
+ AB_RESULTS_FILE="${HOME}/.shipwright/ab-results.jsonl"
42
+
43
+ # ─── Model Costs (per million tokens) ───────────────────────────────────────
44
+ HAIKU_INPUT_COST="0.80"
45
+ HAIKU_OUTPUT_COST="4.00"
46
+ SONNET_INPUT_COST="3.00"
47
+ SONNET_OUTPUT_COST="15.00"
48
+ OPUS_INPUT_COST="15.00"
49
+ OPUS_OUTPUT_COST="75.00"
50
+
51
+ # ─── Default Routing Rules ──────────────────────────────────────────────────
52
+ # Stages that default to haiku (low complexity, fast)
53
+ HAIKU_STAGES="intake|monitor"
54
+ # Stages that default to sonnet (medium complexity)
55
+ SONNET_STAGES="test|review"
56
+ # Stages that default to opus (high complexity, needs deep thinking)
57
+ OPUS_STAGES="plan|design|build|compound_quality"
58
+
59
+ # ─── Complexity Thresholds ──────────────────────────────────────────────────
60
+ COMPLEXITY_LOW=30 # Below this: use sonnet
61
+ COMPLEXITY_HIGH=80 # Above this: use opus
62
+
63
+ # ─── Ensure Config File Exists ──────────────────────────────────────────────
64
+ ensure_config() {
65
+ mkdir -p "${HOME}/.shipwright"
66
+
67
+ if [[ ! -f "$MODEL_ROUTING_CONFIG" ]]; then
68
+ cat > "$MODEL_ROUTING_CONFIG" <<'CONFIG'
69
+ {
70
+ "version": "1.0",
71
+ "default_routing": {
72
+ "intake": "haiku",
73
+ "plan": "opus",
74
+ "design": "opus",
75
+ "build": "opus",
76
+ "test": "sonnet",
77
+ "review": "sonnet",
78
+ "compound_quality": "opus",
79
+ "pr": "sonnet",
80
+ "merge": "sonnet",
81
+ "deploy": "sonnet",
82
+ "validate": "haiku",
83
+ "monitor": "haiku"
84
+ },
85
+ "complexity_thresholds": {
86
+ "low": 30,
87
+ "high": 80
88
+ },
89
+ "escalation_policy": "linear",
90
+ "cost_aware_mode": false,
91
+ "max_cost_per_pipeline": 50.0,
92
+ "a_b_test": {
93
+ "enabled": false,
94
+ "percentage": 10,
95
+ "variant": "cost-optimized"
96
+ }
97
+ }
98
+ CONFIG
99
+ success "Created default routing config at $MODEL_ROUTING_CONFIG"
100
+ fi
101
+ }
102
+
103
+ # ─── Determine Model by Stage and Complexity ────────────────────────────────
104
+ route_model() {
105
+ local stage="$1"
106
+ local complexity="${2:-50}"
107
+
108
+ # Validate inputs
109
+ if [[ -z "$stage" ]]; then
110
+ error "stage is required"
111
+ return 1
112
+ fi
113
+
114
+ if ! [[ "$complexity" =~ ^[0-9]+$ ]] || [[ "$complexity" -lt 0 ]] || [[ "$complexity" -gt 100 ]]; then
115
+ error "complexity must be 0-100, got: $complexity"
116
+ return 1
117
+ fi
118
+
119
+ local model=""
120
+
121
+ # Complexity-based override (applies to all stages)
122
+ if [[ "$complexity" -lt "$COMPLEXITY_LOW" ]]; then
123
+ model="sonnet"
124
+ elif [[ "$complexity" -gt "$COMPLEXITY_HIGH" ]]; then
125
+ model="opus"
126
+ else
127
+ # Stage-based routing for medium complexity
128
+ if [[ "$stage" =~ $HAIKU_STAGES ]]; then
129
+ model="haiku"
130
+ elif [[ "$stage" =~ $SONNET_STAGES ]]; then
131
+ model="sonnet"
132
+ elif [[ "$stage" =~ $OPUS_STAGES ]]; then
133
+ model="opus"
134
+ else
135
+ # Default to sonnet for unknown stages
136
+ model="sonnet"
137
+ fi
138
+ fi
139
+
140
+ echo "$model"
141
+ }
142
+
143
+ # ─── Escalate to Next Model Tier ───────────────────────────────────────────
144
+ escalate_model() {
145
+ local current_model="$1"
146
+
147
+ if [[ -z "$current_model" ]]; then
148
+ error "current model is required"
149
+ return 1
150
+ fi
151
+
152
+ local next_model=""
153
+ case "$current_model" in
154
+ haiku) next_model="sonnet" ;;
155
+ sonnet) next_model="opus" ;;
156
+ opus) next_model="opus" ;; # Already at top
157
+ *) error "Unknown model: $current_model"; return 1 ;;
158
+ esac
159
+
160
+ echo "$next_model"
161
+ }
162
+
163
+ # ─── Show Configuration ─────────────────────────────────────────────────────
164
+ show_config() {
165
+ ensure_config
166
+
167
+ info "Model Routing Configuration"
168
+ echo ""
169
+
170
+ if command -v jq &>/dev/null; then
171
+ jq . "$MODEL_ROUTING_CONFIG" 2>/dev/null || cat "$MODEL_ROUTING_CONFIG"
172
+ else
173
+ cat "$MODEL_ROUTING_CONFIG"
174
+ fi
175
+ }
176
+
177
+ # ─── Set Configuration Value ───────────────────────────────────────────────
178
+ set_config() {
179
+ local key="$1"
180
+ local value="$2"
181
+
182
+ if [[ -z "$key" ]] || [[ -z "$value" ]]; then
183
+ error "Usage: shipwright model config set <key> <value>"
184
+ return 1
185
+ fi
186
+
187
+ ensure_config
188
+
189
+ if ! command -v jq &>/dev/null; then
190
+ error "jq is required for config updates"
191
+ return 1
192
+ fi
193
+
194
+ # Use jq to safely update the config
195
+ local tmp_config
196
+ tmp_config=$(mktemp)
197
+
198
+ if [[ "$value" == "true" ]] || [[ "$value" == "false" ]]; then
199
+ jq ".${key} = ${value}" "$MODEL_ROUTING_CONFIG" > "$tmp_config"
200
+ elif [[ "$value" =~ ^[0-9]+\.?[0-9]*$ ]]; then
201
+ jq ".${key} = ${value}" "$MODEL_ROUTING_CONFIG" > "$tmp_config"
202
+ else
203
+ jq ".${key} = \"${value}\"" "$MODEL_ROUTING_CONFIG" > "$tmp_config"
204
+ fi
205
+
206
+ mv "$tmp_config" "$MODEL_ROUTING_CONFIG"
207
+ success "Updated $key = $value"
208
+ }
209
+
210
+ # ─── Estimate Total Pipeline Cost ──────────────────────────────────────────
211
+ estimate_cost() {
212
+ local template="${1:-standard}"
213
+ local complexity="${2:-50}"
214
+
215
+ info "Estimating cost for template: $template, complexity: $complexity"
216
+ echo ""
217
+
218
+ # Typical token usage by stage (estimated)
219
+ local stage_tokens=(
220
+ "intake:5000"
221
+ "plan:50000"
222
+ "design:50000"
223
+ "build:100000"
224
+ "test:30000"
225
+ "review:20000"
226
+ "compound_quality:40000"
227
+ "pr:10000"
228
+ "merge:5000"
229
+ "deploy:5000"
230
+ "validate:5000"
231
+ "monitor:5000"
232
+ )
233
+
234
+ local total_cost="0"
235
+ local total_input_tokens="0"
236
+ local total_output_tokens="0"
237
+
238
+ echo -e "${BOLD}Stage${RESET} $(printf '%-15s' 'Model') $(printf '%-15s' 'Input Tokens') $(printf '%-15s' 'Output Tokens') $(printf '%-10s' 'Cost')"
239
+ echo "─────────────────────────────────────────────────────────────────────"
240
+
241
+ for stage_info in "${stage_tokens[@]}"; do
242
+ local stage="${stage_info%%:*}"
243
+ local tokens="${stage_info#*:}"
244
+
245
+ # Estimate input/output split (roughly 70% input, 30% output)
246
+ local input_tokens=$((tokens * 7 / 10))
247
+ local output_tokens=$((tokens * 3 / 10))
248
+
249
+ local model
250
+ model=$(route_model "$stage" "$complexity")
251
+
252
+ local input_cost="0" output_cost="0"
253
+ case "$model" in
254
+ haiku)
255
+ input_cost=$(awk "BEGIN {printf \"%.4f\", $input_tokens * $HAIKU_INPUT_COST / 1000000}")
256
+ output_cost=$(awk "BEGIN {printf \"%.4f\", $output_tokens * $HAIKU_OUTPUT_COST / 1000000}")
257
+ ;;
258
+ sonnet)
259
+ input_cost=$(awk "BEGIN {printf \"%.4f\", $input_tokens * $SONNET_INPUT_COST / 1000000}")
260
+ output_cost=$(awk "BEGIN {printf \"%.4f\", $output_tokens * $SONNET_OUTPUT_COST / 1000000}")
261
+ ;;
262
+ opus)
263
+ input_cost=$(awk "BEGIN {printf \"%.4f\", $input_tokens * $OPUS_INPUT_COST / 1000000}")
264
+ output_cost=$(awk "BEGIN {printf \"%.4f\", $output_tokens * $OPUS_OUTPUT_COST / 1000000}")
265
+ ;;
266
+ esac
267
+
268
+ local stage_cost
269
+ stage_cost=$(awk "BEGIN {printf \"%.4f\", $input_cost + $output_cost}")
270
+ total_cost=$(awk "BEGIN {printf \"%.4f\", $total_cost + $stage_cost}")
271
+ total_input_tokens=$((total_input_tokens + input_tokens))
272
+ total_output_tokens=$((total_output_tokens + output_tokens))
273
+
274
+ printf "%-15s %-15s %-15d %-15d \$%-10s\n" "$stage" "$model" "$input_tokens" "$output_tokens" "$stage_cost"
275
+ done
276
+
277
+ echo "─────────────────────────────────────────────────────────────────────"
278
+ echo -e "${BOLD}Total${RESET} ${BOLD}\$${total_cost}${RESET}"
279
+ echo ""
280
+ echo "Tokens: $total_input_tokens input + $total_output_tokens output = $((total_input_tokens + total_output_tokens)) total"
281
+ }
282
+
283
+ # ─── Record Model Usage ─────────────────────────────────────────────────────
284
+ record_usage() {
285
+ local stage="$1"
286
+ local model="$2"
287
+ local input_tokens="${3:-0}"
288
+ local output_tokens="${4:-0}"
289
+
290
+ mkdir -p "${HOME}/.shipwright"
291
+
292
+ local cost
293
+ cost=$(awk "BEGIN {}" ) # Calculate actual cost
294
+ case "$model" in
295
+ haiku)
296
+ cost=$(awk "BEGIN {printf \"%.4f\", ($input_tokens * $HAIKU_INPUT_COST + $output_tokens * $HAIKU_OUTPUT_COST) / 1000000}")
297
+ ;;
298
+ sonnet)
299
+ cost=$(awk "BEGIN {printf \"%.4f\", ($input_tokens * $SONNET_INPUT_COST + $output_tokens * $SONNET_OUTPUT_COST) / 1000000}")
300
+ ;;
301
+ opus)
302
+ cost=$(awk "BEGIN {printf \"%.4f\", ($input_tokens * $OPUS_INPUT_COST + $output_tokens * $OPUS_OUTPUT_COST) / 1000000}")
303
+ ;;
304
+ esac
305
+
306
+ local record="{\"ts\":\"$(now_iso)\",\"stage\":\"$stage\",\"model\":\"$model\",\"input_tokens\":$input_tokens,\"output_tokens\":$output_tokens,\"cost\":$cost}"
307
+ echo "$record" >> "$MODEL_USAGE_LOG"
308
+ }
309
+
310
+ # ─── A/B Test Configuration ────────────────────────────────────────────────
311
+ configure_ab_test() {
312
+ local percentage="${1:-10}"
313
+ local variant="${2:-cost-optimized}"
314
+
315
+ if ! [[ "$percentage" =~ ^[0-9]+$ ]] || [[ "$percentage" -lt 0 ]] || [[ "$percentage" -gt 100 ]]; then
316
+ error "Percentage must be 0-100, got: $percentage"
317
+ return 1
318
+ fi
319
+
320
+ ensure_config
321
+
322
+ if ! command -v jq &>/dev/null; then
323
+ error "jq is required for A/B test configuration"
324
+ return 1
325
+ fi
326
+
327
+ local tmp_config
328
+ tmp_config=$(mktemp)
329
+
330
+ jq ".a_b_test = {\"enabled\": true, \"percentage\": $percentage, \"variant\": \"$variant\"}" \
331
+ "$MODEL_ROUTING_CONFIG" > "$tmp_config"
332
+
333
+ mv "$tmp_config" "$MODEL_ROUTING_CONFIG"
334
+ success "Configured A/B test: $percentage% of pipelines will use $variant variant"
335
+ }
336
+
337
+ # ─── Log A/B Test Result ───────────────────────────────────────────────────
338
+ log_ab_result() {
339
+ local run_id="$1"
340
+ local variant="$2"
341
+ local success_status="$3"
342
+ local cost="$4"
343
+ local duration="${5:-0}"
344
+
345
+ mkdir -p "${HOME}/.shipwright"
346
+
347
+ local record="{\"ts\":\"$(now_iso)\",\"run_id\":\"$run_id\",\"variant\":\"$variant\",\"success\":$success_status,\"cost\":$cost,\"duration_seconds\":$duration}"
348
+ echo "$record" >> "$AB_RESULTS_FILE"
349
+ }
350
+
351
+ # ─── Show Usage Report ──────────────────────────────────────────────────────
352
+ show_report() {
353
+ info "Model Usage Report"
354
+ echo ""
355
+
356
+ if [[ ! -f "$MODEL_USAGE_LOG" ]]; then
357
+ warn "No usage data yet. Run pipelines to collect metrics."
358
+ return 0
359
+ fi
360
+
361
+ if ! command -v jq &>/dev/null; then
362
+ error "jq is required to view reports"
363
+ return 1
364
+ fi
365
+
366
+ # Summary stats
367
+ local total_runs
368
+ total_runs=$(wc -l < "$MODEL_USAGE_LOG" || echo "0")
369
+
370
+ local haiku_runs
371
+ haiku_runs=$(grep -c '"model":"haiku"' "$MODEL_USAGE_LOG" || true)
372
+
373
+ local sonnet_runs
374
+ sonnet_runs=$(grep -c '"model":"sonnet"' "$MODEL_USAGE_LOG" || true)
375
+
376
+ local opus_runs
377
+ opus_runs=$(grep -c '"model":"opus"' "$MODEL_USAGE_LOG" || true)
378
+
379
+ local total_cost
380
+ total_cost=$(jq -s 'map(.cost) | add' "$MODEL_USAGE_LOG" 2>/dev/null || echo "0")
381
+
382
+ echo -e "${BOLD}Summary${RESET}"
383
+ echo " Total runs: $total_runs"
384
+ echo " Haiku runs: $haiku_runs"
385
+ echo " Sonnet runs: $sonnet_runs"
386
+ echo " Opus runs: $opus_runs"
387
+ echo " Total cost: \$$total_cost"
388
+ echo ""
389
+
390
+ echo -e "${BOLD}Cost Per Model${RESET}"
391
+ jq -s '
392
+ group_by(.model) |
393
+ map({
394
+ model: .[0].model,
395
+ count: length,
396
+ total_cost: (map(.cost) | add),
397
+ avg_cost: (map(.cost) | add / length),
398
+ input_tokens: (map(.input_tokens) | add),
399
+ output_tokens: (map(.output_tokens) | add)
400
+ }) |
401
+ sort_by(.model)
402
+ ' "$MODEL_USAGE_LOG" 2>/dev/null | jq -r '.[] | " \(.model): \(.count) runs, $\(.total_cost | tostring), avg $\(.avg_cost | round)"' || true
403
+
404
+ echo ""
405
+ echo -e "${BOLD}Top Stages by Cost${RESET}"
406
+ jq -s '
407
+ group_by(.stage) |
408
+ map({stage: .[0].stage, cost: (map(.cost) | add), runs: length}) |
409
+ sort_by(.cost) | reverse | .[0:5]
410
+ ' "$MODEL_USAGE_LOG" 2>/dev/null | jq -r '.[] | " \(.stage): $\(.cost), \(.runs) runs"' || true
411
+ }
412
+
413
+ # ─── Show A/B Test Results ─────────────────────────────────────────────────
414
+ show_ab_results() {
415
+ info "A/B Test Results"
416
+ echo ""
417
+
418
+ if [[ ! -f "$AB_RESULTS_FILE" ]]; then
419
+ warn "No A/B test data yet."
420
+ return 0
421
+ fi
422
+
423
+ if ! command -v jq &>/dev/null; then
424
+ error "jq is required to view A/B test results"
425
+ return 1
426
+ fi
427
+
428
+ jq -s '
429
+ group_by(.variant) |
430
+ map({
431
+ variant: .[0].variant,
432
+ total_runs: length,
433
+ successful: (map(select(.success == true)) | length),
434
+ failed: (map(select(.success == false)) | length),
435
+ success_rate: ((map(select(.success == true)) | length) / length * 100),
436
+ avg_cost: (map(.cost) | add / length),
437
+ total_cost: (map(.cost) | add),
438
+ avg_duration: (map(.duration_seconds) | add / length)
439
+ })
440
+ ' "$AB_RESULTS_FILE" 2>/dev/null | jq -r '.[] | "\(.variant):\n Runs: \(.total_runs)\n Success: \(.successful)/\(.total_runs) (\(.success_rate | round)%)\n Avg Cost: $\(.avg_cost | round)\n Total Cost: $\(.total_cost | round)\n Avg Duration: \(.avg_duration | round)s"' || true
441
+ }
442
+
443
+ # ─── Help Text ──────────────────────────────────────────────────────────────
444
+ show_help() {
445
+ echo -e "${BOLD}shipwright model${RESET} — Intelligent Model Routing & Optimization"
446
+ echo ""
447
+ echo -e "${BOLD}USAGE${RESET}"
448
+ echo " ${CYAN}shipwright model${RESET} <subcommand> [options]"
449
+ echo ""
450
+ echo -e "${BOLD}SUBCOMMANDS${RESET}"
451
+ echo " ${CYAN}route${RESET} <stage> [complexity] Route task to optimal model (returns: haiku|sonnet|opus)"
452
+ echo " ${CYAN}escalate${RESET} <model> Get next tier model (haiku→sonnet→opus)"
453
+ echo " ${CYAN}config${RESET} [show|set <key> <val>] Show/set routing configuration"
454
+ echo " ${CYAN}estimate${RESET} [template] [complexity] Estimate pipeline cost"
455
+ echo " ${CYAN}ab-test${RESET} [enable|disable] [pct] [variant] Configure A/B testing"
456
+ echo " ${CYAN}report${RESET} Show model usage and cost report"
457
+ echo " ${CYAN}ab-results${RESET} Show A/B test results"
458
+ echo " ${CYAN}help${RESET} Show this help message"
459
+ echo ""
460
+ echo -e "${BOLD}EXAMPLES${RESET}"
461
+ echo " ${DIM}shipwright model route plan 65${RESET} # Route 'plan' stage with 65% complexity"
462
+ echo " ${DIM}shipwright model escalate haiku${RESET} # Upgrade from haiku"
463
+ echo " ${DIM}shipwright model config show${RESET} # View routing rules"
464
+ echo " ${DIM}shipwright model estimate standard 50${RESET} # Estimate standard pipeline cost"
465
+ echo " ${DIM}shipwright model ab-test enable 15 cost-optimized${RESET} # 15% A/B test"
466
+ echo " ${DIM}shipwright model report${RESET} # Show usage stats"
467
+ }
468
+
469
+ # ─── Main ───────────────────────────────────────────────────────────────────
470
+ main() {
471
+ local subcommand="${1:-help}"
472
+
473
+ case "$subcommand" in
474
+ route)
475
+ shift 2>/dev/null || true
476
+ route_model "$@"
477
+ ;;
478
+ escalate)
479
+ shift 2>/dev/null || true
480
+ escalate_model "$@"
481
+ ;;
482
+ config)
483
+ shift 2>/dev/null || true
484
+ case "${1:-show}" in
485
+ show)
486
+ show_config
487
+ ;;
488
+ set)
489
+ shift 2>/dev/null || true
490
+ set_config "$@"
491
+ ;;
492
+ *)
493
+ error "Unknown config subcommand: $1"
494
+ show_help
495
+ exit 1
496
+ ;;
497
+ esac
498
+ ;;
499
+ estimate)
500
+ shift 2>/dev/null || true
501
+ estimate_cost "$@"
502
+ ;;
503
+ ab-test)
504
+ shift 2>/dev/null || true
505
+ if [[ "${1:-}" == "enable" ]]; then
506
+ shift
507
+ configure_ab_test "$@"
508
+ elif [[ "${1:-}" == "disable" ]]; then
509
+ # Disable A/B testing
510
+ ensure_config
511
+ if command -v jq &>/dev/null; then
512
+ local tmp_config
513
+ tmp_config=$(mktemp)
514
+ jq ".a_b_test.enabled = false" "$MODEL_ROUTING_CONFIG" > "$tmp_config"
515
+ mv "$tmp_config" "$MODEL_ROUTING_CONFIG"
516
+ success "Disabled A/B testing"
517
+ else
518
+ error "jq is required"
519
+ return 1
520
+ fi
521
+ else
522
+ configure_ab_test "$@"
523
+ fi
524
+ ;;
525
+
526
+ report)
527
+ show_report
528
+ ;;
529
+ ab-results)
530
+ show_ab_results
531
+ ;;
532
+ help|--help|-h)
533
+ show_help
534
+ ;;
535
+ *)
536
+ error "Unknown subcommand: $subcommand"
537
+ show_help
538
+ exit 1
539
+ ;;
540
+ esac
541
+ }
542
+
543
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
544
+ main "$@"
545
+ fi