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
@@ -8,7 +8,7 @@
8
8
  # ║ ║
9
9
  # ║ --deploy Detect platform and generate deployed.json template ║
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
  trap 'rm -f "${tmp:-}"' EXIT
@@ -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="${REPO_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
12
12
 
@@ -395,7 +395,8 @@ _intelligence_fallback_analyze() {
395
395
  local outcomes_file="$HOME/.shipwright/optimization/outcomes.jsonl"
396
396
  if [[ -f "$outcomes_file" ]] && command -v jq &>/dev/null; then
397
397
  local sample_count
398
- sample_count=$(wc -l < "$outcomes_file" 2>/dev/null || echo "0")
398
+ sample_count=$(wc -l < "$outcomes_file" 2>/dev/null || true)
399
+ sample_count="${sample_count:-0}"
399
400
 
400
401
  if [[ "$sample_count" -gt 5 ]]; then
401
402
  # Compute average complexity from past outcomes
@@ -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
 
@@ -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
  # ║ Captures tmux pane scrollback and provides log browsing/search. ║
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
 
@@ -14,6 +14,7 @@ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
14
14
  unset CLAUDECODE 2>/dev/null || true
15
15
  # Ignore SIGHUP so tmux attach/detach doesn't kill long-running agent sessions
16
16
  trap '' HUP
17
+ trap '' SIGPIPE
17
18
 
18
19
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
20
 
@@ -30,6 +31,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
30
31
  if [[ -f "$SCRIPT_DIR/sw-db.sh" ]]; then
31
32
  source "$SCRIPT_DIR/sw-db.sh" 2>/dev/null || true
32
33
  fi
34
+ # Cross-pipeline discovery (learnings from other pipeline runs)
35
+ [[ -f "$SCRIPT_DIR/sw-discovery.sh" ]] && source "$SCRIPT_DIR/sw-discovery.sh" 2>/dev/null || true
33
36
  # Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
34
37
  [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
35
38
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
@@ -69,7 +72,7 @@ MAX_RESTARTS=$(_config_get_int "loop.max_restarts" 0 2>/dev/null || echo 0)
69
72
  SESSION_RESTART=false
70
73
  RESTART_COUNT=0
71
74
  REPO_OVERRIDE=""
72
- VERSION="3.1.0"
75
+ VERSION="3.2.0"
73
76
 
74
77
  # ─── Token Tracking ─────────────────────────────────────────────────────────
75
78
  LOOP_INPUT_TOKENS=0
@@ -975,7 +978,7 @@ check_fatal_error() {
975
978
  local match
976
979
  match=$(grep -iE "$fatal_patterns" "$log_file" 2>/dev/null | head -1 | cut -c1-120)
977
980
  error "Fatal CLI error: $match"
978
- return 0 # fatal error detected
981
+ return 1 # fatal error detected
979
982
  fi
980
983
 
981
984
  # Non-zero exit + tiny output = likely CLI crash
@@ -1661,13 +1664,20 @@ HOLISTIC_PROMPT
1661
1664
  # Prevents prompt from exceeding Claude's context limit (~200K tokens).
1662
1665
  # Trims least-critical sections first when over budget.
1663
1666
 
1664
- CONTEXT_BUDGET_CHARS="${CONTEXT_BUDGET_CHARS:-180000}" # ~45K tokens at 4 chars/token
1667
+ CONTEXT_BUDGET_CHARS="${CONTEXT_BUDGET_CHARS:-$(_config_get_int "loop.context_budget_chars" 180000 2>/dev/null || echo 180000)}" # ~45K tokens at 4 chars/token
1665
1668
 
1666
1669
  manage_context_window() {
1667
1670
  local prompt="$1"
1668
1671
  local budget="${CONTEXT_BUDGET_CHARS}"
1669
1672
  local current_len=${#prompt}
1670
1673
 
1674
+ # Read trimming tunables from config (env > daemon-config > policy > defaults.json)
1675
+ local trim_memory_chars trim_git_entries trim_hotspot_files trim_test_lines
1676
+ trim_memory_chars=$(_config_get_int "loop.context_trim_memory_chars" 20000 2>/dev/null || echo 20000)
1677
+ trim_git_entries=$(_config_get_int "loop.context_trim_git_entries" 10 2>/dev/null || echo 10)
1678
+ trim_hotspot_files=$(_config_get_int "loop.context_trim_hotspot_files" 5 2>/dev/null || echo 5)
1679
+ trim_test_lines=$(_config_get_int "loop.context_trim_test_lines" 50 2>/dev/null || echo 50)
1680
+
1671
1681
  if [[ "$current_len" -le "$budget" ]]; then
1672
1682
  echo "$prompt"
1673
1683
  return
@@ -1681,19 +1691,19 @@ manage_context_window() {
1681
1691
  trimmed=$(echo "$trimmed" | awk '/^## Performance Baselines/{skip=1; next} skip && /^## [^#]/{skip=0} !skip{print}')
1682
1692
  fi
1683
1693
 
1684
- # 2. Trim file hotspots to top 5
1694
+ # 2. Trim file hotspots to top N
1685
1695
  if [[ "${#trimmed}" -gt "$budget" ]]; then
1686
- trimmed=$(echo "$trimmed" | awk '/## File Hotspots/{p=1; c=0} p && /^- /{c++; if(c>5) next} {print}')
1696
+ trimmed=$(echo "$trimmed" | awk -v max="$trim_hotspot_files" '/## File Hotspots/{p=1; c=0} p && /^- /{c++; if(c>max) next} {print}')
1687
1697
  fi
1688
1698
 
1689
- # 3. Trim git log to last 10 entries
1699
+ # 3. Trim git log to last N entries
1690
1700
  if [[ "${#trimmed}" -gt "$budget" ]]; then
1691
- trimmed=$(echo "$trimmed" | awk '/## Recent Git Activity/{p=1; c=0} p && /^[a-f0-9]/{c++; if(c>10) next} {print}')
1701
+ trimmed=$(echo "$trimmed" | awk -v max="$trim_git_entries" '/## Recent Git Activity/{p=1; c=0} p && /^[a-f0-9]/{c++; if(c>max) next} {print}')
1692
1702
  fi
1693
1703
 
1694
- # 4. Truncate memory context to first 20K chars
1704
+ # 4. Truncate memory context to first N chars
1695
1705
  if [[ "${#trimmed}" -gt "$budget" ]]; then
1696
- trimmed=$(echo "$trimmed" | awk -v max=20000 '
1706
+ trimmed=$(echo "$trimmed" | awk -v max="$trim_memory_chars" '
1697
1707
  /## Memory Context/{mem=1; skip_rest=0; chars=0; print; next}
1698
1708
  mem && /^## [^#]/{mem=0; print; next}
1699
1709
  mem{chars+=length($0)+1; if(chars>max){print "... (memory truncated for context budget)"; skip_rest=1; mem=0; next}}
@@ -1703,11 +1713,11 @@ manage_context_window() {
1703
1713
  ')
1704
1714
  fi
1705
1715
 
1706
- # 5. Truncate test output to last 50 lines
1716
+ # 5. Truncate test output to last N lines
1707
1717
  if [[ "${#trimmed}" -gt "$budget" ]]; then
1708
- trimmed=$(echo "$trimmed" | awk '
1718
+ trimmed=$(echo "$trimmed" | awk -v max="$trim_test_lines" '
1709
1719
  /## Test Results/{found=1; buf=""; print; next}
1710
- found && /^## [^#]/{found=0; n=split(buf,arr,"\n"); start=(n>50)?(n-49):1; for(i=start;i<=n;i++) if(arr[i]!="") print arr[i]; print; next}
1720
+ found && /^## [^#]/{found=0; n=split(buf,arr,"\n"); start=(n>max)?(n-max+1):1; for(i=start;i<=n;i++) if(arr[i]!="") print arr[i]; print; next}
1711
1721
  found{buf=buf $0 "\n"; next}
1712
1722
  {print}
1713
1723
  ')
@@ -1786,6 +1796,16 @@ Fix these specific errors. Each line above is one distinct error from the test o
1786
1796
  memory_section="$("$SCRIPT_DIR/sw-memory.sh" inject build 2>/dev/null || true)"
1787
1797
  fi
1788
1798
 
1799
+ # Cross-pipeline discovery injection (learnings from other pipeline runs)
1800
+ local discovery_section=""
1801
+ if type inject_discoveries >/dev/null 2>&1; then
1802
+ local disc_output
1803
+ disc_output="$(inject_discoveries "${GOAL:-}" 2>/dev/null || true)"
1804
+ if [[ -n "$disc_output" ]]; then
1805
+ discovery_section="$disc_output"
1806
+ fi
1807
+ fi
1808
+
1789
1809
  # DORA baselines for context
1790
1810
  local dora_section=""
1791
1811
  if type memory_get_dora_baseline >/dev/null 2>&1; then
@@ -1990,6 +2010,9 @@ ${error_summary_section:+$error_summary_section
1990
2010
  ${memory_section:+## Memory Context
1991
2011
  $memory_section
1992
2012
  }
2013
+ ${discovery_section:+## Cross-Pipeline Learnings
2014
+ $discovery_section
2015
+ }
1993
2016
  ${dora_section:+$dora_section
1994
2017
  }
1995
2018
  ${intelligence_section:+$intelligence_section
@@ -2004,6 +2027,13 @@ ${restart_section:+$restart_section
2004
2027
  5. Commit your work with a descriptive message
2005
2028
  6. When the goal is FULLY achieved, output exactly: LOOP_COMPLETE
2006
2029
 
2030
+ ## Context Efficiency
2031
+ - Batch independent tool calls in parallel — avoid sequential round-trips
2032
+ - Use targeted file reads (offset/limit) instead of reading entire large files
2033
+ - Delegate large searches to subagents — only import the summary
2034
+ - Filter tool results with grep/jq before reasoning over them
2035
+ - Keep working memory lean — summarize completed steps, don't preserve full outputs
2036
+
2007
2037
  ${audit_section}
2008
2038
 
2009
2039
  ${audit_feedback_section}
@@ -2102,7 +2132,8 @@ detect_stuckness() {
2102
2132
  local stuckness_reasons=()
2103
2133
  local tracking_file="${STUCKNESS_TRACKING_FILE:-$LOG_DIR/stuckness-tracking.txt}"
2104
2134
  local tracking_lines
2105
- tracking_lines=$(wc -l < "$tracking_file" 2>/dev/null || echo "0")
2135
+ tracking_lines=$(wc -l < "$tracking_file" 2>/dev/null || true)
2136
+ tracking_lines="${tracking_lines:-0}"
2106
2137
 
2107
2138
  # Signal 1: Text overlap (existing logic) — compare last 2 iteration logs
2108
2139
  if [[ "$iteration" -ge 3 ]]; then
@@ -2117,7 +2148,8 @@ detect_stuckness() {
2117
2148
 
2118
2149
  if [[ -n "$lines1" && -n "$lines2" ]]; then
2119
2150
  total=$(echo "$lines1" | wc -l | tr -d ' ')
2120
- common=$(comm -12 <(echo "$lines1") <(echo "$lines2") 2>/dev/null | wc -l | tr -d ' ' || echo "0")
2151
+ common=$(comm -12 <(echo "$lines1") <(echo "$lines2") 2>/dev/null | wc -l | tr -d ' ' || true)
2152
+ common="${common:-0}"
2121
2153
  if [[ "$total" -gt 0 ]]; then
2122
2154
  overlap_pct=$(( common * 100 / total ))
2123
2155
  else
@@ -2189,7 +2221,8 @@ detect_stuckness() {
2189
2221
 
2190
2222
  # Signal 6: Git diff size — no or minimal code changes (existing)
2191
2223
  local diff_lines
2192
- diff_lines=$(git -C "${PROJECT_ROOT:-.}" diff HEAD 2>/dev/null | wc -l | tr -d ' ' || echo "0")
2224
+ diff_lines=$(git -C "${PROJECT_ROOT:-.}" diff HEAD 2>/dev/null | wc -l | tr -d ' ' || true)
2225
+ diff_lines="${diff_lines:-0}"
2193
2226
  if [[ "${diff_lines:-0}" -lt 5 ]] && [[ "$iteration" -gt 2 ]]; then
2194
2227
  stuckness_signals=$((stuckness_signals + 1))
2195
2228
  stuckness_reasons+=("no code changes in last iteration")
@@ -2356,7 +2389,7 @@ compose_worker_prompt() {
2356
2389
  role_desc="$recruit_desc"
2357
2390
  fi
2358
2391
  fi
2359
- # Fallback to hardcoded descriptions
2392
+ # Fallback to built-in role descriptions
2360
2393
  if [[ -z "$role_desc" ]]; then
2361
2394
  case "$role" in
2362
2395
  builder) role_desc="Focus on implementation — writing code, fixing bugs, building features. You are the primary builder." ;;
@@ -2412,10 +2445,33 @@ run_claude_iteration() {
2412
2445
  local final_prompt
2413
2446
  final_prompt=$(manage_context_window "$prompt")
2414
2447
 
2448
+ local raw_prompt_chars=${#prompt}
2415
2449
  local prompt_chars=${#final_prompt}
2416
2450
  local approx_tokens=$((prompt_chars / 4))
2417
2451
  info "Prompt: ~${approx_tokens} tokens (${prompt_chars} chars)"
2418
2452
 
2453
+ # Emit context efficiency metrics
2454
+ if type emit_event >/dev/null 2>&1; then
2455
+ local trim_ratio=0
2456
+ local budget_utilization=0
2457
+ if [[ "$raw_prompt_chars" -gt 0 ]]; then
2458
+ trim_ratio=$(awk -v raw="$raw_prompt_chars" -v trimmed="$prompt_chars" \
2459
+ 'BEGIN { printf "%.1f", ((raw - trimmed) / raw) * 100 }')
2460
+ fi
2461
+ if [[ "${CONTEXT_BUDGET_CHARS:-0}" -gt 0 ]]; then
2462
+ budget_utilization=$(awk -v used="$prompt_chars" -v budget="${CONTEXT_BUDGET_CHARS}" \
2463
+ 'BEGIN { printf "%.1f", (used / budget) * 100 }')
2464
+ fi
2465
+ emit_event "loop.context_efficiency" \
2466
+ "iteration=$ITERATION" \
2467
+ "raw_prompt_chars=$raw_prompt_chars" \
2468
+ "trimmed_prompt_chars=$prompt_chars" \
2469
+ "trim_ratio=$trim_ratio" \
2470
+ "budget_utilization=$budget_utilization" \
2471
+ "budget_chars=${CONTEXT_BUDGET_CHARS:-0}" \
2472
+ "job_id=${PIPELINE_JOB_ID:-loop-$$}" 2>/dev/null || true
2473
+ fi
2474
+
2419
2475
  local flags
2420
2476
  flags="$(build_claude_flags)"
2421
2477
 
@@ -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="${REPO_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
12
12
 
@@ -725,7 +725,7 @@ memory_analyze_failure() {
725
725
  "$failures_file" 2>/dev/null || true)
726
726
  fi
727
727
 
728
- # Build valid categories list (from compat.sh if available, else hardcoded)
728
+ # Build valid categories list (from compat.sh if available, else built-in defaults)
729
729
  local valid_cats="test_failure, build_error, lint_error, timeout, dependency, flaky, config"
730
730
  if [[ -n "${SW_ERROR_CATEGORIES:-}" ]]; then
731
731
  valid_cats=$(echo "$SW_ERROR_CATEGORIES" | tr ' ' ', ')
@@ -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
 
@@ -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
 
@@ -416,7 +416,8 @@ show_report() {
416
416
 
417
417
  # Summary stats
418
418
  local total_runs
419
- total_runs=$(wc -l < "$MODEL_USAGE_LOG" || echo "0")
419
+ total_runs=$(wc -l < "$MODEL_USAGE_LOG" || true)
420
+ total_runs="${total_runs:-0}"
420
421
 
421
422
  local haiku_runs
422
423
  haiku_runs=$(grep -c '"model":"haiku"' "$MODEL_USAGE_LOG" || true)
@@ -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
 
@@ -77,6 +77,7 @@ cmd_metrics() {
77
77
 
78
78
  # Parse events.jsonl
79
79
  if [[ -f "$EVENTS_FILE" ]]; then
80
+ {
80
81
  while IFS= read -r line; do
81
82
  [[ -z "$line" ]] && continue
82
83
 
@@ -462,7 +463,8 @@ cmd_report() {
462
463
  local last_event_ts=""
463
464
 
464
465
  if [[ -f "$EVENTS_FILE" ]]; then
465
- event_count=$(wc -l < "$EVENTS_FILE" || echo "0")
466
+ event_count=$(wc -l < "$EVENTS_FILE" || true)
467
+ event_count="${event_count:-0}"
466
468
  export_count=$(grep -c '"type":"otel_export"' "$EVENTS_FILE" 2>/dev/null || true)
467
469
  export_count="${export_count:-0}"
468
470
  webhook_count=$(grep -c '"type":"webhook_sent"' "$EVENTS_FILE" 2>/dev/null || true)
@@ -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
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -17,6 +17,8 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
17
17
  # Canonical helpers (colors, output, events)
18
18
  # shellcheck source=lib/helpers.sh
19
19
  [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
20
+ # shellcheck source=lib/config.sh
21
+ [[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
20
22
  # Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
21
23
  [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
22
24
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
@@ -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
 
@@ -150,7 +150,8 @@ _compute_convergence() {
150
150
  fi
151
151
 
152
152
  local total_errors
153
- total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || echo "0")
153
+ total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || true)
154
+ total_errors="${total_errors:-0}"
154
155
  total_errors=$(_safe_num "$total_errors")
155
156
 
156
157
  if [[ "$total_errors" -eq 0 ]]; then
@@ -270,7 +271,8 @@ _compute_error_maturity() {
270
271
  fi
271
272
 
272
273
  local total_errors
273
- total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || echo "0")
274
+ total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || true)
275
+ total_errors="${total_errors:-0}"
274
276
  total_errors=$(_safe_num "$total_errors")
275
277
 
276
278
  if [[ "$total_errors" -eq 0 ]]; then
@@ -280,7 +282,8 @@ _compute_error_maturity() {
280
282
 
281
283
  # Count unique error signatures
282
284
  local unique_errors
283
- unique_errors=$(jq -r '.signature // "unknown"' "$error_log" 2>/dev/null | sort -u | wc -l | tr -d ' ' || echo "0")
285
+ unique_errors=$(jq -r '.signature // "unknown"' "$error_log" 2>/dev/null | sort -u | wc -l | tr -d ' ' || true)
286
+ unique_errors="${unique_errors:-0}"
284
287
  unique_errors=$(_safe_num "$unique_errors")
285
288
 
286
289
  if [[ "$unique_errors" -eq 0 ]]; then
@@ -507,8 +510,10 @@ pipeline_compute_vitals() {
507
510
  # ── Error counts ──
508
511
  local total_errors=0 unique_errors=0
509
512
  if [[ -f "$error_log" ]]; then
510
- total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || echo "0")
511
- unique_errors=$(jq -r '.signature // "unknown"' "$error_log" 2>/dev/null | sort -u | wc -l | tr -d ' ' || echo "0")
513
+ total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || true)
514
+ total_errors="${total_errors:-0}"
515
+ unique_errors=$(jq -r '.signature // "unknown"' "$error_log" 2>/dev/null | sort -u | wc -l | tr -d ' ' || true)
516
+ unique_errors="${unique_errors:-0}"
512
517
  fi
513
518
 
514
519
  # ── Output JSON ──
@@ -10,8 +10,9 @@ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
  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
+ trap '' SIGPIPE
13
14
 
14
- VERSION="3.1.0"
15
+ VERSION="3.2.0"
15
16
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
17
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
17
18
 
@@ -149,7 +150,8 @@ rotate_event_log_if_needed() {
149
150
  local max_lines=10000
150
151
  [[ ! -f "$events_file" ]] && return
151
152
  local lines
152
- lines=$(wc -l < "$events_file" 2>/dev/null || echo "0")
153
+ lines=$(wc -l < "$events_file" 2>/dev/null || true)
154
+ lines="${lines:-0}"
153
155
  if [[ "$lines" -gt "$max_lines" ]]; then
154
156
  local tmp="${events_file}.rotating"
155
157
  if tail -5000 "$events_file" > "$tmp" 2>/dev/null && mv "$tmp" "$events_file" 2>/dev/null; then
@@ -506,14 +508,16 @@ load_pipeline_config() {
506
508
  # Check for intelligence-composed pipeline first
507
509
  local composed_pipeline="${ARTIFACTS_DIR}/composed-pipeline.json"
508
510
  if [[ -f "$composed_pipeline" ]] && type composer_validate_pipeline >/dev/null 2>&1; then
509
- # Use composed pipeline if fresh (< 1 hour old)
511
+ # Use composed pipeline if fresh (within cache TTL)
512
+ local composed_cache_ttl
513
+ composed_cache_ttl=$(_config_get_int "pipeline.composed_cache_ttl" 3600 2>/dev/null || echo 3600)
510
514
  local composed_age=99999
511
515
  local composed_mtime
512
516
  composed_mtime=$(file_mtime "$composed_pipeline")
513
517
  if [[ "$composed_mtime" -gt 0 ]]; then
514
518
  composed_age=$(( $(now_epoch) - composed_mtime ))
515
519
  fi
516
- if [[ "$composed_age" -lt 3600 ]]; then
520
+ if [[ "$composed_age" -lt "$composed_cache_ttl" ]]; then
517
521
  local validate_json
518
522
  validate_json=$(cat "$composed_pipeline" 2>/dev/null || echo "")
519
523
  if [[ -n "$validate_json" ]] && composer_validate_pipeline "$validate_json" 2>/dev/null; then
@@ -1791,10 +1795,14 @@ pipeline_cleanup_worktree() {
1791
1795
  # Extract branch name before removing worktree
1792
1796
  local _wt_branch=""
1793
1797
  _wt_branch=$(git worktree list --porcelain 2>/dev/null | grep -A1 "worktree ${worktree_path}$" | grep "^branch " | sed 's|^branch refs/heads/||' || true)
1794
- git worktree remove --force "$worktree_path" 2>/dev/null || true
1798
+ if ! git worktree remove --force "$worktree_path" 2>/dev/null; then
1799
+ warn "Failed to remove worktree at ${worktree_path} — may need manual cleanup"
1800
+ fi
1795
1801
  # Clean up the local branch
1796
1802
  if [[ -n "$_wt_branch" ]]; then
1797
- git branch -D "$_wt_branch" 2>/dev/null || true
1803
+ if ! git branch -D "$_wt_branch" 2>/dev/null; then
1804
+ warn "Failed to delete local branch ${_wt_branch}"
1805
+ fi
1798
1806
  fi
1799
1807
  # Clean up the remote branch (if it was pushed)
1800
1808
  if [[ -n "$_wt_branch" && "${NO_GITHUB:-}" != "true" ]]; then
@@ -2258,11 +2266,15 @@ pipeline_start() {
2258
2266
  if [[ -z "$GIT_BRANCH" ]]; then
2259
2267
  local ci_branch="ci/issue-${ISSUE_NUMBER}"
2260
2268
  info "CI resume: creating branch ${ci_branch} from current HEAD"
2261
- git checkout -b "$ci_branch" 2>/dev/null || git checkout "$ci_branch" 2>/dev/null || true
2269
+ if ! git checkout -b "$ci_branch" 2>/dev/null && ! git checkout "$ci_branch" 2>/dev/null; then
2270
+ warn "CI resume: failed to create or checkout branch ${ci_branch}"
2271
+ fi
2262
2272
  GIT_BRANCH="$ci_branch"
2263
2273
  elif [[ "$(git branch --show-current 2>/dev/null)" != "$GIT_BRANCH" ]]; then
2264
2274
  info "CI resume: checking out branch ${GIT_BRANCH}"
2265
- git checkout -b "$GIT_BRANCH" 2>/dev/null || git checkout "$GIT_BRANCH" 2>/dev/null || true
2275
+ if ! git checkout -b "$GIT_BRANCH" 2>/dev/null && ! git checkout "$GIT_BRANCH" 2>/dev/null; then
2276
+ warn "CI resume: failed to create or checkout branch ${GIT_BRANCH}"
2277
+ fi
2266
2278
  fi
2267
2279
  write_state 2>/dev/null || true
2268
2280
  fi
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.1.0"
9
+ VERSION="3.2.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -121,7 +121,8 @@ analyze_issue() {
121
121
  # Count estimated files affected by analyzing body content
122
122
  local file_scope complexity risk estimated_hours
123
123
  local files_mentioned
124
- files_mentioned=$(echo "$body" | grep -o '\b[a-zA-Z0-9_.-]*\.[a-z]*' | sort -u | wc -l || echo "0")
124
+ files_mentioned=$(echo "$body" | grep -o '\b[a-zA-Z0-9_.-]*\.[a-z]*' | sort -u | wc -l || true)
125
+ files_mentioned="${files_mentioned:-0}"
125
126
  files_mentioned=$((files_mentioned + 1)) # At least 1 file
126
127
 
127
128
  # Determine file scope
@@ -203,7 +204,7 @@ analyze_issue() {
203
204
 
204
205
  # ─── recommend_team <analysis_json> ──────────────────────────────────────────
205
206
  # Based on analysis, recommend team composition
206
- # Tries recruit's AI/heuristic team composition first, falls back to hardcoded rules.
207
+ # Tries recruit's AI/heuristic team composition first, falls back to built-in rules.
207
208
  recommend_team() {
208
209
  local analysis="$1"
209
210
 
@@ -253,7 +254,7 @@ recommend_team() {
253
254
  fi
254
255
  fi
255
256
 
256
- # ── Fallback: hardcoded heuristic team composition ──
257
+ # ── Fallback: heuristic team composition ──
257
258
  local complexity risk is_security is_perf file_scope
258
259
  complexity=$(echo "$analysis" | jq -r '.complexity')
259
260
  risk=$(echo "$analysis" | jq -r '.risk')
@@ -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
 
@@ -17,6 +17,8 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
17
17
  # Canonical helpers (colors, output, events)
18
18
  # shellcheck source=lib/helpers.sh
19
19
  [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
20
+ # shellcheck source=lib/config.sh
21
+ [[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
20
22
  # Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
21
23
  [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
22
24
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
@@ -384,10 +386,12 @@ Return JSON format:
384
386
  fi
385
387
 
386
388
  # Fallback: heuristic risk assessment
387
- local risk=50
389
+ local default_risk
390
+ default_risk=$(_config_get_int "predictive.default_risk_score" 50 2>/dev/null || echo 50)
391
+ local risk=$default_risk
388
392
  local reason="Default medium risk — no AI analysis available"
389
393
 
390
- # Check for learned keyword weights first, fall back to hardcoded
394
+ # Check for learned keyword weights first, fall back to config defaults
391
395
  local keywords_json
392
396
  keywords_json=$(_predictive_get_risk_keywords)
393
397
 
@@ -416,9 +420,11 @@ Return JSON format:
416
420
  reason="Learned keyword weights: ${matched_keywords%%, }"
417
421
  fi
418
422
  else
419
- # Default hardcoded keyword check
423
+ # Config-driven keyword risk elevation
424
+ local keyword_risk
425
+ keyword_risk=$(_config_get_int "predictive.keyword_risk_score" 70 2>/dev/null || echo 70)
420
426
  if echo "$issue_json" | grep -qiE "refactor|migration|breaking|security|deploy"; then
421
- risk=70
427
+ risk=$keyword_risk
422
428
  reason="Keywords suggest elevated complexity"
423
429
  fi
424
430
  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
 
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.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