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.
- package/.claude/hooks/post-tool-use.sh +12 -5
- package/package.json +2 -2
- package/scripts/sw +9 -1
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-checkpoint.sh +79 -1
- package/scripts/sw-cleanup.sh +192 -7
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +409 -37
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +1 -1
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet.sh +1 -1
- package/scripts/sw-github-checks.sh +1 -1
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-init.sh +1 -1
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +4 -4
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +444 -49
- package/scripts/sw-memory.sh +198 -3
- package/scripts/sw-pipeline-composer.sh +8 -8
- package/scripts/sw-pipeline-vitals.sh +1096 -0
- package/scripts/sw-pipeline.sh +1692 -84
- package/scripts/sw-predictive.sh +1 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +4 -3
- package/scripts/sw-reaper.sh +5 -3
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-self-optimize.sh +109 -8
- package/scripts/sw-session.sh +31 -9
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-status.sh +192 -1
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-tracker.sh +1 -1
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-worktree.sh +1 -1
- package/templates/pipelines/autonomous.json +8 -1
- package/templates/pipelines/cost-aware.json +21 -0
- package/templates/pipelines/deployed.json +40 -6
- package/templates/pipelines/enterprise.json +16 -2
- package/templates/pipelines/fast.json +19 -0
- package/templates/pipelines/full.json +16 -2
- package/templates/pipelines/hotfix.json +19 -0
- package/templates/pipelines/standard.json +19 -0
package/scripts/sw-predictive.sh
CHANGED
package/scripts/sw-prep.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="1.
|
|
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.
|
|
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 |
|
|
98
|
-
|
|
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
|
package/scripts/sw-reaper.sh
CHANGED
|
@@ -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.
|
|
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 |
|
|
115
|
-
|
|
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
|
package/scripts/sw-remote.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="1.
|
|
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
|
-
|
|
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
|
-
'{
|
|
480
|
-
|
|
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 "$
|
|
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
|
# ═════════════════════════════════════════════════════════════════════════════
|
package/scripts/sw-session.sh
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
471
|
-
|
|
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.) ──────────────────
|
package/scripts/sw-setup.sh
CHANGED
package/scripts/sw-status.sh
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# ║ ║
|
|
5
5
|
# ║ Shows running teams, agent windows, and task progress. ║
|
|
6
6
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
7
|
-
VERSION="1.
|
|
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 ""
|
package/scripts/sw-templates.sh
CHANGED
|
@@ -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.
|
|
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
|
|
package/scripts/sw-tmux.sh
CHANGED
|
@@ -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.
|
|
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
|
|
package/scripts/sw-tracker.sh
CHANGED
package/scripts/sw-upgrade.sh
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
3
|
# ║ sw upgrade — Detect and apply updates from the repo ║
|
|
4
4
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
5
|
-
VERSION="1.
|
|
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
|
|
package/scripts/sw-worktree.sh
CHANGED
|
@@ -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.
|
|
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,
|