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,615 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ sw-dora.sh — DORA Metrics Dashboard with Engineering Intelligence ║
4
+ # ║ ║
5
+ # ║ Computes Lead Time, Deploy Frequency, Change Failure Rate, MTTR, ║
6
+ # ║ DX metrics, AI intelligence metrics, trends, and comparative analysis ║
7
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
8
+ set -euo pipefail
9
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
+
11
+ VERSION="2.0.0"
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && 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' # #4ade80 — success
19
+ YELLOW='\033[38;2;250;204;21m' # #faca15 — warning
20
+ RED='\033[38;2;248;113;113m' # #f87171 — error
21
+ DIM='\033[2m'
22
+ BOLD='\033[1m'
23
+ RESET='\033[0m'
24
+
25
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
26
+ _COMPAT="$SCRIPT_DIR/lib/compat.sh"
27
+ [[ -f "$_COMPAT" ]] && source "$_COMPAT"
28
+
29
+ # ─── 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
+ emit_event() {
36
+ local event_type="$1"; shift
37
+ local events_file="${HOME}/.shipwright/events.jsonl"
38
+ mkdir -p "$(dirname "$events_file")"
39
+ local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
40
+ while [[ $# -gt 0 ]]; do
41
+ local key="${1%%=*}" val="${1#*=}"
42
+ payload="${payload},\"${key}\":\"${val}\""
43
+ shift
44
+ done
45
+ payload="${payload}}"
46
+ echo "$payload" >> "$events_file"
47
+ }
48
+
49
+ now_epoch() {
50
+ date +%s
51
+ }
52
+
53
+ # ─── DORA Metrics Calculation ────────────────────────────────────────────────
54
+
55
+ # Classify performance band per DORA standards
56
+ classify_band() {
57
+ local metric="$1"
58
+ local value="$2"
59
+
60
+ case "$metric" in
61
+ lead_time)
62
+ if (( $(echo "$value <= 1" | bc -l) )); then echo "Elite"
63
+ elif (( $(echo "$value <= 7" | bc -l) )); then echo "High"
64
+ elif (( $(echo "$value <= 30" | bc -l) )); then echo "Medium"
65
+ else echo "Low"; fi
66
+ ;;
67
+ deploy_frequency)
68
+ if (( $(echo "$value >= 7" | bc -l) )); then echo "Elite"
69
+ elif (( $(echo "$value >= 1" | bc -l) )); then echo "High"
70
+ elif (( $(echo "$value >= 0.3" | bc -l) )); then echo "Medium"
71
+ else echo "Low"; fi
72
+ ;;
73
+ cfr)
74
+ if (( $(echo "$value <= 15" | bc -l) )); then echo "Elite"
75
+ elif (( $(echo "$value <= 30" | bc -l) )); then echo "High"
76
+ elif (( $(echo "$value <= 45" | bc -l) )); then echo "Medium"
77
+ else echo "Low"; fi
78
+ ;;
79
+ mttr)
80
+ if (( $(echo "$value <= 1" | bc -l) )); then echo "Elite"
81
+ elif (( $(echo "$value <= 24" | bc -l) )); then echo "High"
82
+ elif (( $(echo "$value <= 168" | bc -l) )); then echo "Medium"
83
+ else echo "Low"; fi
84
+ ;;
85
+ *)
86
+ echo "Unknown"
87
+ ;;
88
+ esac
89
+ }
90
+
91
+ # Determine trend arrow
92
+ trend_arrow() {
93
+ local current="$1"
94
+ local previous="$2"
95
+ local metric="$3"
96
+
97
+ # Handle division by zero
98
+ if (( $(echo "$previous == 0" | bc -l) )); then
99
+ echo "→"
100
+ return
101
+ fi
102
+
103
+ local threshold=0.05 # 5% change threshold
104
+ local pct_change
105
+ pct_change=$(echo "scale=4; ($current - $previous) / $previous" | bc)
106
+
107
+ # For metrics where lower is better (lead_time, cfr, mttr)
108
+ case "$metric" in
109
+ lead_time|cfr|mttr)
110
+ if (( $(echo "$pct_change < -$threshold" | bc -l) )); then echo "↓"
111
+ elif (( $(echo "$pct_change > $threshold" | bc -l) )); then echo "↑"
112
+ else echo "→"; fi
113
+ ;;
114
+ *)
115
+ # For metrics where higher is better (deploy_frequency)
116
+ if (( $(echo "$pct_change > $threshold" | bc -l) )); then echo "↑"
117
+ elif (( $(echo "$pct_change < -$threshold" | bc -l) )); then echo "↓"
118
+ else echo "→"; fi
119
+ ;;
120
+ esac
121
+ }
122
+
123
+ # Calculate DORA metrics for time window
124
+ calculate_dora() {
125
+ local window_days="${1:-7}"
126
+ local offset_days="${2:-0}"
127
+
128
+ local events_file="${HOME}/.shipwright/events.jsonl"
129
+ if [[ ! -f "$events_file" ]]; then
130
+ echo '{"deploy_freq":0,"cycle_time":0,"cfr":0,"mttr":0,"total":0}'
131
+ return 0
132
+ fi
133
+
134
+ if ! command -v jq &>/dev/null; then
135
+ echo '{"deploy_freq":0,"cycle_time":0,"cfr":0,"mttr":0,"total":0}'
136
+ return 0
137
+ fi
138
+
139
+ local now_e
140
+ now_e=$(now_epoch)
141
+ local window_end=$((now_e - offset_days * 86400))
142
+ local window_start=$((window_end - window_days * 86400))
143
+
144
+ jq -s --argjson start "$window_start" --argjson end "$window_end" '
145
+ [.[] | select(.ts_epoch >= $start and .ts_epoch < $end)] as $events |
146
+ [$events[] | select(.type == "pipeline.completed")] as $completed |
147
+ ($completed | length) as $total |
148
+ [$completed[] | select(.result == "success")] as $successes |
149
+ [$completed[] | select(.result == "failure")] as $failures |
150
+ ($successes | length) as $success_count |
151
+ ($failures | length) as $failure_count |
152
+ (if $total > 0 then ($success_count * 7 / '"$window_days"') else 0 end) as $deploy_freq |
153
+ ([$successes[] | .duration_s] | sort |
154
+ if length > 0 then .[length/2 | floor] else 0 end) as $cycle_time |
155
+ (if $total > 0 then ($failure_count / $total * 100) else 0 end) as $cfr |
156
+ ($completed | sort_by(.ts_epoch // 0) |
157
+ [range(length) as $i |
158
+ if .[$i].result == "failure" then
159
+ [.[$i+1:][] | select(.result == "success")][0] as $next |
160
+ if $next and $next.ts_epoch and .[$i].ts_epoch then
161
+ ($next.ts_epoch - .[$i].ts_epoch)
162
+ else null end
163
+ else null end
164
+ ] | map(select(. != null)) |
165
+ if length > 0 then (add / length | floor) else 0 end
166
+ ) as $mttr |
167
+ {
168
+ deploy_freq: ($deploy_freq * 100 | floor / 100),
169
+ cycle_time: ($cycle_time / 3600),
170
+ cfr: ($cfr * 10 | floor / 10),
171
+ mttr: ($mttr / 3600),
172
+ total: $total
173
+ }
174
+ ' "$events_file" 2>/dev/null || echo '{"deploy_freq":0,"cycle_time":0,"cfr":0,"mttr":0,"total":0}'
175
+ }
176
+
177
+ # ─── Dashboard Display ────────────────────────────────────────────────────────
178
+
179
+ show_dora_dashboard() {
180
+ info "Shipwright DORA Metrics Dashboard"
181
+ echo ""
182
+
183
+ local current previous
184
+ current=$(calculate_dora 7 0)
185
+ previous=$(calculate_dora 7 7)
186
+
187
+ if ! command -v jq &>/dev/null; then
188
+ error "jq is required for dashboard display"
189
+ exit 1
190
+ fi
191
+
192
+ # Extract metrics
193
+ local curr_deploy_freq curr_cycle_time curr_cfr curr_mttr curr_total
194
+ local prev_deploy_freq prev_cycle_time prev_cfr prev_mttr
195
+
196
+ curr_deploy_freq=$(echo "$current" | jq -r '.deploy_freq // 0')
197
+ curr_cycle_time=$(echo "$current" | jq -r '.cycle_time // 0')
198
+ curr_cfr=$(echo "$current" | jq -r '.cfr // 0')
199
+ curr_mttr=$(echo "$current" | jq -r '.mttr // 0')
200
+ curr_total=$(echo "$current" | jq -r '.total // 0')
201
+
202
+ prev_deploy_freq=$(echo "$previous" | jq -r '.deploy_freq // 0')
203
+ prev_cycle_time=$(echo "$previous" | jq -r '.cycle_time // 0')
204
+ prev_cfr=$(echo "$previous" | jq -r '.cfr // 0')
205
+ prev_mttr=$(echo "$previous" | jq -r '.mttr // 0')
206
+
207
+ # Trends
208
+ local trend_df trend_ct trend_cfr trend_mttr
209
+ trend_df=$(trend_arrow "$curr_deploy_freq" "$prev_deploy_freq" "deploy_frequency")
210
+ trend_ct=$(trend_arrow "$curr_cycle_time" "$prev_cycle_time" "lead_time")
211
+ trend_cfr=$(trend_arrow "$curr_cfr" "$prev_cfr" "cfr")
212
+ trend_mttr=$(trend_arrow "$curr_mttr" "$prev_mttr" "mttr")
213
+
214
+ # Bands
215
+ local band_df band_ct band_cfr band_mttr
216
+ band_df=$(classify_band "deploy_frequency" "$curr_deploy_freq")
217
+ band_ct=$(classify_band "lead_time" "$curr_cycle_time")
218
+ band_cfr=$(classify_band "cfr" "$curr_cfr")
219
+ band_mttr=$(classify_band "mttr" "$curr_mttr")
220
+
221
+ # Color-code bands
222
+ local color_df color_ct color_cfr color_mttr
223
+ case "$band_df" in
224
+ Elite) color_df="$GREEN" ;;
225
+ High) color_df="$CYAN" ;;
226
+ Medium) color_df="$YELLOW" ;;
227
+ Low) color_df="$RED" ;;
228
+ esac
229
+ case "$band_ct" in
230
+ Elite) color_ct="$GREEN" ;;
231
+ High) color_ct="$CYAN" ;;
232
+ Medium) color_ct="$YELLOW" ;;
233
+ Low) color_ct="$RED" ;;
234
+ esac
235
+ case "$band_cfr" in
236
+ Elite) color_cfr="$GREEN" ;;
237
+ High) color_cfr="$CYAN" ;;
238
+ Medium) color_cfr="$YELLOW" ;;
239
+ Low) color_cfr="$RED" ;;
240
+ esac
241
+ case "$band_mttr" in
242
+ Elite) color_mttr="$GREEN" ;;
243
+ High) color_mttr="$CYAN" ;;
244
+ Medium) color_mttr="$YELLOW" ;;
245
+ Low) color_mttr="$RED" ;;
246
+ esac
247
+
248
+ # Display 4 core metrics
249
+ echo -e "${BOLD}CORE DORA METRICS${RESET} ${DIM}(Last 7 days vs previous 7 days)${RESET}"
250
+ echo ""
251
+
252
+ printf " ${BOLD}Deploy Frequency${RESET} %6.2f /week ${color_df}${BOLD}%s${RESET}%-8s ${CYAN}%s${RESET} (Band: ${color_df}${band_df}${RESET})\n" \
253
+ "$curr_deploy_freq" "$trend_df" "" "[$(echo "scale=1; ($curr_deploy_freq - $prev_deploy_freq)" | bc)%]"
254
+
255
+ printf " ${BOLD}Lead Time${RESET} %6.2f hours ${color_ct}${BOLD}%s${RESET}%-8s ${CYAN}%s${RESET} (Band: ${color_ct}${band_ct}${RESET})\n" \
256
+ "$curr_cycle_time" "$trend_ct" "" "[$(echo "scale=1; ($curr_cycle_time - $prev_cycle_time)" | bc)h]"
257
+
258
+ printf " ${BOLD}Change Failure Rate${RESET} %6.1f %% ${color_cfr}${BOLD}%s${RESET}%-8s ${CYAN}%s${RESET} (Band: ${color_cfr}${band_cfr}${RESET})\n" \
259
+ "$curr_cfr" "$trend_cfr" "" "[$(echo "scale=1; ($curr_cfr - $prev_cfr)" | bc)pp]"
260
+
261
+ local mttr_hours mttr_str
262
+ mttr_hours=$(echo "scale=1; $curr_mttr" | bc)
263
+ if (( $(echo "$curr_mttr >= 24" | bc -l) )); then
264
+ mttr_str=$(echo "scale=1; $curr_mttr / 24" | bc)
265
+ mttr_str="${mttr_str} days"
266
+ else
267
+ mttr_str="${mttr_hours} hours"
268
+ fi
269
+
270
+ printf " ${BOLD}MTTR${RESET} %6s ${color_mttr}${BOLD}%s${RESET}%-8s ${CYAN}%s${RESET} (Band: ${color_mttr}${band_mttr}${RESET})\n" \
271
+ "$mttr_str" "$trend_mttr" "" "[prev: $(echo "scale=1; $prev_mttr" | bc)h]"
272
+
273
+ echo ""
274
+ success "Computed from $(printf '%d' "$curr_total") pipeline runs in the last 7 days"
275
+ }
276
+
277
+ # ─── DX Metrics Display ────────────────────────────────────────────────────────
278
+
279
+ show_dx_metrics() {
280
+ info "Developer Experience (DX) Metrics"
281
+ echo ""
282
+
283
+ local events_file="${HOME}/.shipwright/events.jsonl"
284
+ if [[ ! -f "$events_file" ]]; then
285
+ warn "No events found. Run pipelines to generate metrics."
286
+ return 0
287
+ fi
288
+
289
+ if ! command -v jq &>/dev/null; then
290
+ error "jq is required"
291
+ exit 1
292
+ fi
293
+
294
+ # Calculate DX metrics
295
+ local dx_metrics
296
+ dx_metrics=$(jq -s '
297
+ [.[] | select(.type == "pipeline.completed")] as $completed |
298
+ [.[] | select(.type == "build.iteration")] as $iterations |
299
+ [$completed[] | select(.result == "success")] as $successes |
300
+ ($successes | length) as $success_count |
301
+ ($completed | length) as $total_runs |
302
+
303
+ # First-time pass rate
304
+ (if $total_runs > 0 then
305
+ ($completed | group_by(.issue_id) |
306
+ map(if length == 1 then 1 else 0 end) | add) / $total_runs * 100
307
+ else 0 end) as $ftp_rate |
308
+
309
+ # Avg iterations to pass per issue
310
+ ($iterations | map(.iteration_num) |
311
+ if length > 0 then (add / length) else 1 end) as $avg_iterations |
312
+
313
+ # Cost per issue
314
+ ([$completed[] | .cost_usd] | if length > 0 then add else 0 end) as $total_cost |
315
+ (if $total_runs > 0 then ($total_cost / $total_runs) else 0 end) as $cost_per_issue |
316
+
317
+ {
318
+ ftp_rate: ($ftp_rate * 10 | floor / 10),
319
+ avg_iterations: ($avg_iterations * 100 | floor / 100),
320
+ total_runs: $total_runs,
321
+ cost_per_issue: ($cost_per_issue * 100 | floor / 100)
322
+ }
323
+ ' "$events_file" 2>/dev/null || echo '{"ftp_rate":0,"avg_iterations":0,"total_runs":0,"cost_per_issue":0}')
324
+
325
+ local ftp_rate avg_iterations total_runs cost_per_issue
326
+ ftp_rate=$(echo "$dx_metrics" | jq -r '.ftp_rate // 0')
327
+ avg_iterations=$(echo "$dx_metrics" | jq -r '.avg_iterations // 0')
328
+ total_runs=$(echo "$dx_metrics" | jq -r '.total_runs // 0')
329
+ cost_per_issue=$(echo "$dx_metrics" | jq -r '.cost_per_issue // 0')
330
+
331
+ printf " ${BOLD}First-Time Pass Rate${RESET} %6.1f %%\n" "$ftp_rate"
332
+ printf " ${BOLD}Avg Iterations to Pass${RESET} %6.2f\n" "$avg_iterations"
333
+ printf " ${BOLD}Cost per Issue${RESET} ${GREEN}\$${RESET}%-6.2f\n" "$cost_per_issue"
334
+ printf " ${BOLD}Total Pipeline Runs${RESET} %6d\n" "$total_runs"
335
+ echo ""
336
+ }
337
+
338
+ # ─── AI Metrics Display ────────────────────────────────────────────────────────
339
+
340
+ show_ai_metrics() {
341
+ info "AI Performance Metrics"
342
+ echo ""
343
+
344
+ local events_file="${HOME}/.shipwright/events.jsonl"
345
+ if [[ ! -f "$events_file" ]]; then
346
+ warn "No events found. Run pipelines to generate metrics."
347
+ return 0
348
+ fi
349
+
350
+ if ! command -v jq &>/dev/null; then
351
+ error "jq is required"
352
+ exit 1
353
+ fi
354
+
355
+ # Calculate AI metrics
356
+ local ai_metrics
357
+ ai_metrics=$(jq -s '
358
+ [.[] | select(.type == "intelligence.cache_hit")] as $cache_hits |
359
+ [.[] | select(.type == "intelligence.cache_miss")] as $cache_misses |
360
+ [.[] | select(.type == "intelligence.prediction")] as $predictions |
361
+ [.[] | select(.result == "accurate")] as $accurate |
362
+
363
+ ($cache_hits | length) as $hits |
364
+ ($cache_misses | length) as $misses |
365
+ ($hits + $misses) as $total_cache |
366
+ (if $total_cache > 0 then ($hits / $total_cache * 100) else 0 end) as $cache_rate |
367
+
368
+ ($accurate | length) as $accurate_count |
369
+ ($predictions | length) as $total_predictions |
370
+ (if $total_predictions > 0 then ($accurate_count / $total_predictions * 100) else 0 end) as $pred_accuracy |
371
+
372
+ # Model routing efficiency (cost savings from routing optimization)
373
+ ([.[] | select(.type == "cost.savings")] | map(.amount_usd) | add) as $total_savings |
374
+ (if $total_savings then $total_savings else 0 end) as $savings |
375
+
376
+ {
377
+ cache_hit_rate: ($cache_rate * 10 | floor / 10),
378
+ cache_total: $total_cache,
379
+ prediction_accuracy: ($pred_accuracy * 10 | floor / 10),
380
+ total_predictions: $total_predictions,
381
+ model_routing_savings: ($savings * 100 | floor / 100)
382
+ }
383
+ ' "$events_file" 2>/dev/null || echo '{"cache_hit_rate":0,"cache_total":0,"prediction_accuracy":0,"total_predictions":0,"model_routing_savings":0}')
384
+
385
+ local cache_rate cache_total pred_accuracy total_pred savings
386
+ cache_rate=$(echo "$ai_metrics" | jq -r '.cache_hit_rate // 0')
387
+ cache_total=$(echo "$ai_metrics" | jq -r '.cache_total // 0')
388
+ pred_accuracy=$(echo "$ai_metrics" | jq -r '.prediction_accuracy // 0')
389
+ total_pred=$(echo "$ai_metrics" | jq -r '.total_predictions // 0')
390
+ savings=$(echo "$ai_metrics" | jq -r '.model_routing_savings // 0')
391
+
392
+ printf " ${BOLD}Cache Hit Rate${RESET} %6.1f %% (${DIM}%d total${RESET})\n" "$cache_rate" "$cache_total"
393
+ printf " ${BOLD}Prediction Accuracy${RESET} %6.1f %% (${DIM}%d predictions${RESET})\n" "$pred_accuracy" "$total_pred"
394
+ printf " ${BOLD}Model Routing Savings${RESET} ${GREEN}\$${RESET}%-6.2f\n" "$savings"
395
+ echo ""
396
+ }
397
+
398
+ # ─── Trends Display ────────────────────────────────────────────────────────────
399
+
400
+ show_trends() {
401
+ local period="${1:-7}"
402
+
403
+ info "Shipwright Metrics Trends (Last ${period} Days)"
404
+ echo ""
405
+
406
+ local events_file="${HOME}/.shipwright/events.jsonl"
407
+ if [[ ! -f "$events_file" ]]; then
408
+ warn "No events found. Run pipelines to generate metrics."
409
+ return 0
410
+ fi
411
+
412
+ if ! command -v jq &>/dev/null; then
413
+ error "jq is required"
414
+ exit 1
415
+ fi
416
+
417
+ # Show day-by-day trends
418
+ local day=0
419
+ printf " ${BOLD}Day${RESET} ${BOLD}Deployments${RESET} ${BOLD}Cycle Time${RESET} ${BOLD}CFR${RESET} ${BOLD}MTTR${RESET}\n"
420
+ printf " ${DIM}─────────────────────────────────────────────${RESET}\n"
421
+
422
+ while [[ $day -lt $period ]]; do
423
+ local metrics
424
+ metrics=$(calculate_dora 1 "$day")
425
+
426
+ local deploys ct cfr mttr
427
+ deploys=$(echo "$metrics" | jq -r '.total // 0')
428
+ ct=$(echo "$metrics" | jq -r '.cycle_time // 0')
429
+ cfr=$(echo "$metrics" | jq -r '.cfr // 0')
430
+ mttr=$(echo "$metrics" | jq -r '.mttr // 0')
431
+
432
+ local date_str
433
+ date_str=$(date -u -v-${day}d +"%a" 2>/dev/null || date -u -d "${day} days ago" +"%a" 2>/dev/null || echo "Day")
434
+
435
+ printf " %-3s %d %.1fh %.1f%% %.1fh\n" \
436
+ "$date_str" "$deploys" "$ct" "$cfr" "$mttr"
437
+
438
+ ((day++))
439
+ done
440
+
441
+ echo ""
442
+ }
443
+
444
+ # ─── Comparison Display ────────────────────────────────────────────────────────
445
+
446
+ show_comparison() {
447
+ local current_period="${1:-7}"
448
+ local previous_period="${2:-7}"
449
+
450
+ info "Period Comparison Analysis"
451
+ echo ""
452
+ printf " ${BOLD}Current (last %d days) vs Previous (%d days)${RESET}\n" "$current_period" "$previous_period"
453
+ echo ""
454
+
455
+ local curr prev
456
+ curr=$(calculate_dora "$current_period" 0)
457
+ prev=$(calculate_dora "$previous_period" "$current_period")
458
+
459
+ if ! command -v jq &>/dev/null; then
460
+ error "jq is required"
461
+ exit 1
462
+ fi
463
+
464
+ local curr_df curr_ct curr_cfr curr_mttr
465
+ local prev_df prev_ct prev_cfr prev_mttr
466
+
467
+ curr_df=$(echo "$curr" | jq -r '.deploy_freq // 0')
468
+ curr_ct=$(echo "$curr" | jq -r '.cycle_time // 0')
469
+ curr_cfr=$(echo "$curr" | jq -r '.cfr // 0')
470
+ curr_mttr=$(echo "$curr" | jq -r '.mttr // 0')
471
+
472
+ prev_df=$(echo "$prev" | jq -r '.deploy_freq // 0')
473
+ prev_ct=$(echo "$prev" | jq -r '.cycle_time // 0')
474
+ prev_cfr=$(echo "$prev" | jq -r '.cfr // 0')
475
+ prev_mttr=$(echo "$prev" | jq -r '.mttr // 0')
476
+
477
+ # Calculate percent changes
478
+ local pct_df pct_ct pct_cfr pct_mttr
479
+ pct_df=$(echo "scale=1; (($curr_df - $prev_df) / $prev_df * 100)" | bc 2>/dev/null || echo "0")
480
+ pct_ct=$(echo "scale=1; (($curr_ct - $prev_ct) / $prev_ct * 100)" | bc 2>/dev/null || echo "0")
481
+ pct_cfr=$(echo "scale=1; (($curr_cfr - $prev_cfr) / $prev_cfr * 100)" | bc 2>/dev/null || echo "0")
482
+ pct_mttr=$(echo "scale=1; (($curr_mttr - $prev_mttr) / $prev_mttr * 100)" | bc 2>/dev/null || echo "0")
483
+
484
+ printf " ${BOLD}Deploy Frequency${RESET} %6.2f → %6.2f /week ${CYAN}(%+.1f%%)${RESET}\n" \
485
+ "$prev_df" "$curr_df" "$pct_df"
486
+
487
+ printf " ${BOLD}Lead Time${RESET} %6.2f → %6.2f hours ${CYAN}(%+.1f%%)${RESET}\n" \
488
+ "$prev_ct" "$curr_ct" "$pct_ct"
489
+
490
+ printf " ${BOLD}Change Failure Rate${RESET} %6.1f%% → %6.1f%% ${CYAN}(%+.1f pp)${RESET}\n" \
491
+ "$prev_cfr" "$curr_cfr" "$pct_cfr"
492
+
493
+ printf " ${BOLD}MTTR${RESET} %6.1f → %6.1f hours ${CYAN}(%+.1f%%)${RESET}\n" \
494
+ "$prev_mttr" "$curr_mttr" "$pct_mttr"
495
+
496
+ echo ""
497
+ }
498
+
499
+ # ─── Export to JSON ────────────────────────────────────────────────────────────
500
+
501
+ export_metrics() {
502
+ local current previous
503
+ current=$(calculate_dora 7 0)
504
+ previous=$(calculate_dora 7 7)
505
+
506
+ if ! command -v jq &>/dev/null; then
507
+ error "jq is required for JSON export"
508
+ exit 1
509
+ fi
510
+
511
+ # Build comprehensive metrics object
512
+ jq -n \
513
+ --argjson current "$current" \
514
+ --argjson previous "$previous" \
515
+ --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
516
+ '{
517
+ timestamp: $timestamp,
518
+ current_period: ($current | {
519
+ deploy_freq: .deploy_freq,
520
+ cycle_time: .cycle_time,
521
+ cfr: .cfr,
522
+ mttr: .mttr,
523
+ total_runs: .total
524
+ }),
525
+ previous_period: ($previous | {
526
+ deploy_freq: .deploy_freq,
527
+ cycle_time: .cycle_time,
528
+ cfr: .cfr,
529
+ mttr: .mttr,
530
+ total_runs: .total
531
+ })
532
+ }'
533
+ }
534
+
535
+ # ─── Help Display ────────────────────────────────────────────────────────────
536
+
537
+ show_help() {
538
+ cat << EOF
539
+ ${BOLD}shipwright dora${RESET} — DORA Metrics Dashboard & Engineering Intelligence
540
+
541
+ ${BOLD}USAGE${RESET}
542
+ shipwright dora <subcommand> [options]
543
+
544
+ ${BOLD}SUBCOMMANDS${RESET}
545
+ show Display DORA dashboard (4 core metrics)
546
+ dx Developer Experience metrics (FTP, iterations, cost)
547
+ ai AI performance metrics (cache, predictions, routing savings)
548
+ trends [days] Trend analysis over time (default: 7 days)
549
+ compare [c] [p] Compare current vs previous period (default: 7 vs 7 days)
550
+ export Export all metrics as JSON
551
+ help Show this help message
552
+
553
+ ${BOLD}EXAMPLES${RESET}
554
+ ${DIM}shipwright dora show${RESET} # Display DORA dashboard
555
+ ${DIM}shipwright dora trends 30${RESET} # Show 30-day trends
556
+ ${DIM}shipwright dora compare 7 14${RESET} # Compare last 7 days vs previous 14
557
+ ${DIM}shipwright dora export | jq .${RESET} # Export metrics as JSON
558
+
559
+ ${BOLD}DORA BANDS${RESET} (per DORA standards)
560
+ ${GREEN}Elite${RESET} — Highest performance tier
561
+ ${CYAN}High${RESET} — Above average
562
+ ${YELLOW}Medium${RESET} — Average performance
563
+ ${RED}Low${RESET} — Below average
564
+
565
+ ${BOLD}METRICS REFERENCE${RESET}
566
+ Deploy Frequency — Deployments per week (higher is better)
567
+ Lead Time — Hours from commit to production (lower is better)
568
+ Change Failure Rate — % of deployments requiring hotfix (lower is better)
569
+ MTTR — Hours to restore after failure (lower is better)
570
+
571
+ EOF
572
+ }
573
+
574
+ # ─── Main Entry Point ────────────────────────────────────────────────────────
575
+
576
+ main() {
577
+ local cmd="${1:-show}"
578
+
579
+ case "$cmd" in
580
+ show)
581
+ show_dora_dashboard
582
+ ;;
583
+ dx)
584
+ show_dx_metrics
585
+ ;;
586
+ ai)
587
+ show_ai_metrics
588
+ ;;
589
+ trends)
590
+ local days="${2:-7}"
591
+ show_trends "$days"
592
+ ;;
593
+ compare)
594
+ local curr="${2:-7}"
595
+ local prev="${3:-7}"
596
+ show_comparison "$curr" "$prev"
597
+ ;;
598
+ export)
599
+ export_metrics
600
+ ;;
601
+ help|--help|-h)
602
+ show_help
603
+ ;;
604
+ *)
605
+ error "Unknown command: $cmd"
606
+ echo ""
607
+ show_help
608
+ exit 1
609
+ ;;
610
+ esac
611
+ }
612
+
613
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
614
+ main "$@"
615
+ fi