shipwright-cli 3.0.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 (143) hide show
  1. package/README.md +21 -7
  2. package/completions/_shipwright +247 -93
  3. package/completions/shipwright.bash +69 -15
  4. package/completions/shipwright.fish +309 -41
  5. package/config/decision-tiers.json +55 -0
  6. package/config/defaults.json +25 -2
  7. package/config/event-schema.json +142 -5
  8. package/config/policy.json +8 -0
  9. package/dashboard/public/index.html +6 -0
  10. package/dashboard/public/styles.css +76 -0
  11. package/dashboard/server.ts +51 -0
  12. package/dashboard/src/core/api.ts +5 -0
  13. package/dashboard/src/types/api.ts +10 -0
  14. package/dashboard/src/views/metrics.ts +69 -1
  15. package/package.json +3 -3
  16. package/scripts/lib/architecture.sh +2 -1
  17. package/scripts/lib/bootstrap.sh +0 -0
  18. package/scripts/lib/config.sh +0 -0
  19. package/scripts/lib/daemon-adaptive.sh +4 -2
  20. package/scripts/lib/daemon-dispatch.sh +24 -1
  21. package/scripts/lib/daemon-failure.sh +0 -0
  22. package/scripts/lib/daemon-health.sh +0 -0
  23. package/scripts/lib/daemon-patrol.sh +42 -7
  24. package/scripts/lib/daemon-poll.sh +17 -0
  25. package/scripts/lib/daemon-state.sh +17 -0
  26. package/scripts/lib/daemon-triage.sh +1 -1
  27. package/scripts/lib/decide-autonomy.sh +295 -0
  28. package/scripts/lib/decide-scoring.sh +228 -0
  29. package/scripts/lib/decide-signals.sh +462 -0
  30. package/scripts/lib/fleet-failover.sh +0 -0
  31. package/scripts/lib/helpers.sh +19 -18
  32. package/scripts/lib/pipeline-detection.sh +1 -1
  33. package/scripts/lib/pipeline-github.sh +0 -0
  34. package/scripts/lib/pipeline-intelligence.sh +23 -4
  35. package/scripts/lib/pipeline-quality-checks.sh +11 -6
  36. package/scripts/lib/pipeline-quality.sh +0 -0
  37. package/scripts/lib/pipeline-stages.sh +330 -33
  38. package/scripts/lib/pipeline-state.sh +14 -0
  39. package/scripts/lib/policy.sh +0 -0
  40. package/scripts/lib/test-helpers.sh +0 -0
  41. package/scripts/postinstall.mjs +75 -1
  42. package/scripts/signals/example-collector.sh +36 -0
  43. package/scripts/sw +8 -4
  44. package/scripts/sw-activity.sh +1 -7
  45. package/scripts/sw-adaptive.sh +7 -7
  46. package/scripts/sw-adversarial.sh +1 -1
  47. package/scripts/sw-architecture-enforcer.sh +1 -1
  48. package/scripts/sw-auth.sh +1 -1
  49. package/scripts/sw-autonomous.sh +1 -1
  50. package/scripts/sw-changelog.sh +1 -1
  51. package/scripts/sw-checkpoint.sh +1 -1
  52. package/scripts/sw-ci.sh +11 -6
  53. package/scripts/sw-cleanup.sh +1 -1
  54. package/scripts/sw-code-review.sh +36 -17
  55. package/scripts/sw-connect.sh +1 -1
  56. package/scripts/sw-context.sh +1 -1
  57. package/scripts/sw-cost.sh +71 -5
  58. package/scripts/sw-daemon.sh +6 -3
  59. package/scripts/sw-dashboard.sh +1 -1
  60. package/scripts/sw-db.sh +53 -38
  61. package/scripts/sw-decide.sh +685 -0
  62. package/scripts/sw-decompose.sh +1 -1
  63. package/scripts/sw-deps.sh +1 -1
  64. package/scripts/sw-developer-simulation.sh +1 -1
  65. package/scripts/sw-discovery.sh +80 -4
  66. package/scripts/sw-doc-fleet.sh +1 -1
  67. package/scripts/sw-docs-agent.sh +1 -1
  68. package/scripts/sw-docs.sh +1 -1
  69. package/scripts/sw-doctor.sh +1 -1
  70. package/scripts/sw-dora.sh +1 -1
  71. package/scripts/sw-durable.sh +9 -5
  72. package/scripts/sw-e2e-orchestrator.sh +1 -1
  73. package/scripts/sw-eventbus.sh +7 -4
  74. package/scripts/sw-evidence.sh +1 -1
  75. package/scripts/sw-feedback.sh +1 -1
  76. package/scripts/sw-fix.sh +1 -1
  77. package/scripts/sw-fleet-discover.sh +1 -1
  78. package/scripts/sw-fleet-viz.sh +6 -4
  79. package/scripts/sw-fleet.sh +1 -1
  80. package/scripts/sw-github-app.sh +3 -2
  81. package/scripts/sw-github-checks.sh +1 -1
  82. package/scripts/sw-github-deploy.sh +1 -1
  83. package/scripts/sw-github-graphql.sh +1 -1
  84. package/scripts/sw-guild.sh +1 -1
  85. package/scripts/sw-heartbeat.sh +1 -1
  86. package/scripts/sw-hygiene.sh +5 -3
  87. package/scripts/sw-incident.sh +9 -5
  88. package/scripts/sw-init.sh +1 -1
  89. package/scripts/sw-instrument.sh +1 -1
  90. package/scripts/sw-intelligence.sh +11 -6
  91. package/scripts/sw-jira.sh +1 -1
  92. package/scripts/sw-launchd.sh +1 -1
  93. package/scripts/sw-linear.sh +1 -1
  94. package/scripts/sw-logs.sh +1 -1
  95. package/scripts/sw-loop.sh +338 -32
  96. package/scripts/sw-memory.sh +23 -6
  97. package/scripts/sw-mission-control.sh +1 -1
  98. package/scripts/sw-model-router.sh +3 -2
  99. package/scripts/sw-otel.sh +8 -4
  100. package/scripts/sw-oversight.sh +1 -1
  101. package/scripts/sw-pipeline-composer.sh +3 -1
  102. package/scripts/sw-pipeline-vitals.sh +11 -6
  103. package/scripts/sw-pipeline.sh +92 -8
  104. package/scripts/sw-pm.sh +5 -4
  105. package/scripts/sw-pr-lifecycle.sh +7 -4
  106. package/scripts/sw-predictive.sh +11 -5
  107. package/scripts/sw-prep.sh +1 -1
  108. package/scripts/sw-ps.sh +1 -1
  109. package/scripts/sw-public-dashboard.sh +3 -2
  110. package/scripts/sw-quality.sh +21 -10
  111. package/scripts/sw-reaper.sh +1 -1
  112. package/scripts/sw-recruit.sh +1 -1
  113. package/scripts/sw-regression.sh +1 -1
  114. package/scripts/sw-release-manager.sh +1 -1
  115. package/scripts/sw-release.sh +1 -1
  116. package/scripts/sw-remote.sh +1 -1
  117. package/scripts/sw-replay.sh +1 -1
  118. package/scripts/sw-retro.sh +1 -1
  119. package/scripts/sw-review-rerun.sh +1 -1
  120. package/scripts/sw-scale.sh +69 -11
  121. package/scripts/sw-security-audit.sh +1 -1
  122. package/scripts/sw-self-optimize.sh +168 -4
  123. package/scripts/sw-session.sh +3 -3
  124. package/scripts/sw-setup.sh +1 -1
  125. package/scripts/sw-standup.sh +1 -1
  126. package/scripts/sw-status.sh +1 -1
  127. package/scripts/sw-strategic.sh +11 -6
  128. package/scripts/sw-stream.sh +7 -4
  129. package/scripts/sw-swarm.sh +3 -2
  130. package/scripts/sw-team-stages.sh +1 -1
  131. package/scripts/sw-templates.sh +3 -3
  132. package/scripts/sw-testgen.sh +11 -6
  133. package/scripts/sw-tmux-pipeline.sh +1 -1
  134. package/scripts/sw-tmux.sh +35 -1
  135. package/scripts/sw-trace.sh +1 -1
  136. package/scripts/sw-tracker.sh +1 -1
  137. package/scripts/sw-triage.sh +7 -7
  138. package/scripts/sw-upgrade.sh +1 -1
  139. package/scripts/sw-ux.sh +1 -1
  140. package/scripts/sw-webhook.sh +3 -2
  141. package/scripts/sw-widgets.sh +7 -4
  142. package/scripts/sw-worktree.sh +1 -1
  143. package/scripts/update-homebrew-sha.sh +21 -15
@@ -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.0.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
@@ -93,7 +96,8 @@ validate_quality() {
93
96
  local todos_pass=true
94
97
  if [[ -d "$REPO_DIR/.git" ]]; then
95
98
  local todo_count
96
- todo_count=$(cd "$REPO_DIR" && git diff --cached 2>/dev/null | grep -cE '^\+.*(TODO|FIXME)' || echo "0")
99
+ todo_count=$(cd "$REPO_DIR" && git diff --cached 2>/dev/null | grep -cE '^\+.*(TODO|FIXME)' || true)
100
+ todo_count="${todo_count:-0}"
97
101
  if [[ "$todo_count" -gt 0 ]]; then
98
102
  todos_pass=false
99
103
  all_pass=false
@@ -101,13 +105,16 @@ validate_quality() {
101
105
  json_output=$(echo "$json_output" | jq --arg tp "$todos_pass" '.checks.todos=$tp' 2>/dev/null || true)
102
106
  fi
103
107
 
104
- # Check 5: Hardcoded secrets patterns
108
+ # Check 5: Secrets patterns in diff
105
109
  local secrets_pass=true
106
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)
107
113
  if [[ -d "$REPO_DIR/.git" ]]; then
108
114
  local secret_count
109
- secret_count=$(cd "$REPO_DIR" && git diff --cached 2>/dev/null | grep -ciE "$secret_patterns" || echo "0")
110
- if [[ "$secret_count" -gt 3 ]]; then
115
+ secret_count=$(cd "$REPO_DIR" && git diff --cached 2>/dev/null | grep -ciE "$secret_patterns" || true)
116
+ secret_count="${secret_count:-0}"
117
+ if [[ "$secret_count" -gt "$secret_threshold" ]]; then
111
118
  secrets_pass=false
112
119
  all_pass=false
113
120
  fi
@@ -322,7 +329,8 @@ completion_detection() {
322
329
  # Check diminishing returns: < 10 lines changed in last 3 iterations
323
330
  local recent_changes=0
324
331
  if [[ -f "$ARTIFACTS_DIR/progress.md" ]]; then
325
- recent_changes=$(grep -c "^### Iteration" "$ARTIFACTS_DIR/progress.md" || echo "0")
332
+ recent_changes=$(grep -c "^### Iteration" "$ARTIFACTS_DIR/progress.md" || true)
333
+ recent_changes="${recent_changes:-0}"
326
334
  fi
327
335
 
328
336
  # Check if tests went from failing to passing
@@ -339,7 +347,8 @@ completion_detection() {
339
347
  local subtasks_done=true
340
348
  if [[ -f ".claude/goal.md" ]]; then
341
349
  local unchecked_count
342
- unchecked_count=$(grep -c "^- \[ \]" ".claude/goal.md" 2>/dev/null || echo "0")
350
+ unchecked_count=$(grep -c "^- \[ \]" ".claude/goal.md" 2>/dev/null || true)
351
+ unchecked_count="${unchecked_count:-0}"
343
352
  if [[ "$unchecked_count" -gt 0 ]]; then
344
353
  subtasks_done=false
345
354
  fi
@@ -399,14 +408,16 @@ calculate_quality_score() {
399
408
  # Security audit (20%)
400
409
  local security_files=0
401
410
  if [[ -d "$REPO_DIR" ]]; then
402
- 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}"
403
413
  security_score=$((security_files > 0 ? 85 : 0))
404
414
  fi
405
415
 
406
416
  # Architecture audit (15%)
407
417
  local architecture_files=0
408
418
  if [[ -d "$REPO_DIR" ]]; then
409
- 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}"
410
421
  architecture_score=$((architecture_files > 0 ? 80 : 0))
411
422
  fi
412
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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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.0.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
@@ -424,16 +425,73 @@ cmd_recommend() {
424
425
  echo -e " Modules changed: ${CYAN}${module_threshold}${RESET} (add reviewer above this)"
425
426
  echo ""
426
427
 
427
- # TODO: Parse pipeline context to generate actual recommendations
428
- echo -e " ${DIM}Recommendations require active pipeline context (passed via environment)${RESET}"
429
- echo ""
428
+ # Read pipeline context from env or pipeline-state.md
429
+ local actual_iterations="${ACTUAL_ITERATIONS:-0}"
430
+ local test_coverage="${TEST_COVERAGE:-0}"
431
+ local module_count="${MODULE_COUNT:-0}"
432
+
433
+ # Try to extract from pipeline-state.md if env vars not set
434
+ local state_file=".claude/pipeline-state.md"
435
+ if [[ "$actual_iterations" == "0" && -f "$state_file" ]]; then
436
+ actual_iterations=$(grep -oE 'iterations?[: ]+([0-9]+)' "$state_file" 2>/dev/null | grep -oE '[0-9]+' | tail -1 || echo "0")
437
+ actual_iterations="${actual_iterations:-0}"
438
+ fi
439
+ if [[ "$test_coverage" == "0" && -f "$state_file" ]]; then
440
+ test_coverage=$(grep -oE 'coverage[: ]+([0-9]+)' "$state_file" 2>/dev/null | grep -oE '[0-9]+' | tail -1 || echo "0")
441
+ test_coverage="${test_coverage:-0}"
442
+ fi
443
+
444
+ # Count changed modules via git diff
445
+ if [[ "$module_count" == "0" ]] && command -v git >/dev/null 2>&1; then
446
+ local base_branch="${BASE_BRANCH:-main}"
447
+ if git rev-parse --verify "$base_branch" >/dev/null 2>&1; then
448
+ module_count=$(git diff --name-only "${base_branch}..HEAD" 2>/dev/null \
449
+ | sed 's|/[^/]*$||' | sort -u | wc -l || true)
450
+ module_count="${module_count:-0}"
451
+ fi
452
+ module_count="${module_count:-0}"
453
+ fi
454
+
455
+ local has_recommendations=false
456
+
457
+ # Check iterations against threshold
458
+ if [[ "$actual_iterations" -gt 0 && "$actual_iterations" -ge "$iteration_threshold" ]]; then
459
+ echo -e " ${YELLOW}⚠${RESET} Failed ${actual_iterations} iterations (threshold: ${iteration_threshold})"
460
+ echo -e " ${CYAN}→ Recommend adding: tester${RESET}"
461
+ echo ""
462
+ has_recommendations=true
463
+ fi
464
+
465
+ # Check coverage against threshold
466
+ if [[ "$test_coverage" -gt 0 && "$test_coverage" -lt "$coverage_threshold" ]]; then
467
+ echo -e " ${YELLOW}⚠${RESET} Coverage at ${test_coverage}% (threshold: ${coverage_threshold}%)"
468
+ echo -e " ${CYAN}→ Recommend adding: tester${RESET}"
469
+ echo ""
470
+ has_recommendations=true
471
+ fi
472
+
473
+ # Check module count against threshold
474
+ if [[ "$module_count" -gt 0 && "$module_count" -ge "$module_threshold" ]]; then
475
+ echo -e " ${YELLOW}⚠${RESET} ${module_count} modules changed (threshold: ${module_threshold})"
476
+ echo -e " ${CYAN}→ Recommend adding: reviewer${RESET}"
477
+ echo ""
478
+ has_recommendations=true
479
+ fi
480
+
481
+ if [[ "$has_recommendations" == "false" ]]; then
482
+ if [[ "$actual_iterations" == "0" && "$test_coverage" == "0" && "$module_count" == "0" ]]; then
483
+ echo -e " ${DIM}No pipeline context available — run during an active pipeline or set ACTUAL_ITERATIONS, TEST_COVERAGE, MODULE_COUNT${RESET}"
484
+ else
485
+ success "All metrics within thresholds — no scaling changes needed"
486
+ fi
487
+ echo ""
488
+ fi
430
489
 
431
- # Example output when context is available:
432
- # echo -e " ${YELLOW}⚠${RESET} Failed 4 iterations (threshold: 3)"
433
- # echo -e " ${CYAN}→ Recommend adding: tester${RESET}"
434
- # echo ""
435
- # echo -e " ${YELLOW}⚠${RESET} Coverage at 45% (threshold: 60%)"
436
- # echo -e " ${CYAN}→ Recommend adding: tester${RESET}"
490
+ emit_event "scale.recommendation" \
491
+ "iterations=$actual_iterations" \
492
+ "coverage=$test_coverage" \
493
+ "modules=$module_count" \
494
+ "has_recommendations=$has_recommendations"
437
495
  }
438
496
 
439
497
  # ─── Help message ────────────────────────────────────────────────────────
@@ -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.0.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.0.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.0.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
 
@@ -355,7 +355,7 @@ if [[ "$DRY_RUN" == true ]]; then
355
355
  cat << EOF
356
356
  #!/usr/bin/env bash
357
357
  # Auto-generated by shipwright session — safe to delete
358
- cd ${PROJECT_DIR} || exit 1
358
+ cd "${PROJECT_DIR}" || exit 1
359
359
  printf '\\033]2;${TEAM_NAME}-lead\\033\\\\'
360
360
  PROMPT=\$(cat <prompt-file>)
361
361
  rm -f <prompt-file> "\$0"
@@ -367,7 +367,7 @@ EOF
367
367
  else
368
368
  cat << EOF
369
369
  #!/usr/bin/env bash
370
- cd ${PROJECT_DIR} || exit 1
370
+ cd "${PROJECT_DIR}" || exit 1
371
371
  printf '\\033]2;${TEAM_NAME}-lead\\033\\\\'
372
372
  rm -f "\$0"
373
373
  claude${DRY_RUN_FLAGS}
@@ -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.0.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.0.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.0.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.0.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)"