shipwright-cli 1.9.0 → 1.10.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 (53) hide show
  1. package/.claude/hooks/post-tool-use.sh +12 -5
  2. package/package.json +2 -2
  3. package/scripts/sw +9 -1
  4. package/scripts/sw-adversarial.sh +1 -1
  5. package/scripts/sw-architecture-enforcer.sh +1 -1
  6. package/scripts/sw-checkpoint.sh +79 -1
  7. package/scripts/sw-cleanup.sh +192 -7
  8. package/scripts/sw-connect.sh +1 -1
  9. package/scripts/sw-cost.sh +1 -1
  10. package/scripts/sw-daemon.sh +409 -37
  11. package/scripts/sw-dashboard.sh +1 -1
  12. package/scripts/sw-developer-simulation.sh +1 -1
  13. package/scripts/sw-docs.sh +1 -1
  14. package/scripts/sw-doctor.sh +1 -1
  15. package/scripts/sw-fix.sh +1 -1
  16. package/scripts/sw-fleet.sh +1 -1
  17. package/scripts/sw-github-checks.sh +1 -1
  18. package/scripts/sw-github-deploy.sh +1 -1
  19. package/scripts/sw-github-graphql.sh +1 -1
  20. package/scripts/sw-heartbeat.sh +1 -1
  21. package/scripts/sw-init.sh +1 -1
  22. package/scripts/sw-intelligence.sh +1 -1
  23. package/scripts/sw-jira.sh +1 -1
  24. package/scripts/sw-launchd.sh +4 -4
  25. package/scripts/sw-linear.sh +1 -1
  26. package/scripts/sw-logs.sh +1 -1
  27. package/scripts/sw-loop.sh +444 -49
  28. package/scripts/sw-memory.sh +198 -3
  29. package/scripts/sw-pipeline-composer.sh +8 -8
  30. package/scripts/sw-pipeline-vitals.sh +1096 -0
  31. package/scripts/sw-pipeline.sh +1692 -84
  32. package/scripts/sw-predictive.sh +1 -1
  33. package/scripts/sw-prep.sh +1 -1
  34. package/scripts/sw-ps.sh +4 -3
  35. package/scripts/sw-reaper.sh +5 -3
  36. package/scripts/sw-remote.sh +1 -1
  37. package/scripts/sw-self-optimize.sh +109 -8
  38. package/scripts/sw-session.sh +31 -9
  39. package/scripts/sw-setup.sh +1 -1
  40. package/scripts/sw-status.sh +192 -1
  41. package/scripts/sw-templates.sh +1 -1
  42. package/scripts/sw-tmux.sh +1 -1
  43. package/scripts/sw-tracker.sh +1 -1
  44. package/scripts/sw-upgrade.sh +1 -1
  45. package/scripts/sw-worktree.sh +1 -1
  46. package/templates/pipelines/autonomous.json +8 -1
  47. package/templates/pipelines/cost-aware.json +21 -0
  48. package/templates/pipelines/deployed.json +40 -6
  49. package/templates/pipelines/enterprise.json +16 -2
  50. package/templates/pipelines/fast.json +19 -0
  51. package/templates/pipelines/full.json +16 -2
  52. package/templates/pipelines/hotfix.json +19 -0
  53. package/templates/pipelines/standard.json +19 -0
@@ -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="1.9.0"
9
+ VERSION="1.10.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="1.9.0"
9
+ VERSION="1.10.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="1.9.0"
8
+ VERSION="1.10.0"
9
9
  set -euo pipefail
10
10
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
11
 
@@ -94,8 +94,9 @@ HAS_AGENTS=false
94
94
  CURRENT_WINDOW=""
95
95
 
96
96
  # Format strings for tmux:
97
- # window_name | pane_title | pane_pid | pane_current_command | pane_active | pane_idle | pane_dead | session:window.pane
98
- FORMAT='#{window_name}|#{pane_title}|#{pane_pid}|#{pane_current_command}|#{pane_active}|#{pane_idle}|#{pane_dead}|#{session_name}:#{window_index}.#{pane_index}'
97
+ # window_name | pane_title | pane_pid | pane_current_command | pane_active | pane_idle | pane_dead | pane_id
98
+ # Uses #{pane_id} instead of #{pane_index} — stable regardless of pane-base-index
99
+ FORMAT='#{window_name}|#{pane_title}|#{pane_pid}|#{pane_current_command}|#{pane_active}|#{pane_idle}|#{pane_dead}|#{pane_id}'
99
100
 
100
101
  while IFS='|' read -r window_name pane_title pane_pid cmd pane_active pane_idle pane_dead pane_ref; do
101
102
  [[ -z "$window_name" ]] && continue
@@ -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="1.9.0"
14
+ VERSION="1.10.0"
15
15
  set -euo pipefail
16
16
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
17
17
 
@@ -111,8 +111,10 @@ release_pid_lock() {
111
111
  }
112
112
 
113
113
  # ─── tmux format string (reused from sw-ps.sh) ──────────────────────────
114
- # Fields: window_name | pane_title | pane_pid | pane_current_command | pane_active | pane_idle | pane_dead | session:window.pane
115
- FORMAT='#{window_name}|#{pane_title}|#{pane_pid}|#{pane_current_command}|#{pane_active}|#{pane_idle}|#{pane_dead}|#{session_name}:#{window_index}.#{pane_index}'
114
+ # Fields: window_name | pane_title | pane_pid | pane_current_command | pane_active | pane_idle | pane_dead | pane_id
115
+ # Uses #{pane_id} (%0, %1, ...) instead of #{pane_index} — IDs are stable
116
+ # regardless of pane-base-index setting, preventing wrong-pane kills.
117
+ FORMAT='#{window_name}|#{pane_title}|#{pane_pid}|#{pane_current_command}|#{pane_active}|#{pane_idle}|#{pane_dead}|#{pane_id}'
116
118
 
117
119
  # ─── Detection: should this pane be reaped? ───────────────────────────────
118
120
  # Returns 0 (reap) or 1 (skip), and sets REAP_REASON
@@ -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="1.9.0"
9
+ VERSION="1.10.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="1.9.0"
9
+ VERSION="1.10.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -316,8 +316,29 @@ optimize_tune_templates() {
316
316
  new_weights=$(echo "$new_weights" | jq --arg key "${tmpl}|${lbl}" --argjson w "$new_weight" '.[$key] = $w')
317
317
  done <<< "$combos"
318
318
 
319
+ # Build consumer-friendly format with per-template aggregates
320
+ local consumer_weights
321
+ consumer_weights=$(echo "$new_weights" | jq '
322
+ . as $raw |
323
+ # Extract unique template names
324
+ [keys[] | split("|")[0]] | unique | map(. as $tmpl |
325
+ {
326
+ key: $tmpl,
327
+ value: {
328
+ success_rate: ([$raw | to_entries[] | select(.key | startswith($tmpl + "|")) | .value] | if length > 0 then (add / length) else 0 end),
329
+ avg_duration_min: 0,
330
+ sample_size: ([$raw | to_entries[] | select(.key | startswith($tmpl + "|"))] | length),
331
+ raw_weights: ([$raw | to_entries[] | select(.key | startswith($tmpl + "|"))] | from_entries)
332
+ }
333
+ }
334
+ ) | from_entries |
335
+ {weights: ., updated_at: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))}
336
+ ' 2>/dev/null || echo "$new_weights")
337
+
319
338
  # Atomic write
320
- echo "$new_weights" > "$tmp_weights" && mv "$tmp_weights" "$TEMPLATE_WEIGHTS_FILE"
339
+ local tmp_cw
340
+ tmp_cw=$(mktemp "${TEMPLATE_WEIGHTS_FILE}.tmp.XXXXXX")
341
+ echo "$consumer_weights" > "$tmp_cw" && mv "$tmp_cw" "$TEMPLATE_WEIGHTS_FILE" || rm -f "$tmp_cw"
321
342
  fi
322
343
 
323
344
  rm -f "$tmp_stats" "$tmp_weights" 2>/dev/null || true
@@ -468,16 +489,23 @@ optimize_learn_iterations() {
468
489
  med_stats=$(calc_stats "$tmp_med")
469
490
  high_stats=$(calc_stats "$tmp_high")
470
491
 
471
- # Build iteration model
492
+ # Build iteration model with predictions wrapper
472
493
  local tmp_model
473
- tmp_model=$(mktemp)
494
+ tmp_model=$(mktemp "${ITERATION_MODEL_FILE}.tmp.XXXXXX")
474
495
  jq -n \
475
496
  --argjson low "$low_stats" \
476
497
  --argjson medium "$med_stats" \
477
498
  --argjson high "$high_stats" \
478
499
  --arg updated "$(now_iso)" \
479
- '{low: $low, medium: $medium, high: $high, updated_at: $updated}' \
480
- > "$tmp_model" && mv "$tmp_model" "$ITERATION_MODEL_FILE"
500
+ '{
501
+ predictions: {
502
+ low: {max_iterations: (if $low.mean > 0 then (($low.mean + $low.stddev) | floor | if . < 5 then 5 else . end) else 10 end), confidence: (if $low.samples >= 10 then 0.8 elif $low.samples >= 5 then 0.6 else 0.4 end), mean: $low.mean, stddev: $low.stddev, samples: $low.samples},
503
+ medium: {max_iterations: (if $medium.mean > 0 then (($medium.mean + $medium.stddev) | floor | if . < 10 then 10 else . end) else 20 end), confidence: (if $medium.samples >= 10 then 0.8 elif $medium.samples >= 5 then 0.6 else 0.4 end), mean: $medium.mean, stddev: $medium.stddev, samples: $medium.samples},
504
+ high: {max_iterations: (if $high.mean > 0 then (($high.mean + $high.stddev) | floor | if . < 15 then 15 else . end) else 30 end), confidence: (if $high.samples >= 10 then 0.8 elif $high.samples >= 5 then 0.6 else 0.4 end), mean: $high.mean, stddev: $high.stddev, samples: $high.samples}
505
+ },
506
+ updated_at: $updated
507
+ }' \
508
+ > "$tmp_model" && mv "$tmp_model" "$ITERATION_MODEL_FILE" || rm -f "$tmp_model"
481
509
 
482
510
  rm -f "$tmp_low" "$tmp_med" "$tmp_high" 2>/dev/null || true
483
511
 
@@ -606,10 +634,29 @@ optimize_route_models() {
606
634
  done <<< "$stages"
607
635
  fi
608
636
 
637
+ # Wrap in consumer-friendly format
638
+ local consumer_routing
639
+ consumer_routing=$(echo "$routing" | jq '{
640
+ routes: (. | to_entries | map({
641
+ key: .key,
642
+ value: {
643
+ model: .value.recommended,
644
+ confidence: (if .value.sonnet_samples + .value.opus_samples >= 10 then 0.9
645
+ elif .value.sonnet_samples + .value.opus_samples >= 5 then 0.7
646
+ else 0.5 end),
647
+ sonnet_rate: .value.sonnet_rate,
648
+ opus_rate: .value.opus_rate,
649
+ sonnet_samples: .value.sonnet_samples,
650
+ opus_samples: .value.opus_samples
651
+ }
652
+ }) | from_entries),
653
+ updated_at: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))
654
+ }' 2>/dev/null || echo "$routing")
655
+
609
656
  # Atomic write
610
657
  local tmp_routing
611
- tmp_routing=$(mktemp)
612
- echo "$routing" > "$tmp_routing" && mv "$tmp_routing" "$MODEL_ROUTING_FILE"
658
+ tmp_routing=$(mktemp "${MODEL_ROUTING_FILE}.tmp.XXXXXX")
659
+ echo "$consumer_routing" > "$tmp_routing" && mv "$tmp_routing" "$MODEL_ROUTING_FILE" || rm -f "$tmp_routing"
613
660
 
614
661
  rm -f "$tmp_stage_stats" 2>/dev/null || true
615
662
 
@@ -779,6 +826,8 @@ optimize_full_analysis() {
779
826
  optimize_learn_iterations
780
827
  optimize_route_models
781
828
  optimize_evolve_memory
829
+ optimize_report >> "${OPTIMIZATION_DIR}/last-report.txt" 2>/dev/null || true
830
+ optimize_adjust_audit_intensity 2>/dev/null || true
782
831
 
783
832
  echo ""
784
833
  success "Full optimization analysis complete"
@@ -899,6 +948,58 @@ optimize_report() {
899
948
  success "Report complete"
900
949
  }
901
950
 
951
+ # optimize_adjust_audit_intensity
952
+ # Reads quality-scores.jsonl trends and adjusts intelligence feature flags
953
+ # to increase audit intensity when quality is declining.
954
+ optimize_adjust_audit_intensity() {
955
+ local quality_file="${HOME}/.shipwright/optimization/quality-scores.jsonl"
956
+ local daemon_config="${REPO_DIR:-.}/.claude/daemon-config.json"
957
+
958
+ [[ ! -f "$quality_file" ]] && return 0
959
+ [[ ! -f "$daemon_config" ]] && return 0
960
+
961
+ # Get last 10 quality scores
962
+ local recent_scores avg_quality trend
963
+ recent_scores=$(tail -10 "$quality_file" 2>/dev/null || true)
964
+ [[ -z "$recent_scores" ]] && return 0
965
+
966
+ avg_quality=$(echo "$recent_scores" | jq -r '.quality_score // 70' 2>/dev/null \
967
+ | awk '{ sum += $1; count++ } END { if (count > 0) printf "%.0f", sum/count; else print 70 }')
968
+ avg_quality="${avg_quality:-70}"
969
+
970
+ # Detect trend: compare first half vs second half
971
+ local first_half_avg second_half_avg
972
+ first_half_avg=$(echo "$recent_scores" | head -5 | jq -r '.quality_score // 70' 2>/dev/null \
973
+ | awk '{ sum += $1; count++ } END { if (count > 0) printf "%.0f", sum/count; else print 70 }')
974
+ second_half_avg=$(echo "$recent_scores" | tail -5 | jq -r '.quality_score // 70' 2>/dev/null \
975
+ | awk '{ sum += $1; count++ } END { if (count > 0) printf "%.0f", sum/count; else print 70 }')
976
+
977
+ if [[ "${second_half_avg:-70}" -lt "${first_half_avg:-70}" ]]; then
978
+ trend="declining"
979
+ else
980
+ trend="stable_or_improving"
981
+ fi
982
+
983
+ # Declining quality → enable more audits
984
+ if [[ "$trend" == "declining" || "${avg_quality:-70}" -lt 60 ]]; then
985
+ info "Quality trend: ${trend} (avg: ${avg_quality}) — increasing audit intensity"
986
+ local tmp_dc
987
+ tmp_dc=$(mktemp "${daemon_config}.tmp.XXXXXX")
988
+ jq '.intelligence.adversarial_enabled = true | .intelligence.architecture_enabled = true' \
989
+ "$daemon_config" > "$tmp_dc" 2>/dev/null && mv "$tmp_dc" "$daemon_config" || rm -f "$tmp_dc"
990
+ emit_event "optimize.audit_intensity" \
991
+ "avg_quality=$avg_quality" \
992
+ "trend=$trend" \
993
+ "action=increase"
994
+ elif [[ "${avg_quality:-70}" -gt 85 ]]; then
995
+ info "Quality trend: excellent (avg: ${avg_quality}) — maintaining standard audits"
996
+ emit_event "optimize.audit_intensity" \
997
+ "avg_quality=$avg_quality" \
998
+ "trend=$trend" \
999
+ "action=maintain"
1000
+ fi
1001
+ }
1002
+
902
1003
  # ═════════════════════════════════════════════════════════════════════════════
903
1004
  # HELP
904
1005
  # ═════════════════════════════════════════════════════════════════════════════
@@ -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="1.9.0"
11
+ VERSION="1.10.0"
12
12
  set -euo pipefail
13
13
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
14
14
 
@@ -219,6 +219,13 @@ if [[ -n "$TEMPLATE_NAME" ]]; then
219
219
  exit 1
220
220
  fi
221
221
 
222
+ # Validate template parsed correctly — if jq failed, TEMPLATE_AGENTS is empty
223
+ if [[ ${#TEMPLATE_AGENTS[@]} -eq 0 ]]; then
224
+ error "Template '${TEMPLATE_NAME}' parsed with no agents. Check template JSON."
225
+ echo -e " ${DIM}File: ${TEMPLATE_FILE}${RESET}"
226
+ exit 1
227
+ fi
228
+
222
229
  echo -e " ${DIM}${TEMPLATE_DESC}${RESET}"
223
230
  echo -e " ${DIM}Agents: ${#TEMPLATE_AGENTS[@]} Layout: ${TEMPLATE_LAYOUT}${RESET}"
224
231
  fi
@@ -426,14 +433,19 @@ LAUNCHER_STATIC
426
433
  if [[ "$SKIP_PERMISSIONS" == true ]]; then
427
434
  CLAUDE_FLAGS="--dangerously-skip-permissions"
428
435
  fi
429
- sed "s|__DIR__|${PROJECT_DIR}|g;s|__TEAM__|${TEAM_NAME}|g;s|__PROMPT__|${PROMPT_FILE}|g;s|__CLAUDE_FLAGS__|${CLAUDE_FLAGS}|g" \
436
+ # Use awk for safe string replacement — sed breaks on & | \ in paths
437
+ awk -v dir="$PROJECT_DIR" -v team="$TEAM_NAME" -v prompt="$PROMPT_FILE" -v flags="$CLAUDE_FLAGS" \
438
+ '{gsub(/__DIR__/, dir); gsub(/__TEAM__/, team); gsub(/__PROMPT__/, prompt); gsub(/__CLAUDE_FLAGS__/, flags); print}' \
430
439
  "$LAUNCHER" > "${LAUNCHER}.tmp" && mv "${LAUNCHER}.tmp" "$LAUNCHER"
431
440
  chmod +x "$LAUNCHER"
432
441
 
433
442
  # Create window with command — no race condition!
434
443
  # bash --login loads PATH (needed for ~/.local/bin/claude)
435
- tmux new-window -n "$WINDOW_NAME" -c "$PROJECT_DIR" \
436
- "bash --login ${LAUNCHER}"
444
+ if ! tmux new-window -n "$WINDOW_NAME" -c "$PROJECT_DIR" \
445
+ "bash --login ${LAUNCHER}"; then
446
+ error "Failed to create tmux window '${WINDOW_NAME}'"
447
+ exit 1
448
+ fi
437
449
 
438
450
  elif [[ "$AUTO_LAUNCH" == true && -z "$TEAM_PROMPT" ]]; then
439
451
  # No template and no goal — just launch claude interactively
@@ -454,12 +466,17 @@ LAUNCHER_STATIC
454
466
  if [[ "$SKIP_PERMISSIONS" == true ]]; then
455
467
  CLAUDE_FLAGS="--dangerously-skip-permissions"
456
468
  fi
457
- sed "s|__DIR__|${PROJECT_DIR}|g;s|__TEAM__|${TEAM_NAME}|g;s|__CLAUDE_FLAGS__|${CLAUDE_FLAGS}|g" \
469
+ # Use awk for safe string replacement — sed breaks on & | \ in paths
470
+ awk -v dir="$PROJECT_DIR" -v team="$TEAM_NAME" -v flags="$CLAUDE_FLAGS" \
471
+ '{gsub(/__DIR__/, dir); gsub(/__TEAM__/, team); gsub(/__CLAUDE_FLAGS__/, flags); print}' \
458
472
  "$LAUNCHER" > "${LAUNCHER}.tmp" && mv "${LAUNCHER}.tmp" "$LAUNCHER"
459
473
  chmod +x "$LAUNCHER"
460
474
 
461
- tmux new-window -n "$WINDOW_NAME" -c "$PROJECT_DIR" \
462
- "bash --login ${LAUNCHER}"
475
+ if ! tmux new-window -n "$WINDOW_NAME" -c "$PROJECT_DIR" \
476
+ "bash --login ${LAUNCHER}"; then
477
+ error "Failed to create tmux window '${WINDOW_NAME}'"
478
+ exit 1
479
+ fi
463
480
 
464
481
  else
465
482
  # --no-launch: create window with a regular shell
@@ -467,8 +484,13 @@ LAUNCHER_STATIC
467
484
  info "Window ready. Launch Claude manually: ${DIM}claude${RESET}"
468
485
  fi
469
486
 
470
- # Apply dark theme (safe to run immediately no race with pane content)
471
- tmux select-pane -t "$WINDOW_NAME" -P 'bg=#1a1a2e,fg=#e4e4e7'
487
+ # Apply dark theme after a brief delay to ensure the shell has started.
488
+ # Without this, select-pane -P can race with shell initialization and
489
+ # the styling may not apply to the final pane state.
490
+ {
491
+ sleep 0.3
492
+ tmux select-pane -t "$WINDOW_NAME" -P 'bg=#1a1a2e,fg=#e4e4e7' 2>/dev/null || true
493
+ } &
472
494
 
473
495
  elif [[ -f "$ADAPTER_FILE" ]] && type -t spawn_agent &>/dev/null; then
474
496
  # ─── Non-tmux adapter session (iterm2, wezterm, etc.) ──────────────────
@@ -9,7 +9,7 @@
9
9
  set -euo pipefail
10
10
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
11
 
12
- VERSION="1.9.0"
12
+ VERSION="1.10.0"
13
13
 
14
14
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
15
 
@@ -4,7 +4,7 @@
4
4
  # ║ ║
5
5
  # ║ Shows running teams, agent windows, and task progress. ║
6
6
  # ╚═══════════════════════════════════════════════════════════════════════════╝
7
- VERSION="1.9.0"
7
+ VERSION="1.10.0"
8
8
  set -euo pipefail
9
9
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
 
@@ -24,6 +24,197 @@ _COMPAT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/compat.sh"
24
24
  # shellcheck source=lib/compat.sh
25
25
  [[ -f "$_COMPAT" ]] && source "$_COMPAT"
26
26
 
27
+ # ─── Argument Parsing ─────────────────────────────────────────────────────────
28
+ JSON_OUTPUT="false"
29
+ while [[ $# -gt 0 ]]; do
30
+ case "$1" in
31
+ --json) JSON_OUTPUT="true"; shift ;;
32
+ --help|-h)
33
+ echo "Usage: shipwright status [--json]"
34
+ echo ""
35
+ echo "Options:"
36
+ echo " --json Output structured JSON instead of formatted text"
37
+ echo " --help Show this help message"
38
+ exit 0
39
+ ;;
40
+ *) echo "Unknown option: $1" >&2; exit 1 ;;
41
+ esac
42
+ done
43
+
44
+ # ─── JSON Output Mode ─────────────────────────────────────────────────────────
45
+ if [[ "$JSON_OUTPUT" == "true" ]]; then
46
+ if ! command -v jq &>/dev/null; then
47
+ echo "Error: jq is required for --json output" >&2
48
+ exit 1
49
+ fi
50
+
51
+ # -- tmux windows --
52
+ WINDOWS_JSON="[]"
53
+ if command -v tmux &>/dev/null; then
54
+ WINDOWS_JSON=$(tmux list-windows -a -F '#{session_name}:#{window_index}|#{window_name}|#{window_panes}|#{window_active}' 2>/dev/null | \
55
+ while IFS='|' read -r sw wn pc act; do
56
+ is_claude="false"
57
+ echo "$wn" | grep -qi "claude" && is_claude="true"
58
+ is_active="false"
59
+ [[ "$act" == "1" ]] && is_active="true"
60
+ printf '%s\n' "{\"session_window\":\"$sw\",\"name\":\"$wn\",\"panes\":$pc,\"active\":$is_active,\"claude\":$is_claude}"
61
+ done | jq -s '.' 2>/dev/null) || WINDOWS_JSON="[]"
62
+ fi
63
+
64
+ # -- team configs --
65
+ TEAMS_JSON="[]"
66
+ _teams_dir="${HOME}/.claude/teams"
67
+ if [[ -d "$_teams_dir" ]]; then
68
+ TEAMS_JSON=$(find "$_teams_dir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort | \
69
+ while IFS= read -r td; do
70
+ [[ -z "$td" ]] && continue
71
+ tn="$(basename "$td")"
72
+ cf="${td}/config.json"
73
+ if [[ -f "$cf" ]]; then
74
+ mc=$(jq '.members | length' "$cf" 2>/dev/null || echo 0)
75
+ printf '%s\n' "{\"name\":\"$tn\",\"members\":$mc,\"has_config\":true}"
76
+ else
77
+ printf '%s\n' "{\"name\":\"$tn\",\"members\":0,\"has_config\":false}"
78
+ fi
79
+ done | jq -s '.' 2>/dev/null) || TEAMS_JSON="[]"
80
+ fi
81
+
82
+ # -- task lists --
83
+ TASKS_JSON="[]"
84
+ _tasks_dir="${HOME}/.claude/tasks"
85
+ if [[ -d "$_tasks_dir" ]]; then
86
+ _tasks_tmp=""
87
+ while IFS= read -r td; do
88
+ [[ -z "$td" ]] && continue
89
+ tn="$(basename "$td")"
90
+ _total=0; _completed=0; _in_progress=0; _pending=0
91
+ while IFS= read -r tf; do
92
+ [[ -z "$tf" ]] && continue
93
+ _total=$((_total + 1))
94
+ _st=$(jq -r '.status // "unknown"' "$tf" 2>/dev/null || echo "unknown")
95
+ case "$_st" in
96
+ completed) _completed=$((_completed + 1)) ;;
97
+ in_progress) _in_progress=$((_in_progress + 1)) ;;
98
+ pending) _pending=$((_pending + 1)) ;;
99
+ esac
100
+ done < <(find "$td" -type f -name '*.json' 2>/dev/null)
101
+ _tasks_tmp="${_tasks_tmp}{\"team\":\"$tn\",\"total\":$_total,\"completed\":$_completed,\"in_progress\":$_in_progress,\"pending\":$_pending}
102
+ "
103
+ done < <(find "$_tasks_dir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort)
104
+ if [[ -n "$_tasks_tmp" ]]; then
105
+ TASKS_JSON=$(printf '%s' "$_tasks_tmp" | jq -s '.' 2>/dev/null) || TASKS_JSON="[]"
106
+ fi
107
+ fi
108
+
109
+ # -- daemon --
110
+ DAEMON_JSON="null"
111
+ _state_file="${HOME}/.shipwright/daemon-state.json"
112
+ _pid_file="${HOME}/.shipwright/daemon.pid"
113
+ if [[ -f "$_state_file" ]]; then
114
+ _d_running="false"
115
+ _d_pid="null"
116
+ if [[ -f "$_pid_file" ]]; then
117
+ _d_pid_val=$(cat "$_pid_file" 2>/dev/null || true)
118
+ if [[ -n "$_d_pid_val" ]] && kill -0 "$_d_pid_val" 2>/dev/null; then
119
+ _d_running="true"
120
+ _d_pid="$_d_pid_val"
121
+ fi
122
+ fi
123
+ _active=$(jq -c '.active_jobs // []' "$_state_file" 2>/dev/null || echo "[]")
124
+ _queued=$(jq -c '.queued // []' "$_state_file" 2>/dev/null || echo "[]")
125
+ _completed=$(jq -c '[.completed // [] | reverse | .[:20][]]' "$_state_file" 2>/dev/null || echo "[]")
126
+ _started_at=$(jq -r '.started_at // null' "$_state_file" 2>/dev/null || echo "null")
127
+ _last_poll=$(jq -r '.last_poll // null' "$_state_file" 2>/dev/null || echo "null")
128
+ DAEMON_JSON=$(jq -n \
129
+ --argjson running "$_d_running" \
130
+ --argjson pid "$_d_pid" \
131
+ --argjson active_jobs "$_active" \
132
+ --argjson queued "$_queued" \
133
+ --argjson recent_completions "$_completed" \
134
+ --arg started_at "$_started_at" \
135
+ --arg last_poll "$_last_poll" \
136
+ '{running:$running, pid:$pid, started_at:$started_at, last_poll:$last_poll, active_jobs:$active_jobs, queued:$queued, recent_completions:$recent_completions}') || DAEMON_JSON="null"
137
+ fi
138
+
139
+ # -- issue tracker --
140
+ TRACKER_JSON="null"
141
+ _tracker_cfg="${HOME}/.shipwright/tracker-config.json"
142
+ if [[ -f "$_tracker_cfg" ]]; then
143
+ _provider=$(jq -r '.provider // "none"' "$_tracker_cfg" 2>/dev/null || echo "none")
144
+ if [[ "$_provider" != "none" && -n "$_provider" ]]; then
145
+ _url="null"
146
+ [[ "$_provider" == "jira" ]] && _url=$(jq -r '.jira.base_url // null' "$_tracker_cfg" 2>/dev/null || echo "null")
147
+ TRACKER_JSON=$(jq -n --arg provider "$_provider" --arg url "$_url" '{provider:$provider, url:$url}') || TRACKER_JSON="null"
148
+ fi
149
+ fi
150
+
151
+ # -- heartbeats --
152
+ HEARTBEATS_JSON="[]"
153
+ _hb_dir="${HOME}/.shipwright/heartbeats"
154
+ if [[ -d "$_hb_dir" ]]; then
155
+ HEARTBEATS_JSON=$(find "$_hb_dir" -name '*.json' -type f 2>/dev/null | \
156
+ while IFS= read -r hf; do
157
+ [[ -z "$hf" ]] && continue
158
+ _jid="$(basename "$hf" .json)"
159
+ _stage=$(jq -r '.stage // "unknown"' "$hf" 2>/dev/null || echo "unknown")
160
+ _ts=$(jq -r '.timestamp // null' "$hf" 2>/dev/null || echo "null")
161
+ _iter=$(jq -r '.iteration // 0' "$hf" 2>/dev/null || echo "0")
162
+ printf '%s\n' "{\"job_id\":\"$_jid\",\"stage\":\"$_stage\",\"timestamp\":\"$_ts\",\"iteration\":$_iter}"
163
+ done | jq -s '.' 2>/dev/null) || HEARTBEATS_JSON="[]"
164
+ fi
165
+
166
+ # -- remote machines --
167
+ MACHINES_JSON="[]"
168
+ _machines_file="${HOME}/.shipwright/machines.json"
169
+ if [[ -f "$_machines_file" ]]; then
170
+ MACHINES_JSON=$(jq -c '.machines // []' "$_machines_file" 2>/dev/null) || MACHINES_JSON="[]"
171
+ fi
172
+
173
+ # -- connected developers --
174
+ DEVELOPERS_JSON="null"
175
+ _team_cfg="${HOME}/.shipwright/team-config.json"
176
+ if [[ -f "$_team_cfg" ]]; then
177
+ _dash_url=$(jq -r '.dashboard_url // ""' "$_team_cfg" 2>/dev/null || true)
178
+ if [[ -n "$_dash_url" ]] && command -v curl &>/dev/null; then
179
+ _api_resp=$(curl -s --max-time 3 "${_dash_url}/api/status" 2>/dev/null || echo "")
180
+ if [[ -n "$_api_resp" ]] && echo "$_api_resp" | jq empty 2>/dev/null; then
181
+ _online=$(echo "$_api_resp" | jq '.total_online // 0' 2>/dev/null || echo "0")
182
+ _devs=$(echo "$_api_resp" | jq -c '.developers // []' 2>/dev/null || echo "[]")
183
+ DEVELOPERS_JSON=$(jq -n --argjson reachable true --argjson total_online "$_online" --argjson developers "$_devs" \
184
+ '{reachable:$reachable, total_online:$total_online, developers:$developers}') || DEVELOPERS_JSON="null"
185
+ else
186
+ DEVELOPERS_JSON='{"reachable":false,"total_online":0,"developers":[]}'
187
+ fi
188
+ fi
189
+ fi
190
+
191
+ # -- assemble and output --
192
+ jq -n \
193
+ --arg version "$VERSION" \
194
+ --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
195
+ --argjson tmux_windows "$WINDOWS_JSON" \
196
+ --argjson teams "$TEAMS_JSON" \
197
+ --argjson task_lists "$TASKS_JSON" \
198
+ --argjson daemon "$DAEMON_JSON" \
199
+ --argjson issue_tracker "$TRACKER_JSON" \
200
+ --argjson heartbeats "$HEARTBEATS_JSON" \
201
+ --argjson remote_machines "$MACHINES_JSON" \
202
+ --argjson connected_developers "$DEVELOPERS_JSON" \
203
+ '{
204
+ version: $version,
205
+ timestamp: $timestamp,
206
+ tmux_windows: $tmux_windows,
207
+ teams: $teams,
208
+ task_lists: $task_lists,
209
+ daemon: $daemon,
210
+ issue_tracker: $issue_tracker,
211
+ heartbeats: $heartbeats,
212
+ remote_machines: $remote_machines,
213
+ connected_developers: $connected_developers
214
+ }'
215
+ exit 0
216
+ fi
217
+
27
218
  # ─── Header ──────────────────────────────────────────────────────────────────
28
219
 
29
220
  echo ""
@@ -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="1.9.0"
8
+ VERSION="1.10.0"
9
9
  set -euo pipefail
10
10
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
11
 
@@ -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="1.9.0"
14
+ VERSION="1.10.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="1.9.0"
9
+ VERSION="1.10.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -2,7 +2,7 @@
2
2
  # ╔═══════════════════════════════════════════════════════════════════════════╗
3
3
  # ║ sw upgrade — Detect and apply updates from the repo ║
4
4
  # ╚═══════════════════════════════════════════════════════════════════════════╝
5
- VERSION="1.9.0"
5
+ VERSION="1.10.0"
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
@@ -5,7 +5,7 @@
5
5
  # ║ Each agent gets its own worktree so parallel agents don't clobber ║
6
6
  # ║ each other's files. Worktrees live in .worktrees/ relative to root. ║
7
7
  # ╚═══════════════════════════════════════════════════════════════════════════╝
8
- VERSION="1.9.0"
8
+ VERSION="1.10.0"
9
9
  set -euo pipefail
10
10
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
11
 
@@ -6,6 +6,11 @@
6
6
  "model": "opus",
7
7
  "agents": 1
8
8
  },
9
+ "intelligence": {
10
+ "adversarial_enabled": true,
11
+ "architecture_enabled": true,
12
+ "simulation_enabled": true
13
+ },
9
14
  "stages": [
10
15
  { "id": "intake", "enabled": true, "gate": "auto", "config": {} },
11
16
  {
@@ -42,7 +47,9 @@
42
47
  "negative": true,
43
48
  "e2e": true,
44
49
  "dod_audit": true,
45
- "max_cycles": 3
50
+ "max_cycles": 3,
51
+ "audit_intensity": "auto",
52
+ "compound_quality_blocking": true
46
53
  }
47
54
  },
48
55
  {
@@ -8,6 +8,11 @@
8
8
  "cost_tracking": true,
9
9
  "budget_check": true
10
10
  },
11
+ "intelligence": {
12
+ "adversarial_enabled": true,
13
+ "architecture_enabled": true,
14
+ "simulation_enabled": true
15
+ },
11
16
  "stages": [
12
17
  {
13
18
  "id": "intake",
@@ -59,6 +64,22 @@
59
64
  "cost_tracking": true
60
65
  }
61
66
  },
67
+ {
68
+ "id": "compound_quality",
69
+ "enabled": true,
70
+ "gate": "auto",
71
+ "config": {
72
+ "adversarial": true,
73
+ "negative": true,
74
+ "e2e": true,
75
+ "dod_audit": true,
76
+ "max_cycles": 2,
77
+ "audit_intensity": "auto",
78
+ "compound_quality_blocking": true,
79
+ "model": "sonnet",
80
+ "cost_tracking": true
81
+ }
82
+ },
62
83
  {
63
84
  "id": "pr",
64
85
  "enabled": true,