shipwright-cli 2.3.1 → 3.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 (162) hide show
  1. package/README.md +95 -28
  2. package/completions/_shipwright +1 -1
  3. package/completions/shipwright.bash +3 -8
  4. package/completions/shipwright.fish +1 -1
  5. package/config/defaults.json +111 -0
  6. package/config/event-schema.json +81 -0
  7. package/config/policy.json +155 -2
  8. package/config/policy.schema.json +162 -1
  9. package/dashboard/coverage/coverage-summary.json +14 -0
  10. package/dashboard/public/index.html +1 -1
  11. package/dashboard/server.ts +306 -17
  12. package/dashboard/src/components/charts/bar.test.ts +79 -0
  13. package/dashboard/src/components/charts/donut.test.ts +68 -0
  14. package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
  15. package/dashboard/src/components/charts/sparkline.test.ts +125 -0
  16. package/dashboard/src/core/api.test.ts +309 -0
  17. package/dashboard/src/core/helpers.test.ts +301 -0
  18. package/dashboard/src/core/router.test.ts +307 -0
  19. package/dashboard/src/core/router.ts +7 -0
  20. package/dashboard/src/core/sse.test.ts +144 -0
  21. package/dashboard/src/views/metrics.test.ts +186 -0
  22. package/dashboard/src/views/overview.test.ts +173 -0
  23. package/dashboard/src/views/pipelines.test.ts +183 -0
  24. package/dashboard/src/views/team.test.ts +253 -0
  25. package/dashboard/vitest.config.ts +14 -5
  26. package/docs/TIPS.md +1 -1
  27. package/docs/patterns/README.md +1 -1
  28. package/package.json +15 -5
  29. package/scripts/adapters/docker-deploy.sh +1 -1
  30. package/scripts/adapters/tmux-adapter.sh +11 -1
  31. package/scripts/adapters/wezterm-adapter.sh +1 -1
  32. package/scripts/check-version-consistency.sh +1 -1
  33. package/scripts/lib/architecture.sh +126 -0
  34. package/scripts/lib/bootstrap.sh +75 -0
  35. package/scripts/lib/compat.sh +89 -6
  36. package/scripts/lib/config.sh +91 -0
  37. package/scripts/lib/daemon-adaptive.sh +3 -3
  38. package/scripts/lib/daemon-dispatch.sh +39 -16
  39. package/scripts/lib/daemon-health.sh +1 -1
  40. package/scripts/lib/daemon-patrol.sh +24 -12
  41. package/scripts/lib/daemon-poll.sh +37 -25
  42. package/scripts/lib/daemon-state.sh +115 -23
  43. package/scripts/lib/daemon-triage.sh +30 -8
  44. package/scripts/lib/fleet-failover.sh +63 -0
  45. package/scripts/lib/helpers.sh +30 -6
  46. package/scripts/lib/pipeline-detection.sh +2 -2
  47. package/scripts/lib/pipeline-github.sh +9 -9
  48. package/scripts/lib/pipeline-intelligence.sh +85 -35
  49. package/scripts/lib/pipeline-quality-checks.sh +16 -16
  50. package/scripts/lib/pipeline-quality.sh +1 -1
  51. package/scripts/lib/pipeline-stages.sh +242 -28
  52. package/scripts/lib/pipeline-state.sh +40 -4
  53. package/scripts/lib/test-helpers.sh +247 -0
  54. package/scripts/postinstall.mjs +3 -11
  55. package/scripts/sw +10 -4
  56. package/scripts/sw-activity.sh +1 -11
  57. package/scripts/sw-adaptive.sh +109 -85
  58. package/scripts/sw-adversarial.sh +4 -14
  59. package/scripts/sw-architecture-enforcer.sh +1 -11
  60. package/scripts/sw-auth.sh +8 -17
  61. package/scripts/sw-autonomous.sh +111 -49
  62. package/scripts/sw-changelog.sh +1 -11
  63. package/scripts/sw-checkpoint.sh +144 -20
  64. package/scripts/sw-ci.sh +2 -12
  65. package/scripts/sw-cleanup.sh +13 -17
  66. package/scripts/sw-code-review.sh +16 -36
  67. package/scripts/sw-connect.sh +5 -12
  68. package/scripts/sw-context.sh +9 -26
  69. package/scripts/sw-cost.sh +6 -16
  70. package/scripts/sw-daemon.sh +75 -70
  71. package/scripts/sw-dashboard.sh +57 -17
  72. package/scripts/sw-db.sh +506 -15
  73. package/scripts/sw-decompose.sh +1 -11
  74. package/scripts/sw-deps.sh +15 -25
  75. package/scripts/sw-developer-simulation.sh +1 -11
  76. package/scripts/sw-discovery.sh +112 -30
  77. package/scripts/sw-doc-fleet.sh +7 -17
  78. package/scripts/sw-docs-agent.sh +6 -16
  79. package/scripts/sw-docs.sh +4 -12
  80. package/scripts/sw-doctor.sh +134 -43
  81. package/scripts/sw-dora.sh +11 -19
  82. package/scripts/sw-durable.sh +35 -52
  83. package/scripts/sw-e2e-orchestrator.sh +11 -27
  84. package/scripts/sw-eventbus.sh +115 -115
  85. package/scripts/sw-evidence.sh +748 -0
  86. package/scripts/sw-feedback.sh +3 -13
  87. package/scripts/sw-fix.sh +2 -20
  88. package/scripts/sw-fleet-discover.sh +1 -11
  89. package/scripts/sw-fleet-viz.sh +10 -18
  90. package/scripts/sw-fleet.sh +13 -17
  91. package/scripts/sw-github-app.sh +6 -16
  92. package/scripts/sw-github-checks.sh +1 -11
  93. package/scripts/sw-github-deploy.sh +1 -11
  94. package/scripts/sw-github-graphql.sh +2 -12
  95. package/scripts/sw-guild.sh +1 -11
  96. package/scripts/sw-heartbeat.sh +49 -12
  97. package/scripts/sw-hygiene.sh +45 -43
  98. package/scripts/sw-incident.sh +284 -67
  99. package/scripts/sw-init.sh +35 -37
  100. package/scripts/sw-instrument.sh +1 -11
  101. package/scripts/sw-intelligence.sh +362 -51
  102. package/scripts/sw-jira.sh +5 -14
  103. package/scripts/sw-launchd.sh +2 -12
  104. package/scripts/sw-linear.sh +8 -17
  105. package/scripts/sw-logs.sh +4 -12
  106. package/scripts/sw-loop.sh +641 -90
  107. package/scripts/sw-memory.sh +243 -17
  108. package/scripts/sw-mission-control.sh +2 -12
  109. package/scripts/sw-model-router.sh +73 -34
  110. package/scripts/sw-otel.sh +11 -21
  111. package/scripts/sw-oversight.sh +1 -11
  112. package/scripts/sw-patrol-meta.sh +5 -11
  113. package/scripts/sw-pipeline-composer.sh +7 -17
  114. package/scripts/sw-pipeline-vitals.sh +1 -11
  115. package/scripts/sw-pipeline.sh +478 -122
  116. package/scripts/sw-pm.sh +2 -12
  117. package/scripts/sw-pr-lifecycle.sh +203 -29
  118. package/scripts/sw-predictive.sh +16 -22
  119. package/scripts/sw-prep.sh +6 -16
  120. package/scripts/sw-ps.sh +1 -11
  121. package/scripts/sw-public-dashboard.sh +2 -12
  122. package/scripts/sw-quality.sh +77 -10
  123. package/scripts/sw-reaper.sh +1 -11
  124. package/scripts/sw-recruit.sh +15 -25
  125. package/scripts/sw-regression.sh +11 -21
  126. package/scripts/sw-release-manager.sh +19 -28
  127. package/scripts/sw-release.sh +8 -16
  128. package/scripts/sw-remote.sh +1 -11
  129. package/scripts/sw-replay.sh +48 -44
  130. package/scripts/sw-retro.sh +70 -92
  131. package/scripts/sw-review-rerun.sh +220 -0
  132. package/scripts/sw-scale.sh +109 -32
  133. package/scripts/sw-security-audit.sh +12 -22
  134. package/scripts/sw-self-optimize.sh +239 -23
  135. package/scripts/sw-session.sh +3 -13
  136. package/scripts/sw-setup.sh +8 -18
  137. package/scripts/sw-standup.sh +5 -15
  138. package/scripts/sw-status.sh +32 -23
  139. package/scripts/sw-strategic.sh +129 -13
  140. package/scripts/sw-stream.sh +1 -11
  141. package/scripts/sw-swarm.sh +76 -36
  142. package/scripts/sw-team-stages.sh +10 -20
  143. package/scripts/sw-templates.sh +4 -14
  144. package/scripts/sw-testgen.sh +3 -13
  145. package/scripts/sw-tmux-pipeline.sh +1 -19
  146. package/scripts/sw-tmux-role-color.sh +0 -10
  147. package/scripts/sw-tmux-status.sh +3 -11
  148. package/scripts/sw-tmux.sh +2 -20
  149. package/scripts/sw-trace.sh +1 -19
  150. package/scripts/sw-tracker-github.sh +0 -10
  151. package/scripts/sw-tracker-jira.sh +1 -11
  152. package/scripts/sw-tracker-linear.sh +1 -11
  153. package/scripts/sw-tracker.sh +7 -24
  154. package/scripts/sw-triage.sh +24 -34
  155. package/scripts/sw-upgrade.sh +5 -23
  156. package/scripts/sw-ux.sh +1 -19
  157. package/scripts/sw-webhook.sh +18 -32
  158. package/scripts/sw-widgets.sh +3 -21
  159. package/scripts/sw-worktree.sh +11 -27
  160. package/scripts/update-homebrew-sha.sh +67 -0
  161. package/templates/pipelines/tdd.json +72 -0
  162. package/scripts/sw-pipeline.sh.mock +0 -7
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="2.3.1"
9
+ VERSION="3.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -34,19 +34,12 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
34
34
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
35
35
  }
36
36
  fi
37
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
38
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
39
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
40
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
41
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
42
- RED="${RED:-\033[38;2;248;113;113m}"
43
- DIM="${DIM:-\033[2m}"
44
- BOLD="${BOLD:-\033[1m}"
45
- RESET="${RESET:-\033[0m}"
46
-
47
37
  # ─── Structured Event Log ────────────────────────────────────────────────────
48
38
  EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
49
39
 
40
+ # ─── DB for outcome-based learning ─────────────────────────────────────────────
41
+ [[ -f "$SCRIPT_DIR/sw-db.sh" ]] && source "$SCRIPT_DIR/sw-db.sh"
42
+
50
43
  # ─── Storage Paths ───────────────────────────────────────────────────────────
51
44
  OPTIMIZATION_DIR="${HOME}/.shipwright/optimization"
52
45
  OUTCOMES_FILE="${OPTIMIZATION_DIR}/outcomes.jsonl"
@@ -64,13 +57,13 @@ ensure_optimization_dir() {
64
57
  # ─── GitHub Metrics ──────────────────────────────────────────────────────
65
58
 
66
59
  _optimize_github_metrics() {
67
- type _gh_detect_repo &>/dev/null 2>&1 || { echo "{}"; return 0; }
60
+ type _gh_detect_repo >/dev/null 2>&1 || { echo "{}"; return 0; }
68
61
  _gh_detect_repo 2>/dev/null || { echo "{}"; return 0; }
69
62
 
70
63
  local owner="${GH_OWNER:-}" repo="${GH_REPO:-}"
71
64
  [[ -z "$owner" || -z "$repo" ]] && { echo "{}"; return 0; }
72
65
 
73
- if type gh_actions_runs &>/dev/null 2>&1; then
66
+ if type gh_actions_runs >/dev/null 2>&1; then
74
67
  local runs
75
68
  runs=$(gh_actions_runs "$owner" "$repo" "" 50 2>/dev/null || echo "[]")
76
69
  local success_rate avg_duration
@@ -171,7 +164,7 @@ optimize_analyze_outcome() {
171
164
  echo "$outcome_line" >> "$OUTCOMES_FILE"
172
165
 
173
166
  # Rotate outcomes file to prevent unbounded growth
174
- type rotate_jsonl &>/dev/null 2>&1 && rotate_jsonl "$OUTCOMES_FILE" 10000
167
+ type rotate_jsonl >/dev/null 2>&1 && rotate_jsonl "$OUTCOMES_FILE" 10000
175
168
 
176
169
  # Record GitHub CI metrics alongside outcome
177
170
  local gh_ci_metrics
@@ -223,7 +216,7 @@ optimize_ingest_retro() {
223
216
  latest_retro=$(ls -t "$retros_dir"/retro-*.json 2>/dev/null | head -1)
224
217
  [[ -z "$latest_retro" || ! -f "$latest_retro" ]] && return 0
225
218
 
226
- if ! command -v jq &>/dev/null; then
219
+ if ! command -v jq >/dev/null 2>&1; then
227
220
  warn "jq required for retro ingest — skipping"
228
221
  return 0
229
222
  fi
@@ -290,7 +283,7 @@ optimize_ingest_retro() {
290
283
  fi
291
284
  fi
292
285
 
293
- type rotate_jsonl &>/dev/null 2>&1 && rotate_jsonl "$OUTCOMES_FILE" 10000
286
+ type rotate_jsonl >/dev/null 2>&1 && rotate_jsonl "$OUTCOMES_FILE" 10000
294
287
 
295
288
  emit_event "optimize.retro_ingested" \
296
289
  "success_rate=${success_rate:-0}" \
@@ -680,7 +673,7 @@ _optimize_apply_prediction_bias() {
680
673
  fi
681
674
 
682
675
  # Rotate validation file
683
- type rotate_jsonl &>/dev/null 2>&1 && rotate_jsonl "$validation_file" 5000
676
+ type rotate_jsonl >/dev/null 2>&1 && rotate_jsonl "$validation_file" 5000
684
677
  }
685
678
 
686
679
  # ═════════════════════════════════════════════════════════════════════════════
@@ -739,10 +732,13 @@ optimize_route_models() {
739
732
  done
740
733
  done < "$outcomes_file"
741
734
 
742
- # Build routing recommendations
735
+ # Build routing recommendations; extract .routes from existing file when present
743
736
  local routing='{}'
744
- if [[ -f "$MODEL_ROUTING_FILE" ]]; then
745
- routing=$(cat "$MODEL_ROUTING_FILE")
737
+ if [[ -f "$MODEL_ROUTING_FILE" ]] && command -v jq >/dev/null 2>&1; then
738
+ local existing
739
+ existing=$(cat "$MODEL_ROUTING_FILE")
740
+ # Use .routes when present (self-optimize format), else flatten for merge
741
+ routing=$(echo "$existing" | jq -r 'if .routes then .routes else . end | if type == "object" then . else {} end' 2>/dev/null || echo '{}')
746
742
  fi
747
743
 
748
744
  if [[ -f "$tmp_stage_stats" && -s "$tmp_stage_stats" ]]; then
@@ -760,7 +756,7 @@ optimize_route_models() {
760
756
  sonnet_success="${sonnet_success:-0}"
761
757
 
762
758
  if [[ "$sonnet_total" -gt 0 ]]; then
763
- sonnet_rate=$(awk "BEGIN{printf \"%.1f\", ($sonnet_success/$sonnet_total)*100}")
759
+ sonnet_rate=$(awk "BEGIN{printf \"%.1f\", ($sonnet_success/$sonnet_total)*100}" | tr -d '\n')
764
760
  else
765
761
  sonnet_rate="0"
766
762
  fi
@@ -773,7 +769,7 @@ optimize_route_models() {
773
769
  opus_success="${opus_success:-0}"
774
770
 
775
771
  if [[ "$opus_total" -gt 0 ]]; then
776
- opus_rate=$(awk "BEGIN{printf \"%.1f\", ($opus_success/$opus_total)*100}")
772
+ opus_rate=$(awk "BEGIN{printf \"%.1f\", ($opus_success/$opus_total)*100}" | tr -d '\n')
777
773
  else
778
774
  opus_rate="0"
779
775
  fi
@@ -812,7 +808,7 @@ optimize_route_models() {
812
808
  routes: (. | to_entries | map({
813
809
  key: .key,
814
810
  value: {
815
- model: .value.recommended,
811
+ model: (.value.recommended // .value.model),
816
812
  confidence: (if .value.sonnet_samples + .value.opus_samples >= 10 then 0.9
817
813
  elif .value.sonnet_samples + .value.opus_samples >= 5 then 0.7
818
814
  else 0.5 end),
@@ -836,6 +832,129 @@ optimize_route_models() {
836
832
  success "Model routing updated"
837
833
  }
838
834
 
835
+ # ═════════════════════════════════════════════════════════════════════════════
836
+ # OUTCOME-BASED LEARNING: Thompson Sampling & UCB1
837
+ # ═════════════════════════════════════════════════════════════════════════════
838
+
839
+ # Thompson sampling: select template based on historical success rates
840
+ # Uses Beta distribution approximation: sample from Beta(successes+1, failures+1)
841
+ thompson_select_template() {
842
+ local complexity="${1:-medium}"
843
+
844
+ if ! db_available 2>/dev/null; then
845
+ _legacy_template_select "$complexity"
846
+ return
847
+ fi
848
+
849
+ local templates
850
+ templates=$(_db_query "SELECT template,
851
+ SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as wins,
852
+ SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) as losses
853
+ FROM pipeline_outcomes
854
+ WHERE complexity = '$complexity' AND template IS NOT NULL AND template != ''
855
+ GROUP BY template;" 2>/dev/null || echo "")
856
+
857
+ if [[ -z "$templates" ]]; then
858
+ echo "standard"
859
+ return
860
+ fi
861
+
862
+ local best_template="standard"
863
+ local best_score=0
864
+
865
+ while IFS='|' read -r template wins losses; do
866
+ [[ -z "$template" ]] && continue
867
+ template=$(echo "$template" | xargs)
868
+ local alpha=$((wins + 1))
869
+ local beta_param=$((losses + 1))
870
+ local total=$((alpha + beta_param))
871
+ local mean_x1000=$(( (alpha * 1000) / total ))
872
+ local noise=$(( (RANDOM % 200) - 100 ))
873
+ local variance_factor=$(( 1000 / (total + 1) ))
874
+ local score=$(( mean_x1000 + (noise * variance_factor / 100) ))
875
+
876
+ if [[ $score -gt $best_score ]]; then
877
+ best_score=$score
878
+ best_template="$template"
879
+ fi
880
+ done <<< "$templates"
881
+
882
+ echo "$best_template"
883
+ }
884
+
885
+ # Fallback when DB unavailable: map complexity to template
886
+ _legacy_template_select() {
887
+ local complexity="${1:-medium}"
888
+ case "$complexity" in
889
+ low|fast) echo "fast" ;;
890
+ high|full) echo "full" ;;
891
+ *) echo "standard" ;;
892
+ esac
893
+ }
894
+
895
+ # UCB1: select best model for a given stage
896
+ # UCB1 = mean_reward + sqrt(2 * ln(total_trials) / trials_for_this_arm)
897
+ ucb1_select_model() {
898
+ local stage="${1:-build}"
899
+
900
+ if ! db_available 2>/dev/null; then
901
+ echo "sonnet"
902
+ return
903
+ fi
904
+
905
+ local total_trials
906
+ total_trials=$(_db_query "SELECT COUNT(*) FROM model_outcomes WHERE stage = '$stage';" 2>/dev/null || echo "0")
907
+
908
+ if [[ "${total_trials:-0}" -lt 5 ]]; then
909
+ echo ""
910
+ return
911
+ fi
912
+
913
+ local models
914
+ models=$(_db_query "SELECT model,
915
+ AVG(success) as mean_reward,
916
+ COUNT(*) as trials,
917
+ AVG(cost_usd) as avg_cost
918
+ FROM model_outcomes
919
+ WHERE stage = '$stage'
920
+ GROUP BY model;" 2>/dev/null || echo "")
921
+
922
+ if [[ -z "$models" ]]; then
923
+ echo "sonnet"
924
+ return
925
+ fi
926
+
927
+ local best_model="sonnet"
928
+ local best_ucb=0
929
+
930
+ while IFS='|' read -r model mean_reward trials avg_cost; do
931
+ [[ -z "$model" ]] && continue
932
+ model=$(echo "$model" | xargs)
933
+ local mean_x1000 exploration ucb
934
+ mean_x1000=$(echo "$mean_reward" | awk '{printf "%d", $1 * 1000}')
935
+ exploration=$(awk "BEGIN { printf \"%d\", 1000 * sqrt(2 * log($total_trials) / $trials) }" 2>/dev/null || echo "0")
936
+ ucb=$((mean_x1000 + exploration))
937
+
938
+ if [[ $ucb -gt $best_ucb ]]; then
939
+ best_ucb=$ucb
940
+ best_model="$model"
941
+ fi
942
+ done <<< "$models"
943
+
944
+ echo "$best_model"
945
+ }
946
+
947
+ # Record model outcome for UCB1 learning
948
+ record_model_outcome() {
949
+ local model="$1" stage="$2" success="${3:-1}" duration="${4:-0}" cost="${5:-0}"
950
+ if db_available 2>/dev/null; then
951
+ model="${model//\'/\'\'}"
952
+ stage="${stage//\'/\'\'}"
953
+ _db_exec "INSERT INTO model_outcomes (model, stage, success, duration_secs, cost_usd, created_at)
954
+ VALUES ('$model', '$stage', $success, $duration, $cost, '$(now_iso)');" 2>/dev/null || true
955
+ fi
956
+ }
957
+
839
958
  # ═════════════════════════════════════════════════════════════════════════════
840
959
  # RISK KEYWORD LEARNING
841
960
  # ═════════════════════════════════════════════════════════════════════════════
@@ -1062,6 +1181,100 @@ optimize_evolve_memory() {
1062
1181
  success "Memory evolved: pruned=$pruned, strengthened=$strengthened, promoted=$promoted"
1063
1182
  }
1064
1183
 
1184
+ # ═════════════════════════════════════════════════════════════════════════════
1185
+ # QUALITY INDEX (LONGITUDINAL TRACKING)
1186
+ # ═════════════════════════════════════════════════════════════════════════════
1187
+
1188
+ # optimize_track_quality_index
1189
+ # Compute composite quality metrics from last N pipeline outcomes and append to quality-index.jsonl
1190
+ optimize_track_quality_index() {
1191
+ local quality_file="${HOME}/.shipwright/optimization/quality-index.jsonl"
1192
+ mkdir -p "$(dirname "$quality_file")"
1193
+
1194
+ local outcomes_file="${HOME}/.shipwright/optimization/outcomes.jsonl"
1195
+ [[ ! -f "$outcomes_file" ]] && return 0
1196
+
1197
+ if ! command -v jq >/dev/null 2>&1; then
1198
+ return 0
1199
+ fi
1200
+
1201
+ # Get pipeline outcomes only (exclude retro_summary, ci_metrics)
1202
+ local window=20
1203
+ local recent
1204
+ recent=$(jq -c 'select((.type // "") != "retro_summary" and (.type // "") != "ci_metrics")' "$outcomes_file" 2>/dev/null | tail -"$window" || true)
1205
+
1206
+ [[ -z "$recent" ]] && return 0
1207
+
1208
+ local total success_count
1209
+ total=$(echo "$recent" | wc -l | tr -d ' ')
1210
+ [[ "$total" -lt 3 ]] && return 0
1211
+
1212
+ success_count=$(echo "$recent" | jq -c 'select(.result == "success" or .result == "completed")' 2>/dev/null | wc -l | tr -d ' ')
1213
+ success_count="${success_count:-0}"
1214
+
1215
+ local avg_iterations avg_quality
1216
+ avg_iterations=$(echo "$recent" | jq -s '[.[] | .iterations // 0 | tonumber] | if length > 0 then add / length else 0 end' 2>/dev/null || echo "0")
1217
+ # quality_score: use from record if present, else 100 for success/completed, 0 for failed
1218
+ avg_quality=$(echo "$recent" | jq -s '[.[] | .quality_score // (if (.result == "success" or .result == "completed") then 100 else 0 end) | tonumber] | if length > 0 then add / length else 0 end' 2>/dev/null || echo "0")
1219
+
1220
+ local success_rate=0
1221
+ [[ "$total" -gt 0 ]] && success_rate=$((success_count * 100 / total))
1222
+ [[ "$success_rate" -gt 100 ]] && success_rate=100
1223
+
1224
+ # Efficiency (lower iterations = more efficient)
1225
+ local efficiency
1226
+ efficiency=$(awk "BEGIN{if($avg_iterations > 0) printf \"%.1f\", 100 / $avg_iterations; else print 0}" 2>/dev/null || echo "0")
1227
+
1228
+ # Composite quality index (0-100): success_rate 40%, efficiency 30%, quality 30%
1229
+ local quality_index
1230
+ quality_index=$(awk "BEGIN{printf \"%.0f\", ($success_rate * 0.4) + ($efficiency * 0.3) + ($avg_quality * 0.3)}" 2>/dev/null || echo "0")
1231
+
1232
+ local entry="{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"window\":$window,\"total\":$total,\"success_rate\":$success_rate,\"avg_iterations\":$avg_iterations,\"avg_quality\":$avg_quality,\"efficiency\":$efficiency,\"quality_index\":$quality_index}"
1233
+ echo "$entry" >> "$quality_file"
1234
+
1235
+ # Detect trend
1236
+ if [[ -f "$quality_file" ]]; then
1237
+ local line_count
1238
+ line_count=$(wc -l < "$quality_file" 2>/dev/null | tr -d ' ' || echo "0")
1239
+ if [[ "$line_count" -ge 2 ]]; then
1240
+ local prev_index
1241
+ prev_index=$(tail -2 "$quality_file" | head -1 | jq -r '.quality_index // 0' 2>/dev/null || echo "0")
1242
+ local delta
1243
+ delta=$(awk "BEGIN{printf \"%.0f\", $quality_index - $prev_index}" 2>/dev/null || echo "0")
1244
+
1245
+ if [[ "$delta" -gt 5 ]]; then
1246
+ info "Quality index: $quality_index (+${delta}) - IMPROVING"
1247
+ elif [[ "$delta" -lt -5 ]]; then
1248
+ warn "Quality index: $quality_index (${delta}) - DECLINING"
1249
+ else
1250
+ info "Quality index: $quality_index (stable)"
1251
+ fi
1252
+ fi
1253
+ fi
1254
+
1255
+ emit_event "quality.index_updated" "quality_index=$quality_index" "success_rate=$success_rate" 2>/dev/null || true
1256
+ }
1257
+
1258
+ # cmd_quality_index — Show quality trend (last 10 snapshots)
1259
+ cmd_quality_index() {
1260
+ local quality_file="${HOME}/.shipwright/optimization/quality-index.jsonl"
1261
+ if [[ ! -f "$quality_file" ]]; then
1262
+ echo "No quality data yet. Run some pipelines first."
1263
+ return
1264
+ fi
1265
+
1266
+ echo "Quality Index Trend (last 10 snapshots):"
1267
+ echo "========================================="
1268
+ tail -10 "$quality_file" | while IFS= read -r line; do
1269
+ local ts qi sr ai
1270
+ ts=$(echo "$line" | jq -r '.timestamp' 2>/dev/null)
1271
+ qi=$(echo "$line" | jq -r '.quality_index' 2>/dev/null)
1272
+ sr=$(echo "$line" | jq -r '.success_rate' 2>/dev/null)
1273
+ ai=$(echo "$line" | jq -r '.avg_iterations' 2>/dev/null)
1274
+ printf " %s QI: %s Success: %s%% Avg Iters: %s\n" "$ts" "$qi" "$sr" "$ai"
1275
+ done
1276
+ }
1277
+
1065
1278
  # ═════════════════════════════════════════════════════════════════════════════
1066
1279
  # FULL ANALYSIS (DAILY)
1067
1280
  # ═════════════════════════════════════════════════════════════════════════════
@@ -1082,6 +1295,7 @@ optimize_full_analysis() {
1082
1295
  optimize_route_models
1083
1296
  optimize_learn_risk_keywords
1084
1297
  optimize_evolve_memory
1298
+ optimize_track_quality_index 2>/dev/null || true
1085
1299
  optimize_report >> "${OPTIMIZATION_DIR}/last-report.txt" 2>/dev/null || true
1086
1300
  optimize_adjust_audit_intensity 2>/dev/null || true
1087
1301
 
@@ -1272,6 +1486,7 @@ show_help() {
1272
1486
  echo " analyze-outcome <state-file> Analyze a completed pipeline outcome"
1273
1487
  echo " tune Run full optimization analysis"
1274
1488
  echo " report Show optimization report (last 7 days)"
1489
+ echo " quality-index Show quality trend (last 10 snapshots)"
1275
1490
  echo " ingest-retro Ingest most recent retro into optimization loop"
1276
1491
  echo " evolve-memory Prune/strengthen/promote memory patterns"
1277
1492
  echo " help Show this help"
@@ -1296,6 +1511,7 @@ main() {
1296
1511
  tune) optimize_full_analysis ;;
1297
1512
  ingest-retro) optimize_ingest_retro ;;
1298
1513
  report) optimize_report ;;
1514
+ quality-index) cmd_quality_index ;;
1299
1515
  evolve-memory) optimize_evolve_memory ;;
1300
1516
  help|--help|-h) show_help ;;
1301
1517
  *) error "Unknown command: $cmd"; exit 1 ;;
@@ -8,7 +8,7 @@
8
8
  # ║ Supports --template to scaffold from a team template and --terminal ║
9
9
  # ║ to select a terminal adapter (tmux, iterm2, wezterm). ║
10
10
  # ╚═══════════════════════════════════════════════════════════════════════════╝
11
- VERSION="2.3.1"
11
+ VERSION="3.0.0"
12
12
  set -euo pipefail
13
13
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
14
14
 
@@ -38,16 +38,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
38
38
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
39
39
  }
40
40
  fi
41
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
42
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
43
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
44
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
45
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
46
- RED="${RED:-\033[38;2;248;113;113m}"
47
- DIM="${DIM:-\033[2m}"
48
- BOLD="${BOLD:-\033[1m}"
49
- RESET="${RESET:-\033[0m}"
50
-
51
41
  # ─── Parse Arguments ────────────────────────────────────────────────────────
52
42
 
53
43
  TEAM_NAME=""
@@ -207,7 +197,7 @@ if [[ -n "$TEMPLATE_NAME" ]]; then
207
197
  info "Loading template: ${PURPLE}${BOLD}${TEMPLATE_NAME}${RESET}"
208
198
 
209
199
  # Parse template — single jq call extracts all fields + agents in one pass
210
- if command -v jq &>/dev/null; then
200
+ if command -v jq >/dev/null 2>&1; then
211
201
  # Single jq call: outputs metadata lines then agent lines
212
202
  # Format: META<tab>field<tab>value for metadata, AGENT<tab>name|role|focus for agents
213
203
  while IFS=$'\t' read -r tag key value; do
@@ -508,7 +498,7 @@ LAUNCHER_STATIC
508
498
  tmux select-pane -t "$WINDOW_NAME" -P 'bg=#1a1a2e,fg=#e4e4e7' 2>/dev/null || true
509
499
  } &
510
500
 
511
- elif [[ -f "$ADAPTER_FILE" ]] && type -t spawn_agent &>/dev/null; then
501
+ elif [[ -f "$ADAPTER_FILE" ]] && type -t spawn_agent >/dev/null 2>&1; then
512
502
  # ─── Non-tmux adapter session (iterm2, wezterm, etc.) ──────────────────
513
503
  info "Creating team session: ${CYAN}${BOLD}${TEAM_NAME}${RESET} ${DIM}(${TERMINAL_ADAPTER})${RESET}"
514
504
 
@@ -10,7 +10,7 @@
10
10
  set -euo pipefail
11
11
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
12
12
 
13
- VERSION="2.3.1"
13
+ VERSION="3.0.0"
14
14
 
15
15
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
16
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
@@ -39,16 +39,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
39
39
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
40
40
  }
41
41
  fi
42
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
43
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
44
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
45
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
46
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
47
- RED="${RED:-\033[38;2;248;113;113m}"
48
- DIM="${DIM:-\033[2m}"
49
- BOLD="${BOLD:-\033[1m}"
50
- RESET="${RESET:-\033[0m}"
51
-
52
42
  PASS=0
53
43
  WARN=0
54
44
  FAIL=0
@@ -128,7 +118,7 @@ OPTIONAL_TOOLS=("bun")
128
118
  for tool in "${REQUIRED_TOOLS[@]}"; do
129
119
  case "$tool" in
130
120
  tmux)
131
- if command -v tmux &>/dev/null; then
121
+ if command -v tmux >/dev/null 2>&1; then
132
122
  TMUX_VERSION="$(tmux -V | grep -oE '[0-9]+\.[0-9a-z]+')"
133
123
  TMUX_MAJOR="$(echo "$TMUX_VERSION" | cut -d. -f1)"
134
124
  TMUX_MINOR="$(echo "$TMUX_VERSION" | cut -d. -f2 | tr -dc '0-9')"
@@ -153,7 +143,7 @@ for tool in "${REQUIRED_TOOLS[@]}"; do
153
143
  fi
154
144
  ;;
155
145
  git)
156
- if command -v git &>/dev/null; then
146
+ if command -v git >/dev/null 2>&1; then
157
147
  check_pass "git $(git --version | awk '{print $3}')"
158
148
  else
159
149
  check_fail "git not installed"
@@ -161,7 +151,7 @@ for tool in "${REQUIRED_TOOLS[@]}"; do
161
151
  fi
162
152
  ;;
163
153
  jq)
164
- if command -v jq &>/dev/null; then
154
+ if command -v jq >/dev/null 2>&1; then
165
155
  check_pass "jq $(jq --version 2>&1 | tr -d 'jq-')"
166
156
  else
167
157
  check_fail "jq not installed"
@@ -169,8 +159,8 @@ for tool in "${REQUIRED_TOOLS[@]}"; do
169
159
  fi
170
160
  ;;
171
161
  gh)
172
- if command -v gh &>/dev/null; then
173
- if gh auth status &>/dev/null 2>&1; then
162
+ if command -v gh >/dev/null 2>&1; then
163
+ if gh auth status >/dev/null 2>&1; then
174
164
  GH_USER="$(gh api user -q .login 2>/dev/null || echo "authenticated")"
175
165
  check_pass "GitHub CLI: ${GH_USER}"
176
166
  else
@@ -183,7 +173,7 @@ for tool in "${REQUIRED_TOOLS[@]}"; do
183
173
  fi
184
174
  ;;
185
175
  claude)
186
- if command -v claude &>/dev/null; then
176
+ if command -v claude >/dev/null 2>&1; then
187
177
  check_pass "Claude Code CLI"
188
178
  else
189
179
  check_fail "Claude Code CLI not found"
@@ -199,7 +189,7 @@ echo ""
199
189
  for tool in "${OPTIONAL_TOOLS[@]}"; do
200
190
  case "$tool" in
201
191
  bun)
202
- if command -v bun &>/dev/null; then
192
+ if command -v bun >/dev/null 2>&1; then
203
193
  check_pass "Bun (dashboard server)"
204
194
  else
205
195
  check_warn "Bun not installed (optional — for dashboard)"
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="2.3.1"
9
+ VERSION="3.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -34,16 +34,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
34
34
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
35
35
  }
36
36
  fi
37
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
38
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
39
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
40
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
41
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
42
- RED="${RED:-\033[38;2;248;113;113m}"
43
- DIM="${DIM:-\033[2m}"
44
- BOLD="${BOLD:-\033[1m}"
45
- RESET="${RESET:-\033[0m}"
46
-
47
37
  # ─── Constants ──────────────────────────────────────────────────────────────
48
38
  STANDUP_DIR="${HOME}/.shipwright/standups"
49
39
  EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
@@ -62,7 +52,7 @@ ensure_dirs() {
62
52
  # Convert ISO 8601 timestamp to epoch seconds (works on macOS and Linux)
63
53
  iso_to_epoch() {
64
54
  local iso="$1"
65
- if TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$iso" +%s &>/dev/null 2>&1; then
55
+ if TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$iso" +%s >/dev/null 2>&1; then
66
56
  TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$iso" +%s 2>/dev/null || echo 0
67
57
  else
68
58
  date -d "$iso" +%s 2>/dev/null || echo 0
@@ -533,9 +523,9 @@ cmd_notify() {
533
523
  ]
534
524
  }')
535
525
 
536
- if command -v curl &>/dev/null; then
537
- if curl -s -X POST -H 'Content-type: application/json' \
538
- --data "$payload" "$webhook_url" &>/dev/null; then
526
+ if command -v curl >/dev/null 2>&1; then
527
+ if curl -s --connect-timeout 10 --max-time 30 -X POST -H 'Content-type: application/json' \
528
+ --data "$payload" "$webhook_url" >/dev/null 2>&1; then
539
529
  success "Standup delivered to webhook"
540
530
  else
541
531
  error "Failed to deliver standup to webhook"