shipwright-cli 3.1.0 → 3.2.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 (118) hide show
  1. package/README.md +21 -7
  2. package/config/defaults.json +25 -2
  3. package/config/policy.json +1 -1
  4. package/dashboard/public/index.html +6 -0
  5. package/dashboard/public/styles.css +76 -0
  6. package/dashboard/server.ts +51 -0
  7. package/dashboard/src/core/api.ts +5 -0
  8. package/dashboard/src/types/api.ts +10 -0
  9. package/dashboard/src/views/metrics.ts +69 -1
  10. package/package.json +1 -1
  11. package/scripts/lib/daemon-adaptive.sh +4 -2
  12. package/scripts/lib/daemon-patrol.sh +2 -2
  13. package/scripts/lib/daemon-state.sh +7 -0
  14. package/scripts/lib/helpers.sh +3 -1
  15. package/scripts/lib/pipeline-detection.sh +1 -1
  16. package/scripts/lib/pipeline-intelligence.sh +5 -3
  17. package/scripts/lib/pipeline-quality-checks.sh +8 -4
  18. package/scripts/lib/pipeline-stages.sh +132 -2
  19. package/scripts/sw +1 -1
  20. package/scripts/sw-activity.sh +1 -7
  21. package/scripts/sw-adaptive.sh +7 -7
  22. package/scripts/sw-adversarial.sh +1 -1
  23. package/scripts/sw-architecture-enforcer.sh +1 -1
  24. package/scripts/sw-auth.sh +1 -1
  25. package/scripts/sw-autonomous.sh +1 -1
  26. package/scripts/sw-changelog.sh +1 -1
  27. package/scripts/sw-checkpoint.sh +1 -1
  28. package/scripts/sw-ci.sh +11 -6
  29. package/scripts/sw-cleanup.sh +1 -1
  30. package/scripts/sw-code-review.sh +36 -17
  31. package/scripts/sw-connect.sh +1 -1
  32. package/scripts/sw-context.sh +1 -1
  33. package/scripts/sw-cost.sh +60 -3
  34. package/scripts/sw-daemon.sh +5 -2
  35. package/scripts/sw-dashboard.sh +1 -1
  36. package/scripts/sw-db.sh +13 -5
  37. package/scripts/sw-decide.sh +1 -1
  38. package/scripts/sw-decompose.sh +1 -1
  39. package/scripts/sw-deps.sh +1 -1
  40. package/scripts/sw-developer-simulation.sh +1 -1
  41. package/scripts/sw-discovery.sh +54 -4
  42. package/scripts/sw-doc-fleet.sh +1 -1
  43. package/scripts/sw-docs-agent.sh +1 -1
  44. package/scripts/sw-docs.sh +1 -1
  45. package/scripts/sw-doctor.sh +1 -1
  46. package/scripts/sw-dora.sh +1 -1
  47. package/scripts/sw-durable.sh +9 -5
  48. package/scripts/sw-e2e-orchestrator.sh +1 -1
  49. package/scripts/sw-eventbus.sh +7 -4
  50. package/scripts/sw-evidence.sh +1 -1
  51. package/scripts/sw-feedback.sh +1 -1
  52. package/scripts/sw-fix.sh +1 -1
  53. package/scripts/sw-fleet-discover.sh +1 -1
  54. package/scripts/sw-fleet-viz.sh +6 -4
  55. package/scripts/sw-fleet.sh +1 -1
  56. package/scripts/sw-github-app.sh +3 -2
  57. package/scripts/sw-github-checks.sh +1 -1
  58. package/scripts/sw-github-deploy.sh +1 -1
  59. package/scripts/sw-github-graphql.sh +1 -1
  60. package/scripts/sw-guild.sh +1 -1
  61. package/scripts/sw-heartbeat.sh +1 -1
  62. package/scripts/sw-hygiene.sh +5 -3
  63. package/scripts/sw-incident.sh +9 -5
  64. package/scripts/sw-init.sh +1 -1
  65. package/scripts/sw-instrument.sh +1 -1
  66. package/scripts/sw-intelligence.sh +3 -2
  67. package/scripts/sw-jira.sh +1 -1
  68. package/scripts/sw-launchd.sh +1 -1
  69. package/scripts/sw-linear.sh +1 -1
  70. package/scripts/sw-logs.sh +1 -1
  71. package/scripts/sw-loop.sh +72 -16
  72. package/scripts/sw-memory.sh +2 -2
  73. package/scripts/sw-mission-control.sh +1 -1
  74. package/scripts/sw-model-router.sh +3 -2
  75. package/scripts/sw-otel.sh +4 -2
  76. package/scripts/sw-oversight.sh +1 -1
  77. package/scripts/sw-pipeline-composer.sh +3 -1
  78. package/scripts/sw-pipeline-vitals.sh +11 -6
  79. package/scripts/sw-pipeline.sh +20 -8
  80. package/scripts/sw-pm.sh +5 -4
  81. package/scripts/sw-pr-lifecycle.sh +1 -1
  82. package/scripts/sw-predictive.sh +11 -5
  83. package/scripts/sw-prep.sh +1 -1
  84. package/scripts/sw-ps.sh +1 -1
  85. package/scripts/sw-public-dashboard.sh +3 -2
  86. package/scripts/sw-quality.sh +13 -6
  87. package/scripts/sw-reaper.sh +1 -1
  88. package/scripts/sw-recruit.sh +1 -1
  89. package/scripts/sw-regression.sh +1 -1
  90. package/scripts/sw-release-manager.sh +1 -1
  91. package/scripts/sw-release.sh +1 -1
  92. package/scripts/sw-remote.sh +1 -1
  93. package/scripts/sw-replay.sh +1 -1
  94. package/scripts/sw-retro.sh +1 -1
  95. package/scripts/sw-review-rerun.sh +1 -1
  96. package/scripts/sw-scale.sh +5 -3
  97. package/scripts/sw-security-audit.sh +1 -1
  98. package/scripts/sw-self-optimize.sh +168 -4
  99. package/scripts/sw-session.sh +1 -1
  100. package/scripts/sw-setup.sh +1 -1
  101. package/scripts/sw-standup.sh +1 -1
  102. package/scripts/sw-status.sh +1 -1
  103. package/scripts/sw-strategic.sh +11 -6
  104. package/scripts/sw-stream.sh +7 -4
  105. package/scripts/sw-swarm.sh +3 -2
  106. package/scripts/sw-team-stages.sh +1 -1
  107. package/scripts/sw-templates.sh +3 -3
  108. package/scripts/sw-testgen.sh +11 -6
  109. package/scripts/sw-tmux-pipeline.sh +1 -1
  110. package/scripts/sw-tmux.sh +35 -1
  111. package/scripts/sw-trace.sh +1 -1
  112. package/scripts/sw-tracker.sh +1 -1
  113. package/scripts/sw-triage.sh +2 -2
  114. package/scripts/sw-upgrade.sh +1 -1
  115. package/scripts/sw-ux.sh +1 -1
  116. package/scripts/sw-webhook.sh +3 -2
  117. package/scripts/sw-widgets.sh +7 -4
  118. package/scripts/sw-worktree.sh +1 -1
@@ -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="3.1.0"
9
+ VERSION="3.2.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -112,7 +112,8 @@ gather_pipeline_state() {
112
112
  # Read pipeline artifacts if available
113
113
  if [[ -d "$pipeline_artifacts" ]]; then
114
114
  local stage_count
115
- stage_count=$(find "$pipeline_artifacts" -name "*.md" -o -name "*.json" | wc -l || echo "0")
115
+ stage_count=$(find "$pipeline_artifacts" -name "*.md" -o -name "*.json" | wc -l || true)
116
+ stage_count="${stage_count:-0}"
116
117
  pipeline_data=$(jq --arg count "$stage_count" '.artifact_count = $count' <<<"$pipeline_data")
117
118
  fi
118
119
 
@@ -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="3.1.0"
9
+ VERSION="3.2.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -16,6 +16,8 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
16
16
  # Canonical helpers (colors, output, events)
17
17
  # shellcheck source=lib/helpers.sh
18
18
  [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
19
+ # shellcheck source=lib/config.sh
20
+ [[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
19
21
  # Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
20
22
  [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
21
23
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
@@ -81,7 +83,8 @@ validate_quality() {
81
83
  local uncommitted_pass=true
82
84
  if [[ -d "$REPO_DIR/.git" ]]; then
83
85
  local dirty_count
84
- dirty_count=$(cd "$REPO_DIR" && git status --short 2>/dev/null | wc -l || echo "0")
86
+ dirty_count=$(cd "$REPO_DIR" && git status --short 2>/dev/null | wc -l || true)
87
+ dirty_count="${dirty_count:-0}"
85
88
  if [[ "$dirty_count" -gt 0 ]]; then
86
89
  uncommitted_pass=false
87
90
  all_pass=false
@@ -102,14 +105,16 @@ validate_quality() {
102
105
  json_output=$(echo "$json_output" | jq --arg tp "$todos_pass" '.checks.todos=$tp' 2>/dev/null || true)
103
106
  fi
104
107
 
105
- # Check 5: Hardcoded secrets patterns
108
+ # Check 5: Secrets patterns in diff
106
109
  local secrets_pass=true
107
110
  local secret_patterns="(password|secret|token|api[_-]?key|aws_access|private_key)"
111
+ local secret_threshold
112
+ secret_threshold=$(_config_get_int "quality.secret_threshold" 3 2>/dev/null || echo 3)
108
113
  if [[ -d "$REPO_DIR/.git" ]]; then
109
114
  local secret_count
110
115
  secret_count=$(cd "$REPO_DIR" && git diff --cached 2>/dev/null | grep -ciE "$secret_patterns" || true)
111
116
  secret_count="${secret_count:-0}"
112
- if [[ "$secret_count" -gt 3 ]]; then
117
+ if [[ "$secret_count" -gt "$secret_threshold" ]]; then
113
118
  secrets_pass=false
114
119
  all_pass=false
115
120
  fi
@@ -403,14 +408,16 @@ calculate_quality_score() {
403
408
  # Security audit (20%)
404
409
  local security_files=0
405
410
  if [[ -d "$REPO_DIR" ]]; then
406
- security_files=$(find "$REPO_DIR" -type f \( -name "*.js" -o -name "*.py" -o -name "*.go" \) 2>/dev/null | wc -l || echo "0")
411
+ security_files=$(find "$REPO_DIR" -type f \( -name "*.js" -o -name "*.py" -o -name "*.go" \) 2>/dev/null | wc -l || true)
412
+ security_files="${security_files:-0}"
407
413
  security_score=$((security_files > 0 ? 85 : 0))
408
414
  fi
409
415
 
410
416
  # Architecture audit (15%)
411
417
  local architecture_files=0
412
418
  if [[ -d "$REPO_DIR" ]]; then
413
- architecture_files=$(find "$REPO_DIR" -type f \( -name "*.js" -o -name "*.py" \) 2>/dev/null | wc -l || echo "0")
419
+ architecture_files=$(find "$REPO_DIR" -type f \( -name "*.js" -o -name "*.py" \) 2>/dev/null | wc -l || true)
420
+ architecture_files="${architecture_files:-0}"
414
421
  architecture_score=$((architecture_files > 0 ? 80 : 0))
415
422
  fi
416
423
 
@@ -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="3.1.0"
14
+ VERSION="3.2.0"
15
15
  set -euo pipefail
16
16
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
17
17
 
@@ -2026,7 +2026,7 @@ cmd_evaluate() {
2026
2026
  echo " Tasks Completed: $(echo "$profile" | jq -r '.tasks_completed // "0"')"
2027
2027
  echo ""
2028
2028
 
2029
- # Use population-aware thresholds instead of hardcoded ones
2029
+ # Use population-aware thresholds for performance evaluation
2030
2030
  local pop_stats
2031
2031
  pop_stats=$(_recruit_compute_population_stats)
2032
2032
  local mean_success
@@ -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="3.1.0"
9
+ VERSION="3.2.0"
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="3.1.0"
9
+ VERSION="3.2.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -7,7 +7,7 @@ set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
  trap 'rm -f "${tmp_file:-}" "${tmp_changelog:-}"' EXIT
9
9
 
10
- VERSION="3.1.0"
10
+ VERSION="3.2.0"
11
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
12
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
13
13
 
@@ -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="3.1.0"
9
+ VERSION="3.2.0"
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="3.1.0"
9
+ VERSION="3.2.0"
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="3.1.0"
9
+ VERSION="3.2.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -7,7 +7,7 @@
7
7
  set -euo pipefail
8
8
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
9
9
 
10
- VERSION="3.1.0"
10
+ VERSION="3.2.0"
11
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
12
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
13
13
 
@@ -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="3.1.0"
9
+ VERSION="3.2.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Dependency check ─────────────────────────────────────────────────────────
@@ -357,7 +357,8 @@ cmd_status() {
357
357
  fi
358
358
 
359
359
  if [[ -f "$SCALE_EVENTS_FILE" ]]; then
360
- event_count=$(wc -l < "$SCALE_EVENTS_FILE" || echo "0")
360
+ event_count=$(wc -l < "$SCALE_EVENTS_FILE" || true)
361
+ event_count="${event_count:-0}"
361
362
  fi
362
363
 
363
364
  local last_scale_time
@@ -445,7 +446,8 @@ cmd_recommend() {
445
446
  local base_branch="${BASE_BRANCH:-main}"
446
447
  if git rev-parse --verify "$base_branch" >/dev/null 2>&1; then
447
448
  module_count=$(git diff --name-only "${base_branch}..HEAD" 2>/dev/null \
448
- | sed 's|/[^/]*$||' | sort -u | wc -l | xargs || echo "0")
449
+ | sed 's|/[^/]*$||' | sort -u | wc -l || true)
450
+ module_count="${module_count:-0}"
449
451
  fi
450
452
  module_count="${module_count:-0}"
451
453
  fi
@@ -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="3.1.0"
9
+ VERSION="3.2.0"
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="3.1.0"
9
+ VERSION="3.2.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -1181,6 +1181,162 @@ optimize_evolve_memory() {
1181
1181
  success "Memory evolved: pruned=$pruned, strengthened=$strengthened, promoted=$promoted"
1182
1182
  }
1183
1183
 
1184
+ # ═════════════════════════════════════════════════════════════════════════════
1185
+ # CONTEXT EFFICIENCY CLOSED LOOP
1186
+ # ═════════════════════════════════════════════════════════════════════════════
1187
+
1188
+ # optimize_tune_context_efficiency
1189
+ # Read loop.context_efficiency events and recommend context budget adjustments.
1190
+ # If avg budget_utilization > 90%, recommend increasing context_budget_chars.
1191
+ # If avg trim_ratio > 30%, recommend reducing verbose context sections.
1192
+ optimize_tune_context_efficiency() {
1193
+ local events_file="${EVENTS_FILE:-${HOME}/.shipwright/events.jsonl}"
1194
+
1195
+ if [[ ! -f "$events_file" ]]; then
1196
+ info "No events file found — skipping context efficiency tuning"
1197
+ return 0
1198
+ fi
1199
+
1200
+ ensure_optimization_dir
1201
+
1202
+ info "Analyzing context efficiency..."
1203
+
1204
+ # Extract recent loop.context_efficiency events (last 200)
1205
+ local ctx_events
1206
+ ctx_events=$(grep '"loop.context_efficiency"' "$events_file" 2>/dev/null | tail -200 || true)
1207
+
1208
+ if [[ -z "$ctx_events" ]]; then
1209
+ info "No context efficiency events found — skipping"
1210
+ return 0
1211
+ fi
1212
+
1213
+ local event_count
1214
+ event_count=$(echo "$ctx_events" | wc -l | tr -d ' ')
1215
+ event_count="${event_count:-0}"
1216
+
1217
+ if [[ "$event_count" -lt 3 ]]; then
1218
+ info "Insufficient context efficiency events ($event_count) — need at least 3"
1219
+ return 0
1220
+ fi
1221
+
1222
+ # Calculate averages using jq (handles both string and numeric JSON values)
1223
+ local stats
1224
+ stats=$(echo "$ctx_events" | jq -rs '
1225
+ if length == 0 then {u:0,r:0,raw:0,tr:0,b:0,n:0}
1226
+ else {
1227
+ u: ([.[] | .budget_utilization | tonumber] | add / length),
1228
+ r: ([.[] | .trim_ratio | tonumber] | add / length),
1229
+ raw: ([.[] | .raw_prompt_chars | tonumber] | add / length),
1230
+ tr: ([.[] | .trimmed_prompt_chars | tonumber] | add / length),
1231
+ b: ([.[] | .budget_chars | tonumber] | last // 0),
1232
+ n: length
1233
+ } end
1234
+ | "\(.u) \(.r) \(.raw) \(.tr) \(.b) \(.n)"
1235
+ ' 2>/dev/null || echo "0 0 0 0 0 0")
1236
+
1237
+ local avg_utilization avg_trim_ratio avg_raw avg_trimmed current_budget sample_count
1238
+ avg_utilization=$(echo "$stats" | awk '{printf "%.1f", $1}')
1239
+ avg_trim_ratio=$(echo "$stats" | awk '{printf "%.1f", $2}')
1240
+ avg_raw=$(echo "$stats" | awk '{printf "%.0f", $3}')
1241
+ avg_trimmed=$(echo "$stats" | awk '{printf "%.0f", $4}')
1242
+ current_budget=$(echo "$stats" | awk '{printf "%.0f", $5}')
1243
+ sample_count=$(echo "$stats" | awk '{printf "%d", $6}')
1244
+
1245
+ info "Context efficiency: avg_utilization=${avg_utilization}%, avg_trim_ratio=${avg_trim_ratio}%, samples=${sample_count}"
1246
+
1247
+ local recommendations=""
1248
+ local rec_count=0
1249
+
1250
+ # Rule 1: If avg budget_utilization > 90%, recommend increasing context_budget_chars
1251
+ if awk -v u="$avg_utilization" 'BEGIN { exit !(u > 90) }' 2>/dev/null; then
1252
+ local new_budget
1253
+ new_budget=$(awk -v b="$current_budget" 'BEGIN { printf "%.0f", b * 1.2 }')
1254
+ # Cap at 300000
1255
+ if awk -v nb="$new_budget" 'BEGIN { exit !(nb > 300000) }' 2>/dev/null; then
1256
+ new_budget=300000
1257
+ fi
1258
+ recommendations="${recommendations}increase_budget:${new_budget} "
1259
+ rec_count=$((rec_count + 1))
1260
+ warn "Budget utilization high (${avg_utilization}%) — recommend increasing context_budget_chars from ${current_budget} to ${new_budget}"
1261
+
1262
+ emit_event "optimize.context_recommendation" \
1263
+ "action=increase_budget" \
1264
+ "current=${current_budget}" \
1265
+ "recommended=${new_budget}" \
1266
+ "avg_utilization=${avg_utilization}" \
1267
+ "samples=${sample_count}"
1268
+ fi
1269
+
1270
+ # Rule 2: If avg trim_ratio > 30%, recommend reducing verbose context sections
1271
+ if awk -v r="$avg_trim_ratio" 'BEGIN { exit !(r > 30) }' 2>/dev/null; then
1272
+ local avg_discarded
1273
+ avg_discarded=$(awk -v raw="$avg_raw" -v trimmed="$avg_trimmed" 'BEGIN { printf "%.0f", raw - trimmed }')
1274
+ recommendations="${recommendations}reduce_verbose:${avg_discarded} "
1275
+ rec_count=$((rec_count + 1))
1276
+ warn "Trim ratio high (${avg_trim_ratio}%) — avg ${avg_discarded} chars discarded per iteration"
1277
+ warn "Recommend reducing verbose context: lower context_trim_memory_chars, context_trim_git_entries, or context_trim_test_lines"
1278
+
1279
+ emit_event "optimize.context_recommendation" \
1280
+ "action=reduce_verbose" \
1281
+ "avg_trim_ratio=${avg_trim_ratio}" \
1282
+ "avg_discarded=${avg_discarded}" \
1283
+ "samples=${sample_count}"
1284
+ fi
1285
+
1286
+ # Rule 3: If budget utilization is very low (<50%), recommend decreasing budget to save tokens
1287
+ if awk -v u="$avg_utilization" 'BEGIN { exit !(u < 50) }' 2>/dev/null && [[ "$sample_count" -ge 10 ]]; then
1288
+ local smaller_budget
1289
+ smaller_budget=$(awk -v b="$current_budget" 'BEGIN { printf "%.0f", b * 0.85 }')
1290
+ # Floor at 100000
1291
+ if awk -v sb="$smaller_budget" 'BEGIN { exit !(sb < 100000) }' 2>/dev/null; then
1292
+ smaller_budget=100000
1293
+ fi
1294
+ recommendations="${recommendations}decrease_budget:${smaller_budget} "
1295
+ rec_count=$((rec_count + 1))
1296
+ info "Budget utilization low (${avg_utilization}%) — recommend decreasing context_budget_chars from ${current_budget} to ${smaller_budget} to save tokens"
1297
+
1298
+ emit_event "optimize.context_recommendation" \
1299
+ "action=decrease_budget" \
1300
+ "current=${current_budget}" \
1301
+ "recommended=${smaller_budget}" \
1302
+ "avg_utilization=${avg_utilization}" \
1303
+ "samples=${sample_count}"
1304
+ fi
1305
+
1306
+ # Write context efficiency summary to optimization dir
1307
+ local summary_file="${OPTIMIZATION_DIR}/context-efficiency.json"
1308
+ local tmp_summary
1309
+ tmp_summary=$(mktemp "${summary_file}.tmp.XXXXXX")
1310
+ trap "rm -f '$tmp_summary'" RETURN
1311
+ jq -n \
1312
+ --argjson avg_utilization "$avg_utilization" \
1313
+ --argjson avg_trim_ratio "$avg_trim_ratio" \
1314
+ --argjson avg_raw "$avg_raw" \
1315
+ --argjson avg_trimmed "$avg_trimmed" \
1316
+ --argjson current_budget "${current_budget:-0}" \
1317
+ --argjson sample_count "$sample_count" \
1318
+ --argjson rec_count "$rec_count" \
1319
+ --arg recommendations "${recommendations}" \
1320
+ --arg updated "$(now_iso)" \
1321
+ '{
1322
+ avg_budget_utilization: $avg_utilization,
1323
+ avg_trim_ratio: $avg_trim_ratio,
1324
+ avg_raw_chars: $avg_raw,
1325
+ avg_trimmed_chars: $avg_trimmed,
1326
+ current_budget_chars: $current_budget,
1327
+ sample_count: $sample_count,
1328
+ recommendation_count: $rec_count,
1329
+ recommendations: $recommendations,
1330
+ updated_at: $updated
1331
+ }' > "$tmp_summary" && mv "$tmp_summary" "$summary_file" || rm -f "$tmp_summary"
1332
+
1333
+ if [[ "$rec_count" -eq 0 ]]; then
1334
+ success "Context efficiency is healthy (utilization: ${avg_utilization}%, trim: ${avg_trim_ratio}%)"
1335
+ else
1336
+ success "Context efficiency analysis complete — $rec_count recommendation(s)"
1337
+ fi
1338
+ }
1339
+
1184
1340
  # ═════════════════════════════════════════════════════════════════════════════
1185
1341
  # QUALITY INDEX (LONGITUDINAL TRACKING)
1186
1342
  # ═════════════════════════════════════════════════════════════════════════════
@@ -1206,10 +1362,14 @@ optimize_track_quality_index() {
1206
1362
  [[ -z "$recent" ]] && return 0
1207
1363
 
1208
1364
  local total success_count
1209
- total=$(echo "$recent" | wc -l | tr -d ' ')
1365
+ total=$(echo "$recent" | wc -l || true)
1366
+ total="${total:-0}"
1367
+ total=$(echo "$total" | tr -d ' ')
1210
1368
  [[ "$total" -lt 3 ]] && return 0
1211
1369
 
1212
- success_count=$(echo "$recent" | jq -c 'select(.result == "success" or .result == "completed")' 2>/dev/null | wc -l | tr -d ' ')
1370
+ success_count=$(echo "$recent" | jq -c 'select(.result == "success" or .result == "completed")' 2>/dev/null | wc -l || true)
1371
+ success_count="${success_count:-0}"
1372
+ success_count=$(echo "$success_count" | tr -d ' ')
1213
1373
  success_count="${success_count:-0}"
1214
1374
 
1215
1375
  local avg_iterations avg_quality
@@ -1235,7 +1395,8 @@ optimize_track_quality_index() {
1235
1395
  # Detect trend
1236
1396
  if [[ -f "$quality_file" ]]; then
1237
1397
  local line_count
1238
- line_count=$(wc -l < "$quality_file" 2>/dev/null | tr -d ' ' || echo "0")
1398
+ line_count=$(wc -l < "$quality_file" 2>/dev/null | tr -d ' ' || true)
1399
+ line_count="${line_count:-0}"
1239
1400
  if [[ "$line_count" -ge 2 ]]; then
1240
1401
  local prev_index
1241
1402
  prev_index=$(tail -2 "$quality_file" | head -1 | jq -r '.quality_index // 0' 2>/dev/null || echo "0")
@@ -1295,6 +1456,7 @@ optimize_full_analysis() {
1295
1456
  optimize_route_models
1296
1457
  optimize_learn_risk_keywords
1297
1458
  optimize_evolve_memory
1459
+ optimize_tune_context_efficiency 2>/dev/null || true
1298
1460
  optimize_track_quality_index 2>/dev/null || true
1299
1461
  optimize_report >> "${OPTIMIZATION_DIR}/last-report.txt" 2>/dev/null || true
1300
1462
  optimize_adjust_audit_intensity 2>/dev/null || true
@@ -1489,6 +1651,7 @@ show_help() {
1489
1651
  echo " quality-index Show quality trend (last 10 snapshots)"
1490
1652
  echo " ingest-retro Ingest most recent retro into optimization loop"
1491
1653
  echo " evolve-memory Prune/strengthen/promote memory patterns"
1654
+ echo " context-efficiency Analyze context budget usage and recommend tuning"
1492
1655
  echo " help Show this help"
1493
1656
  echo ""
1494
1657
  echo -e "${CYAN}STORAGE${RESET}"
@@ -1513,6 +1676,7 @@ main() {
1513
1676
  report) optimize_report ;;
1514
1677
  quality-index) cmd_quality_index ;;
1515
1678
  evolve-memory) optimize_evolve_memory ;;
1679
+ context-efficiency) optimize_tune_context_efficiency ;;
1516
1680
  help|--help|-h) show_help ;;
1517
1681
  *) error "Unknown command: $cmd"; exit 1 ;;
1518
1682
  esac
@@ -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="3.1.0"
11
+ VERSION="3.2.0"
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="3.1.0"
13
+ VERSION="3.2.0"
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="3.1.0"
9
+ VERSION="3.2.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -4,7 +4,7 @@
4
4
  # ║ ║
5
5
  # ║ Shows running teams, agent windows, and task progress. ║
6
6
  # ╚═══════════════════════════════════════════════════════════════════════════╝
7
- VERSION="3.1.0"
7
+ VERSION="3.2.0"
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="3.1.0"
10
+ VERSION="3.2.0"
11
11
 
12
12
  # ─── Paths (set defaults if not provided by parent) ──────────────────────────
13
13
  SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
@@ -821,12 +821,14 @@ strategic_status() {
821
821
 
822
822
  # Total issues created
823
823
  local total_created
824
- total_created=$(grep '"strategic.issue_created"' "$events_file" 2>/dev/null | wc -l | tr -d ' ' || echo "0")
824
+ total_created=$(grep '"strategic.issue_created"' "$events_file" 2>/dev/null | wc -l | tr -d ' ' || true)
825
+ total_created="${total_created:-0}"
825
826
  echo -e " Total created: ${total_created} issues (all time)"
826
827
 
827
828
  # Total cycles
828
829
  local total_cycles
829
- total_cycles=$(grep '"strategic.cycle_complete"' "$events_file" 2>/dev/null | wc -l | tr -d ' ' || echo "0")
830
+ total_cycles=$(grep '"strategic.cycle_complete"' "$events_file" 2>/dev/null | wc -l | tr -d ' ' || true)
831
+ total_cycles="${total_cycles:-0}"
830
832
  echo -e " Total cycles: ${total_cycles}"
831
833
 
832
834
  echo ""
@@ -845,9 +847,12 @@ strategic_outcomes() {
845
847
  fi
846
848
 
847
849
  local shipped_count failed_count pending_count
848
- shipped_count=$(grep -c '"outcome":"shipped"' "$outcomes_file" 2>/dev/null || echo "0")
849
- failed_count=$(grep -cE '"outcome":"closed_unshipped"' "$outcomes_file" 2>/dev/null || echo "0")
850
- pending_count=$(grep -c '"outcome":"pending"' "$outcomes_file" 2>/dev/null || echo "0")
850
+ shipped_count=$(grep -c '"outcome":"shipped"' "$outcomes_file" 2>/dev/null || true)
851
+ shipped_count="${shipped_count:-0}"
852
+ failed_count=$(grep -cE '"outcome":"closed_unshipped"' "$outcomes_file" 2>/dev/null || true)
853
+ failed_count="${failed_count:-0}"
854
+ pending_count=$(grep -c '"outcome":"pending"' "$outcomes_file" 2>/dev/null || true)
855
+ pending_count="${pending_count:-0}"
851
856
 
852
857
  echo -e " ${GREEN}Shipped:${RESET} $shipped_count (closed with merged PR)"
853
858
  echo -e " ${RED}Closed unshipped:${RESET} $failed_count (closed without merge)"
@@ -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="3.1.0"
8
+ VERSION="3.2.0"
9
9
  set -euo pipefail
10
10
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
11
 
@@ -113,7 +113,8 @@ capture_pane_output() {
113
113
 
114
114
  # Trim to buffer size (keep latest N lines)
115
115
  local line_count
116
- line_count=$(wc -l < "$pane_file" 2>/dev/null || echo 0)
116
+ line_count=$(wc -l < "$pane_file" 2>/dev/null || true)
117
+ line_count="${line_count:-0}"
117
118
  if [[ "$line_count" -gt "$BUFFER_LINES" ]]; then
118
119
  local skip=$((line_count - BUFFER_LINES))
119
120
  tail -n "$BUFFER_LINES" "$pane_file" > "${pane_file}.tmp"
@@ -273,8 +274,10 @@ stream_list() {
273
274
 
274
275
  # Get file size and line count
275
276
  local file_size lines_count
276
- file_size=$(stat -f%z "$stream_file" 2>/dev/null || stat -c%s "$stream_file" 2>/dev/null || echo 0)
277
- lines_count=$(wc -l < "$stream_file" 2>/dev/null || echo 0)
277
+ file_size=$(stat -f%z "$stream_file" 2>/dev/null || stat -c%s "$stream_file" 2>/dev/null || true)
278
+ file_size="${file_size:-0}"
279
+ lines_count=$(wc -l < "$stream_file" 2>/dev/null || true)
280
+ lines_count="${lines_count:-0}"
278
281
 
279
282
  # Get latest timestamp
280
283
  local latest_ts
@@ -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="3.1.0"
9
+ VERSION="3.2.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -359,7 +359,8 @@ cmd_scale() {
359
359
  fi
360
360
 
361
361
  local current_agents
362
- current_agents=$(tmux list-sessions -F '#{session_name}' 2>/dev/null | grep -cE '^shipwright-sw-agent|^swarm-' || echo "0")
362
+ current_agents=$(tmux list-sessions -F '#{session_name}' 2>/dev/null | grep -cE '^shipwright-sw-agent|^swarm-' || true)
363
+ current_agents="${current_agents:-0}"
363
364
 
364
365
  local target_agents=1
365
366
  if [[ "$queue_depth" -gt 5 ]]; then
@@ -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="3.1.0"
9
+ VERSION="3.2.0"
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="3.1.0"
8
+ VERSION="3.2.0"
9
9
  set -euo pipefail
10
10
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
11
 
@@ -82,8 +82,8 @@ json_agent_count() {
82
82
  if command -v jq >/dev/null 2>&1; then
83
83
  jq -r '.agents // [] | length' "$file" 2>/dev/null
84
84
  else
85
- grep -c '"name"' "$file" 2>/dev/null || echo "0"
86
- fi
85
+ grep -c '"name"' "$file" 2>/dev/null || true
86
+ fi | tr -d ' ' | sed 's/^$/0/'
87
87
  }
88
88
 
89
89
  # Print agent details from a template