shipwright-cli 2.2.0 → 2.2.2

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 (120) hide show
  1. package/README.md +15 -16
  2. package/config/policy.schema.json +104 -29
  3. package/docs/AGI-PLATFORM-PLAN.md +11 -7
  4. package/docs/AGI-WHATS-NEXT.md +26 -20
  5. package/docs/README.md +2 -0
  6. package/package.json +1 -1
  7. package/scripts/check-version-consistency.sh +72 -0
  8. package/scripts/lib/daemon-adaptive.sh +610 -0
  9. package/scripts/lib/daemon-dispatch.sh +489 -0
  10. package/scripts/lib/daemon-failure.sh +387 -0
  11. package/scripts/lib/daemon-patrol.sh +1113 -0
  12. package/scripts/lib/daemon-poll.sh +1202 -0
  13. package/scripts/lib/daemon-state.sh +550 -0
  14. package/scripts/lib/daemon-triage.sh +490 -0
  15. package/scripts/lib/helpers.sh +81 -1
  16. package/scripts/lib/pipeline-detection.sh +278 -0
  17. package/scripts/lib/pipeline-github.sh +196 -0
  18. package/scripts/lib/pipeline-intelligence.sh +1706 -0
  19. package/scripts/lib/pipeline-quality-checks.sh +1054 -0
  20. package/scripts/lib/pipeline-quality.sh +11 -0
  21. package/scripts/lib/pipeline-stages.sh +2508 -0
  22. package/scripts/lib/pipeline-state.sh +529 -0
  23. package/scripts/sw +26 -4
  24. package/scripts/sw-activity.sh +1 -1
  25. package/scripts/sw-adaptive.sh +2 -2
  26. package/scripts/sw-adversarial.sh +1 -1
  27. package/scripts/sw-architecture-enforcer.sh +1 -1
  28. package/scripts/sw-auth.sh +1 -1
  29. package/scripts/sw-autonomous.sh +1 -1
  30. package/scripts/sw-changelog.sh +1 -1
  31. package/scripts/sw-checkpoint.sh +1 -1
  32. package/scripts/sw-ci.sh +1 -1
  33. package/scripts/sw-cleanup.sh +1 -1
  34. package/scripts/sw-code-review.sh +1 -1
  35. package/scripts/sw-connect.sh +1 -1
  36. package/scripts/sw-context.sh +1 -1
  37. package/scripts/sw-cost.sh +1 -1
  38. package/scripts/sw-daemon.sh +52 -4816
  39. package/scripts/sw-dashboard.sh +1 -1
  40. package/scripts/sw-db.sh +1 -1
  41. package/scripts/sw-decompose.sh +1 -1
  42. package/scripts/sw-deps.sh +1 -1
  43. package/scripts/sw-developer-simulation.sh +1 -1
  44. package/scripts/sw-discovery.sh +1 -1
  45. package/scripts/sw-doc-fleet.sh +1 -1
  46. package/scripts/sw-docs-agent.sh +1 -1
  47. package/scripts/sw-docs.sh +1 -1
  48. package/scripts/sw-doctor.sh +42 -1
  49. package/scripts/sw-dora.sh +1 -1
  50. package/scripts/sw-durable.sh +1 -1
  51. package/scripts/sw-e2e-orchestrator.sh +1 -1
  52. package/scripts/sw-eventbus.sh +1 -1
  53. package/scripts/sw-feedback.sh +1 -1
  54. package/scripts/sw-fix.sh +1 -1
  55. package/scripts/sw-fleet-discover.sh +1 -1
  56. package/scripts/sw-fleet-viz.sh +3 -3
  57. package/scripts/sw-fleet.sh +1 -1
  58. package/scripts/sw-github-app.sh +1 -1
  59. package/scripts/sw-github-checks.sh +1 -1
  60. package/scripts/sw-github-deploy.sh +1 -1
  61. package/scripts/sw-github-graphql.sh +1 -1
  62. package/scripts/sw-guild.sh +1 -1
  63. package/scripts/sw-heartbeat.sh +1 -1
  64. package/scripts/sw-hygiene.sh +1 -1
  65. package/scripts/sw-incident.sh +1 -1
  66. package/scripts/sw-init.sh +1 -1
  67. package/scripts/sw-instrument.sh +1 -1
  68. package/scripts/sw-intelligence.sh +1 -1
  69. package/scripts/sw-jira.sh +1 -1
  70. package/scripts/sw-launchd.sh +1 -1
  71. package/scripts/sw-linear.sh +1 -1
  72. package/scripts/sw-logs.sh +1 -1
  73. package/scripts/sw-loop.sh +1 -1
  74. package/scripts/sw-memory.sh +1 -1
  75. package/scripts/sw-mission-control.sh +1 -1
  76. package/scripts/sw-model-router.sh +1 -1
  77. package/scripts/sw-otel.sh +4 -4
  78. package/scripts/sw-oversight.sh +1 -1
  79. package/scripts/sw-pipeline-composer.sh +1 -1
  80. package/scripts/sw-pipeline-vitals.sh +1 -1
  81. package/scripts/sw-pipeline.sh +23 -56
  82. package/scripts/sw-pipeline.sh.mock +7 -0
  83. package/scripts/sw-pm.sh +1 -1
  84. package/scripts/sw-pr-lifecycle.sh +1 -1
  85. package/scripts/sw-predictive.sh +1 -1
  86. package/scripts/sw-prep.sh +1 -1
  87. package/scripts/sw-ps.sh +1 -1
  88. package/scripts/sw-public-dashboard.sh +1 -1
  89. package/scripts/sw-quality.sh +1 -1
  90. package/scripts/sw-reaper.sh +1 -1
  91. package/scripts/sw-recruit.sh +9 -1
  92. package/scripts/sw-regression.sh +1 -1
  93. package/scripts/sw-release-manager.sh +1 -1
  94. package/scripts/sw-release.sh +1 -1
  95. package/scripts/sw-remote.sh +1 -1
  96. package/scripts/sw-replay.sh +1 -1
  97. package/scripts/sw-retro.sh +1 -1
  98. package/scripts/sw-scale.sh +8 -5
  99. package/scripts/sw-security-audit.sh +1 -1
  100. package/scripts/sw-self-optimize.sh +158 -7
  101. package/scripts/sw-session.sh +1 -1
  102. package/scripts/sw-setup.sh +1 -1
  103. package/scripts/sw-standup.sh +3 -3
  104. package/scripts/sw-status.sh +1 -1
  105. package/scripts/sw-strategic.sh +1 -1
  106. package/scripts/sw-stream.sh +8 -2
  107. package/scripts/sw-swarm.sh +7 -10
  108. package/scripts/sw-team-stages.sh +1 -1
  109. package/scripts/sw-templates.sh +1 -1
  110. package/scripts/sw-testgen.sh +1 -1
  111. package/scripts/sw-tmux-pipeline.sh +1 -1
  112. package/scripts/sw-tmux.sh +1 -1
  113. package/scripts/sw-trace.sh +1 -1
  114. package/scripts/sw-tracker.sh +24 -6
  115. package/scripts/sw-triage.sh +1 -1
  116. package/scripts/sw-upgrade.sh +1 -1
  117. package/scripts/sw-ux.sh +1 -1
  118. package/scripts/sw-webhook.sh +1 -1
  119. package/scripts/sw-widgets.sh +1 -1
  120. package/scripts/sw-worktree.sh +1 -1
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ MOCK_LOG="${MOCK_PIPELINE_LOG:-/tmp/mock-pipeline.log}"
3
+ echo "pipeline $*" >> "$MOCK_LOG"
4
+ # Simulate quick pipeline run
5
+ sleep 0.1
6
+ echo "https://github.com/test/repo/pull/42"
7
+ exit 0
package/scripts/sw-pm.sh CHANGED
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Handle subcommands ───────────────────────────────────────────────────────
package/scripts/sw-ps.sh CHANGED
@@ -5,7 +5,7 @@
5
5
  # ║ Displays a table of agents running in claude-* tmux windows with ║
6
6
  # ║ PID, status, idle time, and pane references. ║
7
7
  # ╚═══════════════════════════════════════════════════════════════════════════╝
8
- VERSION="2.2.0"
8
+ VERSION="2.2.2"
9
9
  set -euo pipefail
10
10
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
11
 
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -11,7 +11,7 @@
11
11
  # ║ shipwright reaper --watch Continuous loop (default: 5s) ║
12
12
  # ║ shipwright reaper --dry-run Preview what would be reaped ║
13
13
  # ╚═══════════════════════════════════════════════════════════════════════════╝
14
- VERSION="2.2.0"
14
+ VERSION="2.2.2"
15
15
  set -euo pipefail
16
16
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
17
17
 
@@ -284,7 +284,15 @@ initialize_builtin_roles() {
284
284
  }
285
285
  EOF
286
286
  )
287
- echo "$roles_json" | jq '.' > "$ROLES_DB"
287
+ local _tmp_roles
288
+ _tmp_roles=$(mktemp)
289
+ if echo "$roles_json" | jq '.' > "$_tmp_roles" 2>/dev/null && [[ -s "$_tmp_roles" ]]; then
290
+ mv "$_tmp_roles" "$ROLES_DB"
291
+ else
292
+ rm -f "$_tmp_roles"
293
+ error "Failed to initialize roles DB"
294
+ return 1
295
+ fi
288
296
  success "Initialized 10 built-in agent roles"
289
297
  }
290
298
 
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
12
12
 
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Dependency check ─────────────────────────────────────────────────────────
@@ -108,7 +108,11 @@ update_scale_state() {
108
108
 
109
109
  if [[ -f "$SCALE_STATE_FILE" ]]; then
110
110
  # Update existing state
111
- jq --arg now "$(now_epoch)" '.last_scale_time = ($now | tonumber)' "$SCALE_STATE_FILE" > "$tmp_file"
111
+ if ! jq --arg now "$(now_epoch)" '.last_scale_time = ($now | tonumber)' "$SCALE_STATE_FILE" > "$tmp_file" 2>/dev/null || [[ ! -s "$tmp_file" ]]; then
112
+ rm -f "$tmp_file"
113
+ warn "Failed to update scale state"
114
+ return 1
115
+ fi
112
116
  else
113
117
  # Create new state
114
118
  cat > "$tmp_file" << JSON
@@ -243,9 +247,8 @@ cmd_rules() {
243
247
  .[$key] = ($value | tonumber)
244
248
  else
245
249
  .[$key] = $value
246
- end' "$SCALE_RULES_FILE" > "$tmp_file"
247
-
248
- mv "$tmp_file" "$SCALE_RULES_FILE"
250
+ end' "$SCALE_RULES_FILE" > "$tmp_file" && [[ -s "$tmp_file" ]] && \
251
+ mv "$tmp_file" "$SCALE_RULES_FILE" || { rm -f "$tmp_file"; error "Failed to update config"; return 1; }
249
252
  success "Updated: ${key} = ${value}"
250
253
  ;;
251
254
  reset)
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -485,7 +485,7 @@ optimize_learn_iterations() {
485
485
  med_stats=$(calc_stats "$tmp_med")
486
486
  high_stats=$(calc_stats "$tmp_high")
487
487
 
488
- # Build iteration model with predictions wrapper
488
+ # Build iteration model (flat format for readers: .low, .medium, .high)
489
489
  local tmp_model
490
490
  tmp_model=$(mktemp "${ITERATION_MODEL_FILE}.tmp.XXXXXX")
491
491
  jq -n \
@@ -494,11 +494,9 @@ optimize_learn_iterations() {
494
494
  --argjson high "$high_stats" \
495
495
  --arg updated "$(now_iso)" \
496
496
  '{
497
- predictions: {
498
- low: {max_iterations: (if $low.mean > 0 then (($low.mean + $low.stddev) | floor | if . < 5 then 5 else . end) else 10 end), confidence: (if $low.samples >= 10 then 0.8 elif $low.samples >= 5 then 0.6 else 0.4 end), mean: $low.mean, stddev: $low.stddev, samples: $low.samples},
499
- medium: {max_iterations: (if $medium.mean > 0 then (($medium.mean + $medium.stddev) | floor | if . < 10 then 10 else . end) else 20 end), confidence: (if $medium.samples >= 10 then 0.8 elif $medium.samples >= 5 then 0.6 else 0.4 end), mean: $medium.mean, stddev: $medium.stddev, samples: $medium.samples},
500
- high: {max_iterations: (if $high.mean > 0 then (($high.mean + $high.stddev) | floor | if . < 15 then 15 else . end) else 30 end), confidence: (if $high.samples >= 10 then 0.8 elif $high.samples >= 5 then 0.6 else 0.4 end), mean: $high.mean, stddev: $high.stddev, samples: $high.samples}
501
- },
497
+ low: {max_iterations: (if $low.mean > 0 then (($low.mean + $low.stddev) | floor | if . < 5 then 5 else . end) else 10 end), confidence: (if $low.samples >= 10 then 0.8 elif $low.samples >= 5 then 0.6 else 0.4 end), mean: $low.mean, stddev: $low.stddev, samples: $low.samples},
498
+ medium: {max_iterations: (if $medium.mean > 0 then (($medium.mean + $medium.stddev) | floor | if . < 10 then 10 else . end) else 20 end), confidence: (if $medium.samples >= 10 then 0.8 elif $medium.samples >= 5 then 0.6 else 0.4 end), mean: $medium.mean, stddev: $medium.stddev, samples: $medium.samples},
499
+ high: {max_iterations: (if $high.mean > 0 then (($high.mean + $high.stddev) | floor | if . < 15 then 15 else . end) else 30 end), confidence: (if $high.samples >= 10 then 0.8 elif $high.samples >= 5 then 0.6 else 0.4 end), mean: $high.mean, stddev: $high.stddev, samples: $high.samples},
502
500
  updated_at: $updated
503
501
  }' \
504
502
  > "$tmp_model" && mv "$tmp_model" "$ITERATION_MODEL_FILE" || rm -f "$tmp_model"
@@ -506,6 +504,80 @@ optimize_learn_iterations() {
506
504
  rm -f "$tmp_low" "$tmp_med" "$tmp_high" 2>/dev/null || true
507
505
 
508
506
  success "Iteration model updated"
507
+
508
+ # Apply prediction error bias correction from validation data
509
+ _optimize_apply_prediction_bias
510
+ }
511
+
512
+ # _optimize_apply_prediction_bias
513
+ # Reads prediction-validation.jsonl and applies bias correction to iteration model.
514
+ # If predictions consistently over/under-estimate, shift the model's means.
515
+ _optimize_apply_prediction_bias() {
516
+ local validation_file="${HOME}/.shipwright/optimization/prediction-validation.jsonl"
517
+ [[ ! -f "$validation_file" ]] && return 0
518
+
519
+ local model_file="$ITERATION_MODEL_FILE"
520
+ [[ ! -f "$model_file" ]] && return 0
521
+
522
+ # Compute mean delta (predicted - actual) from recent validations
523
+ local recent_count=50
524
+ local bias_data
525
+ bias_data=$(tail -n "$recent_count" "$validation_file" | jq -s '
526
+ if length == 0 then empty
527
+ else
528
+ group_by(
529
+ if .predicted_complexity <= 3 then "low"
530
+ elif .predicted_complexity <= 6 then "medium"
531
+ else "high" end
532
+ ) | map({
533
+ bucket: (.[0] | if .predicted_complexity <= 3 then "low" elif .predicted_complexity <= 6 then "medium" else "high" end),
534
+ mean_delta: ([.[].delta] | add / length),
535
+ count: length
536
+ })
537
+ end' 2>/dev/null || true)
538
+
539
+ [[ -z "$bias_data" || "$bias_data" == "null" ]] && return 0
540
+
541
+ # Apply bias correction: if mean_delta > 0, predictions are too high → increase model mean
542
+ # (model mean drives estimates, and positive delta = predicted > actual = model underestimates actual iterations needed)
543
+ local updated_model
544
+ updated_model=$(cat "$model_file")
545
+ local changed=false
546
+
547
+ for bucket in low medium high; do
548
+ local bucket_bias count
549
+ bucket_bias=$(echo "$bias_data" | jq -r --arg b "$bucket" '.[] | select(.bucket == $b) | .mean_delta // 0' 2>/dev/null || echo "0")
550
+ count=$(echo "$bias_data" | jq -r --arg b "$bucket" '.[] | select(.bucket == $b) | .count // 0' 2>/dev/null || echo "0")
551
+
552
+ # Only correct if enough samples and significant bias (|delta| > 1)
553
+ if [[ "${count:-0}" -ge 5 ]]; then
554
+ local abs_bias
555
+ abs_bias=$(awk -v b="$bucket_bias" 'BEGIN { v = b < 0 ? -b : b; printf "%.1f", v }')
556
+ if awk -v ab="$abs_bias" 'BEGIN { exit !(ab > 1.0) }' 2>/dev/null; then
557
+ # Correction = -delta * 0.3 (partial correction to avoid overshooting)
558
+ local correction
559
+ correction=$(awk -v d="$bucket_bias" 'BEGIN { printf "%.2f", -d * 0.3 }')
560
+ updated_model=$(echo "$updated_model" | jq --arg b "$bucket" --argjson c "$correction" \
561
+ '.[$b].mean = ((.[$b].mean // 0) + $c) | .[$b].bias_correction = $c' 2>/dev/null || echo "$updated_model")
562
+ changed=true
563
+ info "Prediction bias correction for $bucket: delta=${bucket_bias}, correction=${correction} (${count} samples)"
564
+ fi
565
+ fi
566
+ done
567
+
568
+ if [[ "$changed" == true ]]; then
569
+ local tmp_model
570
+ tmp_model=$(mktemp)
571
+ if echo "$updated_model" | jq '.' > "$tmp_model" 2>/dev/null && [[ -s "$tmp_model" ]]; then
572
+ mv "$tmp_model" "$model_file"
573
+ emit_event "optimize.prediction_bias_corrected"
574
+ else
575
+ rm -f "$tmp_model"
576
+ fi
577
+ fi
578
+
579
+ # Rotate validation file
580
+ type rotate_jsonl &>/dev/null 2>&1 && rotate_jsonl "$validation_file" 5000
509
581
  }
510
582
 
511
583
  # ═════════════════════════════════════════════════════════════════════════════
@@ -659,6 +731,84 @@ optimize_route_models() {
659
731
  success "Model routing updated"
660
732
  }
661
733
 
734
+ # ═════════════════════════════════════════════════════════════════════════════
735
+ # RISK KEYWORD LEARNING
736
+ # ═════════════════════════════════════════════════════════════════════════════
737
+
738
+ # optimize_learn_risk_keywords [outcomes_file]
739
+ # Learns keyword→risk-weight mapping from pipeline outcomes for predictive risk scoring.
740
+ # Failed pipelines with labels/keywords get positive weights; successful ones get negative.
741
+ optimize_learn_risk_keywords() {
742
+ local outcomes_file="${1:-$OUTCOMES_FILE}"
743
+
744
+ if [[ ! -f "$outcomes_file" ]]; then
745
+ return 0
746
+ fi
747
+
748
+ ensure_optimization_dir
749
+
750
+ info "Learning risk keywords from outcomes..."
751
+
752
+ local risk_file="${OPTIMIZATION_DIR}/risk-keywords.json"
753
+ local keywords='{}'
754
+ if [[ -f "$risk_file" ]]; then
755
+ keywords=$(jq '.' "$risk_file" 2>/dev/null || echo '{}')
756
+ fi
757
+
758
+ local decay=0.95
759
+ local learn_rate=5
760
+
761
+ # Read outcomes and extract keywords from labels
762
+ local updated=false
763
+ while IFS= read -r line; do
764
+ local result labels
765
+ result=$(echo "$line" | jq -r '.result // "unknown"' 2>/dev/null) || continue
766
+ labels=$(echo "$line" | jq -r '.labels // ""' 2>/dev/null) || continue
767
+ [[ -z "$labels" || "$labels" == "null" ]] && continue
768
+
769
+ # Split labels on comma/space and learn from each keyword
770
+ local IFS=', '
771
+ for kw in $labels; do
772
+ kw=$(echo "$kw" | tr '[:upper:]' '[:lower:]' | tr -cd '[:alnum:]-')
773
+ [[ -z "$kw" || ${#kw} -lt 3 ]] && continue
774
+
775
+ local current_weight
776
+ current_weight=$(echo "$keywords" | jq -r --arg k "$kw" '.[$k] // 0' 2>/dev/null || echo "0")
777
+
778
+ local delta=0
779
+ if [[ "$result" == "failed" || "$result" == "error" ]]; then
780
+ delta=$learn_rate
781
+ elif [[ "$result" == "success" || "$result" == "complete" ]]; then
782
+ delta=$((-learn_rate / 2))
783
+ fi
784
+
785
+ if [[ "$delta" -ne 0 ]]; then
786
+ local new_weight
787
+ new_weight=$(awk -v cw="$current_weight" -v d="$decay" -v dw="$delta" 'BEGIN { printf "%.0f", (cw * d) + dw }')
788
+ # Clamp to -50..50
789
+ new_weight=$(awk -v w="$new_weight" 'BEGIN { if(w>50) w=50; if(w<-50) w=-50; printf "%.0f", w }')
790
+ keywords=$(echo "$keywords" | jq --arg k "$kw" --argjson w "$new_weight" '.[$k] = $w' 2>/dev/null || echo "$keywords")
791
+ updated=true
792
+ fi
793
+ done
794
+ done < "$outcomes_file"
795
+
796
+ if [[ "$updated" == true ]]; then
797
+ # Prune zero-weight keywords
798
+ keywords=$(echo "$keywords" | jq 'to_entries | map(select(.value != 0)) | from_entries' 2>/dev/null || echo "$keywords")
799
+ local tmp_risk
800
+ tmp_risk=$(mktemp)
801
+ if echo "$keywords" | jq '.' > "$tmp_risk" 2>/dev/null && [[ -s "$tmp_risk" ]]; then
802
+ mv "$tmp_risk" "$risk_file"
803
+ success "Risk keywords updated ($(echo "$keywords" | jq 'length' 2>/dev/null || echo '?') keywords)"
804
+ else
805
+ rm -f "$tmp_risk"
806
+ fi
807
+ else
808
+ info "No label data in outcomes — risk keywords unchanged"
809
+ fi
810
+ }
811
+
662
812
  # ═════════════════════════════════════════════════════════════════════════════
663
813
  # MEMORY EVOLUTION
664
814
  # ═════════════════════════════════════════════════════════════════════════════
@@ -821,6 +971,7 @@ optimize_full_analysis() {
821
971
  optimize_tune_templates
822
972
  optimize_learn_iterations
823
973
  optimize_route_models
974
+ optimize_learn_risk_keywords
824
975
  optimize_evolve_memory
825
976
  optimize_report >> "${OPTIMIZATION_DIR}/last-report.txt" 2>/dev/null || true
826
977
  optimize_adjust_audit_intensity 2>/dev/null || true
@@ -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.2.0"
11
+ VERSION="2.2.2"
12
12
  set -euo pipefail
13
13
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
14
14
 
@@ -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.2.0"
13
+ VERSION="2.2.2"
14
14
 
15
15
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
16
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -302,14 +302,14 @@ cmd_velocity() {
302
302
  if [[ "$ts_epoch" -ge "$cutoff" ]]; then
303
303
  local event_type
304
304
  event_type=$(echo "$line" | jq -r '.type // ""' 2>/dev/null || true)
305
- if [[ "$event_type" == "pipeline_completed" ]]; then
305
+ if [[ "$event_type" == "pipeline.completed" || "$event_type" == "pipeline_completed" ]]; then
306
306
  completed_24h=$((completed_24h + 1))
307
307
  fi
308
308
  fi
309
309
 
310
310
  local event_type
311
311
  event_type=$(echo "$line" | jq -r '.type // ""' 2>/dev/null || true)
312
- if [[ "$event_type" == "pipeline_completed" ]]; then
312
+ if [[ "$event_type" == "pipeline.completed" || "$event_type" == "pipeline_completed" ]]; then
313
313
  total_completed=$((total_completed + 1))
314
314
  fi
315
315
  done < "$EVENTS_FILE"
@@ -4,7 +4,7 @@
4
4
  # ║ ║
5
5
  # ║ Shows running teams, agent windows, and task progress. ║
6
6
  # ╚═══════════════════════════════════════════════════════════════════════════╝
7
- VERSION="2.2.0"
7
+ VERSION="2.2.2"
8
8
  set -euo pipefail
9
9
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
 
@@ -7,7 +7,7 @@
7
7
  # When sourced, do NOT add set -euo pipefail — the parent handles that.
8
8
  # When run directly, main() sets up the error handling.
9
9
 
10
- VERSION="2.2.0"
10
+ VERSION="2.2.2"
11
11
 
12
12
  # ─── Paths (set defaults if not provided by parent) ──────────────────────────
13
13
  SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
@@ -5,7 +5,7 @@
5
5
  # ║ Streams tmux pane output in real-time to the dashboard or CLI. ║
6
6
  # ║ Captures output periodically, tags by agent/team, supports replay. ║
7
7
  # ╚═══════════════════════════════════════════════════════════════════════════╝
8
- VERSION="2.2.0"
8
+ VERSION="2.2.2"
9
9
  set -euo pipefail
10
10
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
11
 
@@ -374,7 +374,13 @@ stream_config() {
374
374
  ;;
375
375
  esac
376
376
 
377
- mv "$tmp_file" "$STREAM_CONFIG"
377
+ if [[ -s "$tmp_file" ]]; then
378
+ mv "$tmp_file" "$STREAM_CONFIG"
379
+ else
380
+ rm -f "$tmp_file"
381
+ error "Failed to update stream config"
382
+ return 1
383
+ fi
378
384
  success "Config updated: $key = $value"
379
385
  emit_event "stream.config_updated" "key=$key" "value=$value"
380
386
  }
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -190,9 +190,8 @@ cmd_spawn() {
190
190
  resource_usage: {cpu: 0, memory: 0},
191
191
  last_heartbeat: "'$(now_iso)'"
192
192
  }] | .active_count += 1 | .last_updated = "'$(now_iso)'"' \
193
- "$REGISTRY_FILE" > "$tmp_file"
194
-
195
- mv "$tmp_file" "$REGISTRY_FILE"
193
+ "$REGISTRY_FILE" > "$tmp_file" && [[ -s "$tmp_file" ]] && \
194
+ mv "$tmp_file" "$REGISTRY_FILE" || { rm -f "$tmp_file"; error "Failed to update registry"; return 1; }
196
195
  record_metric "$agent_id" "spawn" "1" "$agent_type"
197
196
 
198
197
  # Create real tmux session for the agent (so scale/loop can send commands)
@@ -258,9 +257,8 @@ cmd_retire() {
258
257
 
259
258
  jq --arg aid "$agent_id" \
260
259
  '.agents |= map(select(.id != $aid)) | .active_count = ([.agents[] | select(.status == "active")] | length) | .last_updated = "'$(now_iso)'"' \
261
- "$REGISTRY_FILE" > "$tmp_file"
262
-
263
- mv "$tmp_file" "$REGISTRY_FILE"
260
+ "$REGISTRY_FILE" > "$tmp_file" && [[ -s "$tmp_file" ]] && \
261
+ mv "$tmp_file" "$REGISTRY_FILE" || { rm -f "$tmp_file"; error "Failed to update registry"; return 1; }
264
262
  record_metric "$agent_id" "retire" "1" "graceful_shutdown"
265
263
 
266
264
  success "Retired agent: ${CYAN}${agent_id}${RESET}"
@@ -492,9 +490,8 @@ cmd_config() {
492
490
  .[$key] = ($value | fromjson)
493
491
  else
494
492
  .[$key] = $value
495
- end' "$CONFIG_FILE" > "$tmp_file"
496
-
497
- mv "$tmp_file" "$CONFIG_FILE"
493
+ end' "$CONFIG_FILE" > "$tmp_file" && [[ -s "$tmp_file" ]] && \
494
+ mv "$tmp_file" "$CONFIG_FILE" || { rm -f "$tmp_file"; error "Failed to update config"; return 1; }
498
495
  success "Updated: ${key} = ${value}"
499
496
  ;;
500
497
  reset)
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -5,7 +5,7 @@
5
5
  # ║ Templates define reusable agent team configurations (roles, layout, ║
6
6
  # ║ focus areas) that shipwright session --template can use to scaffold teams. ║
7
7
  # ╚═══════════════════════════════════════════════════════════════════════════╝
8
- VERSION="2.2.0"
8
+ VERSION="2.2.2"
9
9
  set -euo pipefail
10
10
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
11
 
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Handle subcommands ───────────────────────────────────────────────────────
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -11,7 +11,7 @@
11
11
  # ║ shipwright tmux fix — Auto-fix common issues ║
12
12
  # ║ shipwright tmux reload — Reload tmux config ║
13
13
  # ╚═══════════════════════════════════════════════════════════════════════════╝
14
- VERSION="2.2.0"
14
+ VERSION="2.2.2"
15
15
  set -euo pipefail
16
16
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
17
17
 
@@ -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.2.0"
9
+ VERSION="2.2.2"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12