shipwright-cli 3.0.0 → 3.1.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 (135) hide show
  1. package/README.md +3 -3
  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/event-schema.json +142 -5
  7. package/config/policy.json +8 -0
  8. package/package.json +3 -3
  9. package/scripts/lib/architecture.sh +2 -1
  10. package/scripts/lib/bootstrap.sh +0 -0
  11. package/scripts/lib/config.sh +0 -0
  12. package/scripts/lib/daemon-adaptive.sh +0 -0
  13. package/scripts/lib/daemon-dispatch.sh +24 -1
  14. package/scripts/lib/daemon-failure.sh +0 -0
  15. package/scripts/lib/daemon-health.sh +0 -0
  16. package/scripts/lib/daemon-patrol.sh +40 -5
  17. package/scripts/lib/daemon-poll.sh +17 -0
  18. package/scripts/lib/daemon-state.sh +10 -0
  19. package/scripts/lib/daemon-triage.sh +1 -1
  20. package/scripts/lib/decide-autonomy.sh +295 -0
  21. package/scripts/lib/decide-scoring.sh +228 -0
  22. package/scripts/lib/decide-signals.sh +462 -0
  23. package/scripts/lib/fleet-failover.sh +0 -0
  24. package/scripts/lib/helpers.sh +16 -17
  25. package/scripts/lib/pipeline-detection.sh +0 -0
  26. package/scripts/lib/pipeline-github.sh +0 -0
  27. package/scripts/lib/pipeline-intelligence.sh +20 -3
  28. package/scripts/lib/pipeline-quality-checks.sh +3 -2
  29. package/scripts/lib/pipeline-quality.sh +0 -0
  30. package/scripts/lib/pipeline-stages.sh +199 -32
  31. package/scripts/lib/pipeline-state.sh +14 -0
  32. package/scripts/lib/policy.sh +0 -0
  33. package/scripts/lib/test-helpers.sh +0 -0
  34. package/scripts/postinstall.mjs +75 -1
  35. package/scripts/signals/example-collector.sh +36 -0
  36. package/scripts/sw +8 -4
  37. package/scripts/sw-activity.sh +1 -1
  38. package/scripts/sw-adaptive.sh +1 -1
  39. package/scripts/sw-adversarial.sh +1 -1
  40. package/scripts/sw-architecture-enforcer.sh +1 -1
  41. package/scripts/sw-auth.sh +1 -1
  42. package/scripts/sw-autonomous.sh +1 -1
  43. package/scripts/sw-changelog.sh +1 -1
  44. package/scripts/sw-checkpoint.sh +1 -1
  45. package/scripts/sw-ci.sh +1 -1
  46. package/scripts/sw-cleanup.sh +1 -1
  47. package/scripts/sw-code-review.sh +1 -1
  48. package/scripts/sw-connect.sh +1 -1
  49. package/scripts/sw-context.sh +1 -1
  50. package/scripts/sw-cost.sh +12 -3
  51. package/scripts/sw-daemon.sh +2 -2
  52. package/scripts/sw-dashboard.sh +1 -1
  53. package/scripts/sw-db.sh +41 -34
  54. package/scripts/sw-decide.sh +685 -0
  55. package/scripts/sw-decompose.sh +1 -1
  56. package/scripts/sw-deps.sh +1 -1
  57. package/scripts/sw-developer-simulation.sh +1 -1
  58. package/scripts/sw-discovery.sh +27 -1
  59. package/scripts/sw-doc-fleet.sh +1 -1
  60. package/scripts/sw-docs-agent.sh +1 -1
  61. package/scripts/sw-docs.sh +1 -1
  62. package/scripts/sw-doctor.sh +1 -1
  63. package/scripts/sw-dora.sh +1 -1
  64. package/scripts/sw-durable.sh +1 -1
  65. package/scripts/sw-e2e-orchestrator.sh +1 -1
  66. package/scripts/sw-eventbus.sh +1 -1
  67. package/scripts/sw-evidence.sh +1 -1
  68. package/scripts/sw-feedback.sh +1 -1
  69. package/scripts/sw-fix.sh +1 -1
  70. package/scripts/sw-fleet-discover.sh +1 -1
  71. package/scripts/sw-fleet-viz.sh +1 -1
  72. package/scripts/sw-fleet.sh +1 -1
  73. package/scripts/sw-github-app.sh +1 -1
  74. package/scripts/sw-github-checks.sh +1 -1
  75. package/scripts/sw-github-deploy.sh +1 -1
  76. package/scripts/sw-github-graphql.sh +1 -1
  77. package/scripts/sw-guild.sh +1 -1
  78. package/scripts/sw-heartbeat.sh +1 -1
  79. package/scripts/sw-hygiene.sh +1 -1
  80. package/scripts/sw-incident.sh +1 -1
  81. package/scripts/sw-init.sh +1 -1
  82. package/scripts/sw-instrument.sh +1 -1
  83. package/scripts/sw-intelligence.sh +9 -5
  84. package/scripts/sw-jira.sh +1 -1
  85. package/scripts/sw-launchd.sh +1 -1
  86. package/scripts/sw-linear.sh +1 -1
  87. package/scripts/sw-logs.sh +1 -1
  88. package/scripts/sw-loop.sh +267 -17
  89. package/scripts/sw-memory.sh +22 -5
  90. package/scripts/sw-mission-control.sh +1 -1
  91. package/scripts/sw-model-router.sh +1 -1
  92. package/scripts/sw-otel.sh +5 -3
  93. package/scripts/sw-oversight.sh +1 -1
  94. package/scripts/sw-pipeline-composer.sh +1 -1
  95. package/scripts/sw-pipeline-vitals.sh +1 -1
  96. package/scripts/sw-pipeline.sh +73 -1
  97. package/scripts/sw-pm.sh +1 -1
  98. package/scripts/sw-pr-lifecycle.sh +7 -4
  99. package/scripts/sw-predictive.sh +1 -1
  100. package/scripts/sw-prep.sh +1 -1
  101. package/scripts/sw-ps.sh +1 -1
  102. package/scripts/sw-public-dashboard.sh +1 -1
  103. package/scripts/sw-quality.sh +9 -5
  104. package/scripts/sw-reaper.sh +1 -1
  105. package/scripts/sw-regression.sh +1 -1
  106. package/scripts/sw-release-manager.sh +1 -1
  107. package/scripts/sw-release.sh +1 -1
  108. package/scripts/sw-remote.sh +1 -1
  109. package/scripts/sw-replay.sh +1 -1
  110. package/scripts/sw-retro.sh +1 -1
  111. package/scripts/sw-review-rerun.sh +1 -1
  112. package/scripts/sw-scale.sh +66 -10
  113. package/scripts/sw-security-audit.sh +1 -1
  114. package/scripts/sw-self-optimize.sh +1 -1
  115. package/scripts/sw-session.sh +3 -3
  116. package/scripts/sw-setup.sh +1 -1
  117. package/scripts/sw-standup.sh +1 -1
  118. package/scripts/sw-status.sh +1 -1
  119. package/scripts/sw-strategic.sh +1 -1
  120. package/scripts/sw-stream.sh +1 -1
  121. package/scripts/sw-swarm.sh +1 -1
  122. package/scripts/sw-team-stages.sh +1 -1
  123. package/scripts/sw-templates.sh +1 -1
  124. package/scripts/sw-testgen.sh +1 -1
  125. package/scripts/sw-tmux-pipeline.sh +1 -1
  126. package/scripts/sw-tmux.sh +1 -1
  127. package/scripts/sw-trace.sh +1 -1
  128. package/scripts/sw-tracker.sh +1 -1
  129. package/scripts/sw-triage.sh +6 -6
  130. package/scripts/sw-upgrade.sh +1 -1
  131. package/scripts/sw-ux.sh +1 -1
  132. package/scripts/sw-webhook.sh +1 -1
  133. package/scripts/sw-widgets.sh +1 -1
  134. package/scripts/sw-worktree.sh +1 -1
  135. package/scripts/update-homebrew-sha.sh +21 -15
@@ -11,7 +11,7 @@ unset CLAUDECODE 2>/dev/null || true
11
11
  # Ignore SIGHUP so tmux attach/detach doesn't kill long-running plan/design/review stages
12
12
  trap '' HUP
13
13
 
14
- VERSION="3.0.0"
14
+ VERSION="3.1.0"
15
15
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
16
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
17
17
 
@@ -93,6 +93,12 @@ if [[ -f "$SCRIPT_DIR/sw-durable.sh" ]]; then
93
93
  fi
94
94
  # shellcheck source=sw-db.sh — for db_save_checkpoint/db_load_checkpoint (durable workflows)
95
95
  [[ -f "$SCRIPT_DIR/sw-db.sh" ]] && source "$SCRIPT_DIR/sw-db.sh"
96
+ # Ensure DB schema exists so emit_event → db_add_event can write rows (CREATE IF NOT EXISTS is idempotent)
97
+ if type init_schema >/dev/null 2>&1 && type check_sqlite3 >/dev/null 2>&1 && check_sqlite3 2>/dev/null; then
98
+ init_schema 2>/dev/null || true
99
+ fi
100
+ # shellcheck source=sw-cost.sh — for cost_record persistence to costs.json + DB
101
+ [[ -f "$SCRIPT_DIR/sw-cost.sh" ]] && source "$SCRIPT_DIR/sw-cost.sh"
96
102
 
97
103
  # ─── GitHub API Modules (optional) ─────────────────────────────────────────
98
104
  # shellcheck source=sw-github-graphql.sh
@@ -633,6 +639,11 @@ cleanup_on_exit() {
633
639
  git stash pop --quiet 2>/dev/null || true
634
640
  fi
635
641
 
642
+ # Release durable pipeline lock
643
+ if [[ -n "${_PIPELINE_LOCK_ID:-}" ]] && type release_lock >/dev/null 2>&1; then
644
+ release_lock "$_PIPELINE_LOCK_ID" 2>/dev/null || true
645
+ fi
646
+
636
647
  # Cancel lingering in_progress GitHub Check Runs
637
648
  pipeline_cancel_check_runs 2>/dev/null || true
638
649
 
@@ -1550,6 +1561,10 @@ run_pipeline() {
1550
1561
  stage_dur_s=$(( $(now_epoch) - stage_start_epoch ))
1551
1562
  success "Stage ${BOLD}$id${RESET} complete ${DIM}(${timing})${RESET}"
1552
1563
  emit_event "stage.completed" "issue=${ISSUE_NUMBER:-0}" "stage=$id" "duration_s=$stage_dur_s" "result=success"
1564
+ # Emit vitals snapshot on every stage transition (not just build/test)
1565
+ if type pipeline_emit_progress_snapshot >/dev/null 2>&1 && [[ -n "${ISSUE_NUMBER:-}" ]]; then
1566
+ pipeline_emit_progress_snapshot "${ISSUE_NUMBER}" "$id" "0" "0" "0" "" 2>/dev/null || true
1567
+ fi
1553
1568
  # Record model outcome for UCB1 learning
1554
1569
  type record_model_outcome >/dev/null 2>&1 && record_model_outcome "$stage_model_used" "$id" 1 "$stage_dur_s" 0 2>/dev/null || true
1555
1570
  # Broadcast discovery for cross-pipeline learning
@@ -1580,6 +1595,10 @@ run_pipeline() {
1580
1595
  "duration_s=$stage_dur_s" \
1581
1596
  "error=${LAST_STAGE_ERROR:-unknown}" \
1582
1597
  "error_class=${LAST_STAGE_ERROR_CLASS:-unknown}"
1598
+ # Emit vitals snapshot on failure too
1599
+ if type pipeline_emit_progress_snapshot >/dev/null 2>&1 && [[ -n "${ISSUE_NUMBER:-}" ]]; then
1600
+ pipeline_emit_progress_snapshot "${ISSUE_NUMBER}" "$id" "0" "0" "0" "${LAST_STAGE_ERROR:-unknown}" 2>/dev/null || true
1601
+ fi
1583
1602
  # Log model used for prediction feedback
1584
1603
  echo "${id}|${stage_model_used}|false" >> "${ARTIFACTS_DIR}/model-routing.log"
1585
1604
  # Record model outcome for UCB1 learning
@@ -2103,6 +2122,19 @@ pipeline_start() {
2103
2122
 
2104
2123
  setup_dirs
2105
2124
 
2125
+ # Acquire durable lock to prevent concurrent pipelines on the same issue/goal
2126
+ _PIPELINE_LOCK_ID=""
2127
+ if type acquire_lock >/dev/null 2>&1; then
2128
+ _PIPELINE_LOCK_ID="pipeline-${ISSUE_NUMBER:-goal-$$}"
2129
+ if ! acquire_lock "$_PIPELINE_LOCK_ID" 5 2>/dev/null; then
2130
+ error "Another pipeline is already running for this issue/goal"
2131
+ echo -e " Wait for it to finish, or remove stale lock:"
2132
+ echo -e " ${DIM}rm -rf ~/.shipwright/durable/locks/${_PIPELINE_LOCK_ID}.lock${RESET}"
2133
+ _PIPELINE_LOCK_ID=""
2134
+ exit 1
2135
+ fi
2136
+ fi
2137
+
2106
2138
  # Generate reasoning trace (complexity analysis, template selection, failure predictions)
2107
2139
  local user_specified_pipeline="$PIPELINE_NAME"
2108
2140
  generate_reasoning_trace 2>/dev/null || true
@@ -2339,6 +2371,11 @@ pipeline_start() {
2339
2371
  "model=${MODEL:-opus}" \
2340
2372
  "goal=${GOAL}"
2341
2373
 
2374
+ # Record pipeline run in SQLite for dashboard visibility
2375
+ if type add_pipeline_run >/dev/null 2>&1; then
2376
+ add_pipeline_run "${SHIPWRIGHT_PIPELINE_ID}" "${ISSUE_NUMBER:-0}" "${GOAL}" "${BRANCH:-}" "${PIPELINE_NAME}" 2>/dev/null || true
2377
+ fi
2378
+
2342
2379
  # Durable WAL: publish pipeline start event
2343
2380
  if type publish_event >/dev/null 2>&1; then
2344
2381
  publish_event "pipeline.started" "{\"issue\":\"${ISSUE_NUMBER:-0}\",\"pipeline\":\"${PIPELINE_NAME}\",\"goal\":\"${GOAL:0:200}\"}" 2>/dev/null || true
@@ -2385,10 +2422,35 @@ pipeline_start() {
2385
2422
  "total_cost=$total_cost" \
2386
2423
  "self_heal_count=$SELF_HEAL_COUNT"
2387
2424
 
2425
+ # Update pipeline run status in SQLite
2426
+ if type update_pipeline_status >/dev/null 2>&1; then
2427
+ update_pipeline_status "${SHIPWRIGHT_PIPELINE_ID}" "completed" "${PIPELINE_SLOWEST_STAGE:-}" "complete" "${total_dur_s:-0}" 2>/dev/null || true
2428
+ fi
2429
+
2388
2430
  # Auto-ingest pipeline outcome into recruit profiles
2389
2431
  if [[ -x "$SCRIPT_DIR/sw-recruit.sh" ]]; then
2390
2432
  bash "$SCRIPT_DIR/sw-recruit.sh" ingest-pipeline 1 2>/dev/null || true
2391
2433
  fi
2434
+
2435
+ # Capture success patterns to memory (learn what works — parallel the failure path)
2436
+ if [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
2437
+ bash "$SCRIPT_DIR/sw-memory.sh" capture "$STATE_FILE" "$ARTIFACTS_DIR" 2>/dev/null || true
2438
+ fi
2439
+ # Update memory baselines with successful run metrics
2440
+ if type memory_update_metrics >/dev/null 2>&1; then
2441
+ memory_update_metrics "build_duration_s" "${total_dur_s:-0}" 2>/dev/null || true
2442
+ memory_update_metrics "total_cost_usd" "${total_cost:-0}" 2>/dev/null || true
2443
+ memory_update_metrics "iterations" "$((SELF_HEAL_COUNT + 1))" 2>/dev/null || true
2444
+ fi
2445
+
2446
+ # Record positive fix outcome if self-healing succeeded
2447
+ if [[ "$SELF_HEAL_COUNT" -gt 0 && -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
2448
+ local _success_sig
2449
+ _success_sig=$(tail -30 "$ARTIFACTS_DIR/test-results.log" 2>/dev/null | head -3 | tr '\n' ' ' | sed 's/^ *//;s/ *$//' || true)
2450
+ if [[ -n "$_success_sig" ]]; then
2451
+ bash "$SCRIPT_DIR/sw-memory.sh" fix-outcome "$_success_sig" "true" "true" 2>/dev/null || true
2452
+ fi
2453
+ fi
2392
2454
  else
2393
2455
  notify "Pipeline Failed" "Goal: ${GOAL}\nFailed at: ${CURRENT_STAGE_ID:-unknown}" "error"
2394
2456
  emit_event "pipeline.completed" \
@@ -2406,6 +2468,11 @@ pipeline_start() {
2406
2468
  "total_cost=$total_cost" \
2407
2469
  "self_heal_count=$SELF_HEAL_COUNT"
2408
2470
 
2471
+ # Update pipeline run status in SQLite
2472
+ if type update_pipeline_status >/dev/null 2>&1; then
2473
+ update_pipeline_status "${SHIPWRIGHT_PIPELINE_ID}" "failed" "${CURRENT_STAGE_ID:-unknown}" "failed" "${total_dur_s:-0}" 2>/dev/null || true
2474
+ fi
2475
+
2409
2476
  # Auto-ingest pipeline outcome into recruit profiles
2410
2477
  if [[ -x "$SCRIPT_DIR/sw-recruit.sh" ]]; then
2411
2478
  bash "$SCRIPT_DIR/sw-recruit.sh" ingest-pipeline 1 2>/dev/null || true
@@ -2542,6 +2609,11 @@ pipeline_start() {
2542
2609
  "model=$model_key" \
2543
2610
  "cost_usd=$total_cost"
2544
2611
 
2612
+ # Persist cost entry to costs.json + SQLite (was missing — tokens accumulated but never written)
2613
+ if type cost_record >/dev/null 2>&1; then
2614
+ cost_record "$TOTAL_INPUT_TOKENS" "$TOTAL_OUTPUT_TOKENS" "$model_key" "pipeline" "${ISSUE_NUMBER:-}" 2>/dev/null || true
2615
+ fi
2616
+
2545
2617
  # Record pipeline outcome for Thompson sampling / outcome-based learning
2546
2618
  if type db_record_outcome >/dev/null 2>&1; then
2547
2619
  local _outcome_success=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="3.0.0"
9
+ VERSION="3.1.0"
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="3.0.0"
9
+ VERSION="3.1.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -274,13 +274,16 @@ pr_review() {
274
274
  # Evaluate quality criteria
275
275
  local issues_found=0
276
276
  local file_count
277
- file_count=$(echo "$diff_output" | grep -c '^diff --git' || echo "0")
277
+ file_count=$(echo "$diff_output" | grep -c '^diff --git' || true)
278
+ file_count="${file_count:-0}"
278
279
 
279
280
  local line_additions
280
- line_additions=$(echo "$diff_output" | grep -c '^+' || echo "0")
281
+ line_additions=$(echo "$diff_output" | grep -c '^+' || true)
282
+ line_additions="${line_additions:-0}"
281
283
 
282
284
  local line_deletions
283
- line_deletions=$(echo "$diff_output" | grep -c '^-' || echo "0")
285
+ line_deletions=$(echo "$diff_output" | grep -c '^-' || true)
286
+ line_deletions="${line_deletions:-0}"
284
287
 
285
288
  info "Diff analysis: ${file_count} files, +${line_additions}/-${line_deletions} lines"
286
289
 
@@ -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.1.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.1.0"
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="3.0.0"
8
+ VERSION="3.1.0"
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="3.0.0"
9
+ VERSION="3.1.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.1.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -93,7 +93,8 @@ validate_quality() {
93
93
  local todos_pass=true
94
94
  if [[ -d "$REPO_DIR/.git" ]]; then
95
95
  local todo_count
96
- todo_count=$(cd "$REPO_DIR" && git diff --cached 2>/dev/null | grep -cE '^\+.*(TODO|FIXME)' || echo "0")
96
+ todo_count=$(cd "$REPO_DIR" && git diff --cached 2>/dev/null | grep -cE '^\+.*(TODO|FIXME)' || true)
97
+ todo_count="${todo_count:-0}"
97
98
  if [[ "$todo_count" -gt 0 ]]; then
98
99
  todos_pass=false
99
100
  all_pass=false
@@ -106,7 +107,8 @@ validate_quality() {
106
107
  local secret_patterns="(password|secret|token|api[_-]?key|aws_access|private_key)"
107
108
  if [[ -d "$REPO_DIR/.git" ]]; then
108
109
  local secret_count
109
- secret_count=$(cd "$REPO_DIR" && git diff --cached 2>/dev/null | grep -ciE "$secret_patterns" || echo "0")
110
+ secret_count=$(cd "$REPO_DIR" && git diff --cached 2>/dev/null | grep -ciE "$secret_patterns" || true)
111
+ secret_count="${secret_count:-0}"
110
112
  if [[ "$secret_count" -gt 3 ]]; then
111
113
  secrets_pass=false
112
114
  all_pass=false
@@ -322,7 +324,8 @@ completion_detection() {
322
324
  # Check diminishing returns: < 10 lines changed in last 3 iterations
323
325
  local recent_changes=0
324
326
  if [[ -f "$ARTIFACTS_DIR/progress.md" ]]; then
325
- recent_changes=$(grep -c "^### Iteration" "$ARTIFACTS_DIR/progress.md" || echo "0")
327
+ recent_changes=$(grep -c "^### Iteration" "$ARTIFACTS_DIR/progress.md" || true)
328
+ recent_changes="${recent_changes:-0}"
326
329
  fi
327
330
 
328
331
  # Check if tests went from failing to passing
@@ -339,7 +342,8 @@ completion_detection() {
339
342
  local subtasks_done=true
340
343
  if [[ -f ".claude/goal.md" ]]; then
341
344
  local unchecked_count
342
- unchecked_count=$(grep -c "^- \[ \]" ".claude/goal.md" 2>/dev/null || echo "0")
345
+ unchecked_count=$(grep -c "^- \[ \]" ".claude/goal.md" 2>/dev/null || true)
346
+ unchecked_count="${unchecked_count:-0}"
343
347
  if [[ "$unchecked_count" -gt 0 ]]; then
344
348
  subtasks_done=false
345
349
  fi
@@ -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.1.0"
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="3.0.0"
9
+ VERSION="3.1.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.1.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.1.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.1.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.1.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.1.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.1.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.1.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Dependency check ─────────────────────────────────────────────────────────
@@ -424,16 +424,72 @@ cmd_recommend() {
424
424
  echo -e " Modules changed: ${CYAN}${module_threshold}${RESET} (add reviewer above this)"
425
425
  echo ""
426
426
 
427
- # TODO: Parse pipeline context to generate actual recommendations
428
- echo -e " ${DIM}Recommendations require active pipeline context (passed via environment)${RESET}"
429
- echo ""
427
+ # Read pipeline context from env or pipeline-state.md
428
+ local actual_iterations="${ACTUAL_ITERATIONS:-0}"
429
+ local test_coverage="${TEST_COVERAGE:-0}"
430
+ local module_count="${MODULE_COUNT:-0}"
431
+
432
+ # Try to extract from pipeline-state.md if env vars not set
433
+ local state_file=".claude/pipeline-state.md"
434
+ if [[ "$actual_iterations" == "0" && -f "$state_file" ]]; then
435
+ actual_iterations=$(grep -oE 'iterations?[: ]+([0-9]+)' "$state_file" 2>/dev/null | grep -oE '[0-9]+' | tail -1 || echo "0")
436
+ actual_iterations="${actual_iterations:-0}"
437
+ fi
438
+ if [[ "$test_coverage" == "0" && -f "$state_file" ]]; then
439
+ test_coverage=$(grep -oE 'coverage[: ]+([0-9]+)' "$state_file" 2>/dev/null | grep -oE '[0-9]+' | tail -1 || echo "0")
440
+ test_coverage="${test_coverage:-0}"
441
+ fi
442
+
443
+ # Count changed modules via git diff
444
+ if [[ "$module_count" == "0" ]] && command -v git >/dev/null 2>&1; then
445
+ local base_branch="${BASE_BRANCH:-main}"
446
+ if git rev-parse --verify "$base_branch" >/dev/null 2>&1; then
447
+ module_count=$(git diff --name-only "${base_branch}..HEAD" 2>/dev/null \
448
+ | sed 's|/[^/]*$||' | sort -u | wc -l | xargs || echo "0")
449
+ fi
450
+ module_count="${module_count:-0}"
451
+ fi
452
+
453
+ local has_recommendations=false
454
+
455
+ # Check iterations against threshold
456
+ if [[ "$actual_iterations" -gt 0 && "$actual_iterations" -ge "$iteration_threshold" ]]; then
457
+ echo -e " ${YELLOW}⚠${RESET} Failed ${actual_iterations} iterations (threshold: ${iteration_threshold})"
458
+ echo -e " ${CYAN}→ Recommend adding: tester${RESET}"
459
+ echo ""
460
+ has_recommendations=true
461
+ fi
462
+
463
+ # Check coverage against threshold
464
+ if [[ "$test_coverage" -gt 0 && "$test_coverage" -lt "$coverage_threshold" ]]; then
465
+ echo -e " ${YELLOW}⚠${RESET} Coverage at ${test_coverage}% (threshold: ${coverage_threshold}%)"
466
+ echo -e " ${CYAN}→ Recommend adding: tester${RESET}"
467
+ echo ""
468
+ has_recommendations=true
469
+ fi
470
+
471
+ # Check module count against threshold
472
+ if [[ "$module_count" -gt 0 && "$module_count" -ge "$module_threshold" ]]; then
473
+ echo -e " ${YELLOW}⚠${RESET} ${module_count} modules changed (threshold: ${module_threshold})"
474
+ echo -e " ${CYAN}→ Recommend adding: reviewer${RESET}"
475
+ echo ""
476
+ has_recommendations=true
477
+ fi
478
+
479
+ if [[ "$has_recommendations" == "false" ]]; then
480
+ if [[ "$actual_iterations" == "0" && "$test_coverage" == "0" && "$module_count" == "0" ]]; then
481
+ echo -e " ${DIM}No pipeline context available — run during an active pipeline or set ACTUAL_ITERATIONS, TEST_COVERAGE, MODULE_COUNT${RESET}"
482
+ else
483
+ success "All metrics within thresholds — no scaling changes needed"
484
+ fi
485
+ echo ""
486
+ fi
430
487
 
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}"
488
+ emit_event "scale.recommendation" \
489
+ "iterations=$actual_iterations" \
490
+ "coverage=$test_coverage" \
491
+ "modules=$module_count" \
492
+ "has_recommendations=$has_recommendations"
437
493
  }
438
494
 
439
495
  # ─── 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.1.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.1.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -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.1.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.1.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.1.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.1.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.1.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)}"
@@ -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.0.0"
8
+ VERSION="3.1.0"
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="3.0.0"
9
+ VERSION="3.1.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.1.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.0.0"
8
+ VERSION="3.1.0"
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="3.0.0"
9
+ VERSION="3.1.0"
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="3.0.0"
9
+ VERSION="3.1.0"
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="3.0.0"
14
+ VERSION="3.1.0"
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="3.0.0"
9
+ VERSION="3.1.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.1.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12