shipwright-cli 2.3.1 → 3.0.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/README.md +95 -28
- package/completions/_shipwright +1 -1
- package/completions/shipwright.bash +3 -8
- package/completions/shipwright.fish +1 -1
- package/config/defaults.json +111 -0
- package/config/event-schema.json +81 -0
- package/config/policy.json +155 -2
- package/config/policy.schema.json +162 -1
- package/dashboard/coverage/coverage-summary.json +14 -0
- package/dashboard/public/index.html +1 -1
- package/dashboard/server.ts +306 -17
- package/dashboard/src/components/charts/bar.test.ts +79 -0
- package/dashboard/src/components/charts/donut.test.ts +68 -0
- package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
- package/dashboard/src/components/charts/sparkline.test.ts +125 -0
- package/dashboard/src/core/api.test.ts +309 -0
- package/dashboard/src/core/helpers.test.ts +301 -0
- package/dashboard/src/core/router.test.ts +307 -0
- package/dashboard/src/core/router.ts +7 -0
- package/dashboard/src/core/sse.test.ts +144 -0
- package/dashboard/src/views/metrics.test.ts +186 -0
- package/dashboard/src/views/overview.test.ts +173 -0
- package/dashboard/src/views/pipelines.test.ts +183 -0
- package/dashboard/src/views/team.test.ts +253 -0
- package/dashboard/vitest.config.ts +14 -5
- package/docs/TIPS.md +1 -1
- package/docs/patterns/README.md +1 -1
- package/package.json +15 -5
- package/scripts/adapters/docker-deploy.sh +1 -1
- package/scripts/adapters/tmux-adapter.sh +11 -1
- package/scripts/adapters/wezterm-adapter.sh +1 -1
- package/scripts/check-version-consistency.sh +1 -1
- package/scripts/lib/architecture.sh +126 -0
- package/scripts/lib/bootstrap.sh +75 -0
- package/scripts/lib/compat.sh +89 -6
- package/scripts/lib/config.sh +91 -0
- package/scripts/lib/daemon-adaptive.sh +3 -3
- package/scripts/lib/daemon-dispatch.sh +39 -16
- package/scripts/lib/daemon-health.sh +1 -1
- package/scripts/lib/daemon-patrol.sh +24 -12
- package/scripts/lib/daemon-poll.sh +37 -25
- package/scripts/lib/daemon-state.sh +115 -23
- package/scripts/lib/daemon-triage.sh +30 -8
- package/scripts/lib/fleet-failover.sh +63 -0
- package/scripts/lib/helpers.sh +30 -6
- package/scripts/lib/pipeline-detection.sh +2 -2
- package/scripts/lib/pipeline-github.sh +9 -9
- package/scripts/lib/pipeline-intelligence.sh +85 -35
- package/scripts/lib/pipeline-quality-checks.sh +16 -16
- package/scripts/lib/pipeline-quality.sh +1 -1
- package/scripts/lib/pipeline-stages.sh +242 -28
- package/scripts/lib/pipeline-state.sh +40 -4
- package/scripts/lib/test-helpers.sh +247 -0
- package/scripts/postinstall.mjs +3 -11
- package/scripts/sw +10 -4
- package/scripts/sw-activity.sh +1 -11
- package/scripts/sw-adaptive.sh +109 -85
- package/scripts/sw-adversarial.sh +4 -14
- package/scripts/sw-architecture-enforcer.sh +1 -11
- package/scripts/sw-auth.sh +8 -17
- package/scripts/sw-autonomous.sh +111 -49
- package/scripts/sw-changelog.sh +1 -11
- package/scripts/sw-checkpoint.sh +144 -20
- package/scripts/sw-ci.sh +2 -12
- package/scripts/sw-cleanup.sh +13 -17
- package/scripts/sw-code-review.sh +16 -36
- package/scripts/sw-connect.sh +5 -12
- package/scripts/sw-context.sh +9 -26
- package/scripts/sw-cost.sh +6 -16
- package/scripts/sw-daemon.sh +75 -70
- package/scripts/sw-dashboard.sh +57 -17
- package/scripts/sw-db.sh +506 -15
- package/scripts/sw-decompose.sh +1 -11
- package/scripts/sw-deps.sh +15 -25
- package/scripts/sw-developer-simulation.sh +1 -11
- package/scripts/sw-discovery.sh +112 -30
- package/scripts/sw-doc-fleet.sh +7 -17
- package/scripts/sw-docs-agent.sh +6 -16
- package/scripts/sw-docs.sh +4 -12
- package/scripts/sw-doctor.sh +134 -43
- package/scripts/sw-dora.sh +11 -19
- package/scripts/sw-durable.sh +35 -52
- package/scripts/sw-e2e-orchestrator.sh +11 -27
- package/scripts/sw-eventbus.sh +115 -115
- package/scripts/sw-evidence.sh +748 -0
- package/scripts/sw-feedback.sh +3 -13
- package/scripts/sw-fix.sh +2 -20
- package/scripts/sw-fleet-discover.sh +1 -11
- package/scripts/sw-fleet-viz.sh +10 -18
- package/scripts/sw-fleet.sh +13 -17
- package/scripts/sw-github-app.sh +6 -16
- package/scripts/sw-github-checks.sh +1 -11
- package/scripts/sw-github-deploy.sh +1 -11
- package/scripts/sw-github-graphql.sh +2 -12
- package/scripts/sw-guild.sh +1 -11
- package/scripts/sw-heartbeat.sh +49 -12
- package/scripts/sw-hygiene.sh +45 -43
- package/scripts/sw-incident.sh +284 -67
- package/scripts/sw-init.sh +35 -37
- package/scripts/sw-instrument.sh +1 -11
- package/scripts/sw-intelligence.sh +362 -51
- package/scripts/sw-jira.sh +5 -14
- package/scripts/sw-launchd.sh +2 -12
- package/scripts/sw-linear.sh +8 -17
- package/scripts/sw-logs.sh +4 -12
- package/scripts/sw-loop.sh +641 -90
- package/scripts/sw-memory.sh +243 -17
- package/scripts/sw-mission-control.sh +2 -12
- package/scripts/sw-model-router.sh +73 -34
- package/scripts/sw-otel.sh +11 -21
- package/scripts/sw-oversight.sh +1 -11
- package/scripts/sw-patrol-meta.sh +5 -11
- package/scripts/sw-pipeline-composer.sh +7 -17
- package/scripts/sw-pipeline-vitals.sh +1 -11
- package/scripts/sw-pipeline.sh +478 -122
- package/scripts/sw-pm.sh +2 -12
- package/scripts/sw-pr-lifecycle.sh +203 -29
- package/scripts/sw-predictive.sh +16 -22
- package/scripts/sw-prep.sh +6 -16
- package/scripts/sw-ps.sh +1 -11
- package/scripts/sw-public-dashboard.sh +2 -12
- package/scripts/sw-quality.sh +77 -10
- package/scripts/sw-reaper.sh +1 -11
- package/scripts/sw-recruit.sh +15 -25
- package/scripts/sw-regression.sh +11 -21
- package/scripts/sw-release-manager.sh +19 -28
- package/scripts/sw-release.sh +8 -16
- package/scripts/sw-remote.sh +1 -11
- package/scripts/sw-replay.sh +48 -44
- package/scripts/sw-retro.sh +70 -92
- package/scripts/sw-review-rerun.sh +220 -0
- package/scripts/sw-scale.sh +109 -32
- package/scripts/sw-security-audit.sh +12 -22
- package/scripts/sw-self-optimize.sh +239 -23
- package/scripts/sw-session.sh +3 -13
- package/scripts/sw-setup.sh +8 -18
- package/scripts/sw-standup.sh +5 -15
- package/scripts/sw-status.sh +32 -23
- package/scripts/sw-strategic.sh +129 -13
- package/scripts/sw-stream.sh +1 -11
- package/scripts/sw-swarm.sh +76 -36
- package/scripts/sw-team-stages.sh +10 -20
- package/scripts/sw-templates.sh +4 -14
- package/scripts/sw-testgen.sh +3 -13
- package/scripts/sw-tmux-pipeline.sh +1 -19
- package/scripts/sw-tmux-role-color.sh +0 -10
- package/scripts/sw-tmux-status.sh +3 -11
- package/scripts/sw-tmux.sh +2 -20
- package/scripts/sw-trace.sh +1 -19
- package/scripts/sw-tracker-github.sh +0 -10
- package/scripts/sw-tracker-jira.sh +1 -11
- package/scripts/sw-tracker-linear.sh +1 -11
- package/scripts/sw-tracker.sh +7 -24
- package/scripts/sw-triage.sh +24 -34
- package/scripts/sw-upgrade.sh +5 -23
- package/scripts/sw-ux.sh +1 -19
- package/scripts/sw-webhook.sh +18 -32
- package/scripts/sw-widgets.sh +3 -21
- package/scripts/sw-worktree.sh +11 -27
- package/scripts/update-homebrew-sha.sh +67 -0
- package/templates/pipelines/tdd.json +72 -0
- package/scripts/sw-pipeline.sh.mock +0 -7
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="
|
|
7
|
+
VERSION="3.0.0"
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
10
10
|
|
|
@@ -17,6 +17,24 @@ _COMPAT="$SCRIPT_DIR/lib/compat.sh"
|
|
|
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
|
+
[[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
|
|
21
|
+
|
|
22
|
+
# Portable ISO timestamp parsing (macOS uses -j -f, Linux uses -d)
|
|
23
|
+
_parse_iso_epoch() {
|
|
24
|
+
local ts="$1"
|
|
25
|
+
if date -j -f "%Y-%m-%dT%H:%M:%SZ" "$ts" "+%s" 2>/dev/null; then
|
|
26
|
+
return
|
|
27
|
+
fi
|
|
28
|
+
# Linux: date -d handles ISO format
|
|
29
|
+
date -d "$ts" "+%s" 2>/dev/null || echo "0"
|
|
30
|
+
}
|
|
31
|
+
_format_iso_time() {
|
|
32
|
+
local ts="$1" fmt="${2:-+%H:%M}"
|
|
33
|
+
if date -j -f "%Y-%m-%dT%H:%M:%SZ" "$ts" "$fmt" 2>/dev/null; then
|
|
34
|
+
return
|
|
35
|
+
fi
|
|
36
|
+
date -d "$ts" "$fmt" 2>/dev/null || echo ""
|
|
37
|
+
}
|
|
20
38
|
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
21
39
|
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
22
40
|
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
@@ -34,15 +52,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
34
52
|
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
35
53
|
}
|
|
36
54
|
fi
|
|
37
|
-
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
38
|
-
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
39
|
-
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
40
|
-
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
41
|
-
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
42
|
-
RED="${RED:-\033[38;2;248;113;113m}"
|
|
43
|
-
DIM="${DIM:-\033[2m}"
|
|
44
|
-
BOLD="${BOLD:-\033[1m}"
|
|
45
|
-
RESET="${RESET:-\033[0m}"
|
|
46
55
|
|
|
47
56
|
# ─── Argument Parsing ─────────────────────────────────────────────────────────
|
|
48
57
|
JSON_OUTPUT="false"
|
|
@@ -63,14 +72,14 @@ done
|
|
|
63
72
|
|
|
64
73
|
# ─── JSON Output Mode ─────────────────────────────────────────────────────────
|
|
65
74
|
if [[ "$JSON_OUTPUT" == "true" ]]; then
|
|
66
|
-
if ! command -v jq
|
|
75
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
67
76
|
echo "Error: jq is required for --json output" >&2
|
|
68
77
|
exit 1
|
|
69
78
|
fi
|
|
70
79
|
|
|
71
80
|
# -- tmux windows --
|
|
72
81
|
WINDOWS_JSON="[]"
|
|
73
|
-
if command -v tmux
|
|
82
|
+
if command -v tmux >/dev/null 2>&1; then
|
|
74
83
|
WINDOWS_JSON=$(tmux list-windows -a -F '#{session_name}:#{window_index}|#{window_name}|#{window_panes}|#{window_active}' 2>/dev/null | \
|
|
75
84
|
while IFS='|' read -r sw wn pc act; do
|
|
76
85
|
is_claude="false"
|
|
@@ -195,7 +204,7 @@ if [[ "$JSON_OUTPUT" == "true" ]]; then
|
|
|
195
204
|
_team_cfg="${HOME}/.shipwright/team-config.json"
|
|
196
205
|
if [[ -f "$_team_cfg" ]]; then
|
|
197
206
|
_dash_url=$(jq -r '.dashboard_url // ""' "$_team_cfg" 2>/dev/null || true)
|
|
198
|
-
if [[ -n "$_dash_url" ]] && command -v curl
|
|
207
|
+
if [[ -n "$_dash_url" ]] && command -v curl >/dev/null 2>&1; then
|
|
199
208
|
_api_resp=$(curl -s --max-time 3 "${_dash_url}/api/status" 2>/dev/null || echo "")
|
|
200
209
|
if [[ -n "$_api_resp" ]] && echo "$_api_resp" | jq empty 2>/dev/null; then
|
|
201
210
|
_online=$(echo "$_api_resp" | jq '.total_online // 0' 2>/dev/null || echo "0")
|
|
@@ -211,7 +220,7 @@ if [[ "$JSON_OUTPUT" == "true" ]]; then
|
|
|
211
220
|
# -- database --
|
|
212
221
|
DATABASE_JSON="null"
|
|
213
222
|
_db_file="${HOME}/.shipwright/shipwright.db"
|
|
214
|
-
if command -v sqlite3
|
|
223
|
+
if command -v sqlite3 >/dev/null 2>&1 && [[ -f "$_db_file" ]]; then
|
|
215
224
|
_db_ver=$(sqlite3 "$_db_file" "SELECT MAX(version) FROM _schema;" 2>/dev/null || echo "0")
|
|
216
225
|
_db_wal=$(sqlite3 "$_db_file" "PRAGMA journal_mode;" 2>/dev/null || echo "unknown")
|
|
217
226
|
_db_events=$(sqlite3 "$_db_file" "SELECT COUNT(*) FROM events;" 2>/dev/null || echo "0")
|
|
@@ -433,7 +442,7 @@ if [[ -f "$STATE_FILE" ]]; then
|
|
|
433
442
|
# Calculate uptime
|
|
434
443
|
uptime_str=""
|
|
435
444
|
if [[ "$started_at" != "unknown" && "$started_at" != "null" ]]; then
|
|
436
|
-
start_epoch=$(
|
|
445
|
+
start_epoch=$(_parse_iso_epoch "$started_at")
|
|
437
446
|
if [[ "$start_epoch" -gt 0 ]]; then
|
|
438
447
|
now_e=$(date +%s)
|
|
439
448
|
elapsed=$((now_e - start_epoch))
|
|
@@ -471,7 +480,7 @@ if [[ -f "$STATE_FILE" ]]; then
|
|
|
471
480
|
# Time elapsed
|
|
472
481
|
age_str=""
|
|
473
482
|
if [[ -n "$a_started" && "$a_started" != "null" ]]; then
|
|
474
|
-
s_epoch=$(
|
|
483
|
+
s_epoch=$(_parse_iso_epoch "$a_started")
|
|
475
484
|
if [[ "$s_epoch" -gt 0 ]]; then
|
|
476
485
|
now_e=$(date +%s)
|
|
477
486
|
el=$((now_e - s_epoch))
|
|
@@ -592,7 +601,7 @@ if [[ -f "$STATE_FILE" ]]; then
|
|
|
592
601
|
# Format timestamp as HH:MM
|
|
593
602
|
evt_time=""
|
|
594
603
|
if [[ -n "$evt_ts" && "$evt_ts" != "null" ]]; then
|
|
595
|
-
evt_time=$(
|
|
604
|
+
evt_time=$(_format_iso_time "$evt_ts" "+%H:%M")
|
|
596
605
|
fi
|
|
597
606
|
|
|
598
607
|
case "$evt_type" in
|
|
@@ -678,7 +687,7 @@ if [[ -d "$HEARTBEAT_DIR" ]]; then
|
|
|
678
687
|
|
|
679
688
|
for hb_file in "${HEARTBEAT_DIR}"/*.json; do
|
|
680
689
|
[[ -f "$hb_file" ]] || continue
|
|
681
|
-
|
|
690
|
+
job_id="$(basename "$hb_file" .json)"
|
|
682
691
|
hb_pid=$(jq -r '.pid // ""' "$hb_file" 2>/dev/null || true)
|
|
683
692
|
hb_stage=$(jq -r '.stage // ""' "$hb_file" 2>/dev/null || true)
|
|
684
693
|
hb_issue=$(jq -r '.issue // ""' "$hb_file" 2>/dev/null || true)
|
|
@@ -696,7 +705,7 @@ if [[ -d "$HEARTBEAT_DIR" ]]; then
|
|
|
696
705
|
# Calculate age
|
|
697
706
|
hb_age_str=""
|
|
698
707
|
if [[ -n "$hb_updated" && "$hb_updated" != "null" ]]; then
|
|
699
|
-
hb_epoch=$(
|
|
708
|
+
hb_epoch=$(_parse_iso_epoch "$hb_updated")
|
|
700
709
|
if [[ "$hb_epoch" -gt 0 ]]; then
|
|
701
710
|
now_e=$(date +%s)
|
|
702
711
|
hb_age=$((now_e - hb_epoch))
|
|
@@ -714,7 +723,7 @@ if [[ -d "$HEARTBEAT_DIR" ]]; then
|
|
|
714
723
|
hb_icon="${RED}●${RESET}"
|
|
715
724
|
fi
|
|
716
725
|
|
|
717
|
-
echo -e " ${hb_icon} ${BOLD}${
|
|
726
|
+
echo -e " ${hb_icon} ${BOLD}${job_id}${RESET} ${DIM}pid:${hb_pid}${RESET}"
|
|
718
727
|
detail_line=" "
|
|
719
728
|
[[ -n "$hb_issue" && "$hb_issue" != "null" && "$hb_issue" != "0" ]] && detail_line+="${CYAN}#${hb_issue}${RESET} "
|
|
720
729
|
[[ -n "$hb_stage" && "$hb_stage" != "null" ]] && detail_line+="${BLUE}${hb_stage}${RESET} "
|
|
@@ -753,7 +762,7 @@ fi
|
|
|
753
762
|
# ─── Database ────────────────────────────────────────────────────────────
|
|
754
763
|
|
|
755
764
|
_DB_FILE="${HOME}/.shipwright/shipwright.db"
|
|
756
|
-
if command -v sqlite3
|
|
765
|
+
if command -v sqlite3 >/dev/null 2>&1 && [[ -f "$_DB_FILE" ]]; then
|
|
757
766
|
echo ""
|
|
758
767
|
echo -e "${PURPLE}${BOLD} DATABASE${RESET} ${DIM}~/.shipwright/shipwright.db${RESET}"
|
|
759
768
|
echo -e "${DIM} ──────────────────────────────────────────${RESET}"
|
|
@@ -773,14 +782,14 @@ fi
|
|
|
773
782
|
# ─── Connected Developers ─────────────────────────────────────────────────
|
|
774
783
|
|
|
775
784
|
# Check if curl and jq are available
|
|
776
|
-
if command -v curl
|
|
785
|
+
if command -v curl >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
|
|
777
786
|
# Read dashboard URL from config, fall back to default
|
|
778
787
|
TEAM_CONFIG="${HOME}/.shipwright/team-config.json"
|
|
779
788
|
DASHBOARD_URL=""
|
|
780
789
|
if [[ -f "$TEAM_CONFIG" ]]; then
|
|
781
790
|
DASHBOARD_URL=$(jq -r '.dashboard_url // ""' "$TEAM_CONFIG" 2>/dev/null || true)
|
|
782
791
|
fi
|
|
783
|
-
[[ -z "$DASHBOARD_URL" ]] && DASHBOARD_URL="http://localhost
|
|
792
|
+
[[ -z "$DASHBOARD_URL" ]] && DASHBOARD_URL="http://localhost:$(_config_get_int "dashboard.port" 8767)"
|
|
784
793
|
|
|
785
794
|
# Try to reach the dashboard /api/team endpoint with 3s timeout
|
|
786
795
|
api_response=$(curl -s --max-time 3 "$DASHBOARD_URL/api/team" 2>/dev/null || true)
|
package/scripts/sw-strategic.sh
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# When sourced, do NOT add set -euo pipefail — the parent handles that.
|
|
8
8
|
# When run directly, main() sets up the error handling.
|
|
9
9
|
|
|
10
|
-
VERSION="
|
|
10
|
+
VERSION="3.0.0"
|
|
11
11
|
|
|
12
12
|
# ─── Paths (set defaults if not provided by parent) ──────────────────────────
|
|
13
13
|
SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
|
|
@@ -34,15 +34,14 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
|
34
34
|
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
35
35
|
now_epoch() { date +%s; }
|
|
36
36
|
fi
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
# Color fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
38
|
+
[[ -z "${PURPLE+set}" ]] && PURPLE='\033[38;2;124;58;237m'
|
|
39
|
+
[[ -z "${BOLD+set}" ]] && BOLD='\033[1m'
|
|
40
|
+
[[ -z "${DIM+set}" ]] && DIM='\033[2m'
|
|
41
|
+
[[ -z "${RESET+set}" ]] && RESET='\033[0m'
|
|
42
|
+
[[ -z "${YELLOW+set}" ]] && YELLOW='\033[38;2;250;204;21m'
|
|
43
|
+
[[ -z "${GREEN+set}" ]] && GREEN='\033[38;2;74;222;128m'
|
|
44
|
+
[[ -z "${RED+set}" ]] && RED='\033[38;2;248;113;113m'
|
|
46
45
|
# ─── Constants (policy overrides when config/policy.json exists) ─────────────
|
|
47
46
|
STRATEGIC_MAX_ISSUES=5
|
|
48
47
|
STRATEGIC_COOLDOWN_SECONDS=14400 # 4 hours
|
|
@@ -52,7 +51,7 @@ STRATEGIC_STRATEGY_LINES=200
|
|
|
52
51
|
STRATEGIC_LABELS="auto-patrol,ready-to-build,strategic,shipwright"
|
|
53
52
|
STRATEGIC_OVERLAP_THRESHOLD=60 # Skip if >60% word overlap
|
|
54
53
|
[[ -f "${SCRIPT_DIR:-}/lib/policy.sh" ]] && source "${SCRIPT_DIR:-}/lib/policy.sh"
|
|
55
|
-
if type policy_get
|
|
54
|
+
if type policy_get >/dev/null 2>&1; then
|
|
56
55
|
STRATEGIC_MAX_ISSUES=$(policy_get ".strategic.max_issues_per_cycle" "5")
|
|
57
56
|
STRATEGIC_COOLDOWN_SECONDS=$(policy_get ".strategic.cooldown_seconds" "14400")
|
|
58
57
|
STRATEGIC_STRATEGY_LINES=$(policy_get ".strategic.strategy_lines" "200")
|
|
@@ -118,6 +117,55 @@ strategic_load_title_cache() {
|
|
|
118
117
|
${closed_titles}"
|
|
119
118
|
}
|
|
120
119
|
|
|
120
|
+
# ─── Outcome Tracking (Learning Loop) ────────────────────────────────────────
|
|
121
|
+
# Tracks which strategic issues shipped vs closed unshipped, so we learn from outcomes.
|
|
122
|
+
strategic_track_outcomes() {
|
|
123
|
+
local outcomes_file="$HOME/.shipwright/strategic/outcomes.jsonl"
|
|
124
|
+
mkdir -p "$HOME/.shipwright/strategic"
|
|
125
|
+
|
|
126
|
+
if [[ "${NO_GITHUB:-false}" == "true" ]]; then
|
|
127
|
+
return 0
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
local strategic_issues
|
|
131
|
+
strategic_issues=$(gh issue list --label "strategic" --state all --json number,title,state,closedAt,labels --limit 50 2>/dev/null) || return 0
|
|
132
|
+
|
|
133
|
+
[[ -z "$strategic_issues" || "$strategic_issues" == "[]" ]] && return 0
|
|
134
|
+
|
|
135
|
+
touch "$outcomes_file" 2>/dev/null || true
|
|
136
|
+
|
|
137
|
+
while IFS= read -r issue; do
|
|
138
|
+
[[ -z "$issue" || "$issue" == "null" ]] && continue
|
|
139
|
+
local num title state
|
|
140
|
+
num=$(echo "$issue" | jq -r '.number')
|
|
141
|
+
title=$(echo "$issue" | jq -r '.title')
|
|
142
|
+
state=$(echo "$issue" | jq -r '.state')
|
|
143
|
+
|
|
144
|
+
# Check if already tracked
|
|
145
|
+
if grep -q "\"issue\":$num" "$outcomes_file" 2>/dev/null; then
|
|
146
|
+
continue
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# Determine outcome
|
|
150
|
+
local outcome="pending"
|
|
151
|
+
local success=false
|
|
152
|
+
if [[ "$state" == "CLOSED" ]]; then
|
|
153
|
+
local merged_prs
|
|
154
|
+
merged_prs=$(gh pr list --search "closes #$num" --state merged --json number --limit 1 2>/dev/null)
|
|
155
|
+
if [[ "$(echo "$merged_prs" | jq 'length' 2>/dev/null)" -gt 0 ]]; then
|
|
156
|
+
outcome="shipped"
|
|
157
|
+
success=true
|
|
158
|
+
else
|
|
159
|
+
outcome="closed_unshipped"
|
|
160
|
+
fi
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
local title_escaped
|
|
164
|
+
title_escaped=$(echo "$title" | jq -R . 2>/dev/null || echo "null")
|
|
165
|
+
echo "{\"issue\":$num,\"title\":$title_escaped,\"outcome\":\"$outcome\",\"success\":$success,\"tracked_at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" >> "$outcomes_file"
|
|
166
|
+
done < <(echo "$strategic_issues" | jq -c '.[]' 2>/dev/null)
|
|
167
|
+
}
|
|
168
|
+
|
|
121
169
|
# Check if a title has >threshold% overlap with any cached title.
|
|
122
170
|
# Returns 0 (true) if a near-duplicate is found, 1 (false) otherwise.
|
|
123
171
|
strategic_is_near_duplicate() {
|
|
@@ -340,6 +388,25 @@ strategic_build_prompt() {
|
|
|
340
388
|
recent_closed="(GitHub access disabled)"
|
|
341
389
|
fi
|
|
342
390
|
|
|
391
|
+
# Outcome history (shipped vs closed_unshipped) — learning loop
|
|
392
|
+
local outcomes_section="(No past outcomes yet — run a few cycles to build history)"
|
|
393
|
+
local outcomes_file="$HOME/.shipwright/strategic/outcomes.jsonl"
|
|
394
|
+
if [[ -f "$outcomes_file" ]]; then
|
|
395
|
+
local shipped failed
|
|
396
|
+
shipped=$(grep '"outcome":"shipped"' "$outcomes_file" 2>/dev/null | tail -10 | jq -r '.title' 2>/dev/null | sed 's/^/ - SUCCEEDED: /' || true)
|
|
397
|
+
failed=$(grep -E '"outcome":"closed_unshipped"' "$outcomes_file" 2>/dev/null | tail -10 | jq -r '.title' 2>/dev/null | sed 's/^/ - FAILED: /' || true)
|
|
398
|
+
if [[ -n "$shipped" || -n "$failed" ]]; then
|
|
399
|
+
outcomes_section="
|
|
400
|
+
Learn from these outcomes. Suggest more things like the successes, avoid patterns similar to failures.
|
|
401
|
+
|
|
402
|
+
SUCCEEDED (shipped with merged PR):
|
|
403
|
+
${shipped:- (none yet)}
|
|
404
|
+
|
|
405
|
+
FAILED (closed without shipping):
|
|
406
|
+
${failed:- (none yet)}"
|
|
407
|
+
fi
|
|
408
|
+
fi
|
|
409
|
+
|
|
343
410
|
# Platform health (hygiene + platform-refactor scan) — for AGI-level self-improvement
|
|
344
411
|
local platform_health_section="(No platform hygiene data — run \`shipwright hygiene platform-refactor\` or \`shipwright hygiene scan\` to generate .claude/platform-hygiene.json)"
|
|
345
412
|
if [[ -f "${repo_dir}/.claude/platform-hygiene.json" ]]; then
|
|
@@ -381,6 +448,9 @@ ${recent_closed}
|
|
|
381
448
|
## Platform Health (refactor / hardcoded / AGI-level readiness)
|
|
382
449
|
${platform_health_section}
|
|
383
450
|
|
|
451
|
+
## Past Strategic Suggestions and Outcomes (learn from these)
|
|
452
|
+
${outcomes_section}
|
|
453
|
+
|
|
384
454
|
## Your Task
|
|
385
455
|
Based on the strategy priorities and current data, recommend 1-3 concrete improvements to build next. Each should be a single, well-scoped task completable by one autonomous pipeline run.
|
|
386
456
|
|
|
@@ -395,6 +465,7 @@ ACCEPTANCE: <bullet list of acceptance criteria, one per line starting with "- "
|
|
|
395
465
|
---
|
|
396
466
|
|
|
397
467
|
Rules:
|
|
468
|
+
- Learn from PAST STRATEGIC SUGGESTIONS: prefer patterns that succeeded, avoid patterns similar to failures
|
|
398
469
|
- Do NOT duplicate any open issue OR any recently completed issue
|
|
399
470
|
- Prioritize based on STRATEGY.md priorities (P0 > P1 > P2 > ...)
|
|
400
471
|
- Focus on concrete, actionable improvements (not vague goals)
|
|
@@ -416,7 +487,7 @@ strategic_call_api() {
|
|
|
416
487
|
return 1
|
|
417
488
|
fi
|
|
418
489
|
|
|
419
|
-
if ! command -v claude
|
|
490
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
420
491
|
error "Claude Code CLI not found — install with: npm install -g @anthropic-ai/claude-code"
|
|
421
492
|
return 1
|
|
422
493
|
fi
|
|
@@ -659,6 +730,10 @@ strategic_run() {
|
|
|
659
730
|
return 1
|
|
660
731
|
fi
|
|
661
732
|
|
|
733
|
+
# Track outcomes of past strategic issues (learning loop)
|
|
734
|
+
info "Tracking outcomes of past strategic issues..."
|
|
735
|
+
strategic_track_outcomes || true
|
|
736
|
+
|
|
662
737
|
# Load existing issue titles for semantic dedup
|
|
663
738
|
info "Loading issue title cache for dedup..."
|
|
664
739
|
strategic_load_title_cache
|
|
@@ -757,6 +832,45 @@ strategic_status() {
|
|
|
757
832
|
echo ""
|
|
758
833
|
}
|
|
759
834
|
|
|
835
|
+
# ─── Outcomes Command ─────────────────────────────────────────────────────────
|
|
836
|
+
strategic_outcomes() {
|
|
837
|
+
local outcomes_file="$HOME/.shipwright/strategic/outcomes.jsonl"
|
|
838
|
+
|
|
839
|
+
echo -e "\n${PURPLE}${BOLD}━━━ Strategic Outcomes (Learning Loop) ━━━${RESET}\n"
|
|
840
|
+
|
|
841
|
+
if [[ ! -f "$outcomes_file" ]]; then
|
|
842
|
+
info "No outcomes tracked yet. Run \`shipwright strategic run\` to start the learning loop."
|
|
843
|
+
echo ""
|
|
844
|
+
return 0
|
|
845
|
+
fi
|
|
846
|
+
|
|
847
|
+
local shipped_count failed_count pending_count
|
|
848
|
+
shipped_count=$(grep -c '"outcome":"shipped"' "$outcomes_file" 2>/dev/null || echo "0")
|
|
849
|
+
failed_count=$(grep -cE '"outcome":"closed_unshipped"' "$outcomes_file" 2>/dev/null || echo "0")
|
|
850
|
+
pending_count=$(grep -c '"outcome":"pending"' "$outcomes_file" 2>/dev/null || echo "0")
|
|
851
|
+
|
|
852
|
+
echo -e " ${GREEN}Shipped:${RESET} $shipped_count (closed with merged PR)"
|
|
853
|
+
echo -e " ${RED}Closed unshipped:${RESET} $failed_count (closed without merge)"
|
|
854
|
+
echo -e " ${DIM}Pending:${RESET} $pending_count (still open)"
|
|
855
|
+
echo ""
|
|
856
|
+
|
|
857
|
+
echo -e " ${BOLD}Recent shipped:${RESET}"
|
|
858
|
+
grep '"outcome":"shipped"' "$outcomes_file" 2>/dev/null | tail -5 | while IFS= read -r line; do
|
|
859
|
+
local title
|
|
860
|
+
title=$(echo "$line" | jq -r '.title // "?"' 2>/dev/null)
|
|
861
|
+
echo -e " ${GREEN}✓${RESET} $title"
|
|
862
|
+
done
|
|
863
|
+
echo ""
|
|
864
|
+
|
|
865
|
+
echo -e " ${BOLD}Recent failed (closed without shipping):${RESET}"
|
|
866
|
+
grep -E '"outcome":"closed_unshipped"' "$outcomes_file" 2>/dev/null | tail -5 | while IFS= read -r line; do
|
|
867
|
+
local title
|
|
868
|
+
title=$(echo "$line" | jq -r '.title // "?"' 2>/dev/null)
|
|
869
|
+
echo -e " ${RED}✗${RESET} $title"
|
|
870
|
+
done
|
|
871
|
+
echo ""
|
|
872
|
+
}
|
|
873
|
+
|
|
760
874
|
# ─── Help ─────────────────────────────────────────────────────────────────────
|
|
761
875
|
strategic_show_help() {
|
|
762
876
|
echo -e "${PURPLE}${BOLD}Shipwright Strategic Intelligence Agent${RESET} v${VERSION}\n"
|
|
@@ -765,7 +879,8 @@ strategic_show_help() {
|
|
|
765
879
|
echo -e " sw-strategic.sh <command>\n"
|
|
766
880
|
echo -e "${BOLD}Commands:${RESET}"
|
|
767
881
|
echo -e " run [--force] Run a strategic analysis cycle (--force bypasses cooldown)"
|
|
768
|
-
echo -e " status
|
|
882
|
+
echo -e " status Show last run stats and cooldown"
|
|
883
|
+
echo -e " outcomes Show outcome tracking (shipped vs failed suggestions)"
|
|
769
884
|
echo -e " help Show this help\n"
|
|
770
885
|
echo -e "${BOLD}Environment:${RESET}"
|
|
771
886
|
echo -e " CLAUDE_CODE_OAUTH_TOKEN Required for Claude access"
|
|
@@ -806,6 +921,7 @@ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
|
806
921
|
case "$cmd" in
|
|
807
922
|
run) strategic_run "$@" ;;
|
|
808
923
|
status) strategic_status ;;
|
|
924
|
+
outcomes) strategic_outcomes ;;
|
|
809
925
|
help) strategic_show_help ;;
|
|
810
926
|
*)
|
|
811
927
|
error "Unknown command: $cmd"
|
package/scripts/sw-stream.sh
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# ║ Streams tmux pane output in real-time to the dashboard or CLI. ║
|
|
6
6
|
# ║ Captures output periodically, tags by agent/team, supports replay. ║
|
|
7
7
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
8
|
-
VERSION="
|
|
8
|
+
VERSION="3.0.0"
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
11
11
|
|
|
@@ -35,16 +35,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
35
35
|
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
36
36
|
}
|
|
37
37
|
fi
|
|
38
|
-
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
39
|
-
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
40
|
-
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
41
|
-
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
42
|
-
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
43
|
-
RED="${RED:-\033[38;2;248;113;113m}"
|
|
44
|
-
DIM="${DIM:-\033[2m}"
|
|
45
|
-
BOLD="${BOLD:-\033[1m}"
|
|
46
|
-
RESET="${RESET:-\033[0m}"
|
|
47
|
-
|
|
48
38
|
# ─── Stream configuration ─────────────────────────────────────────────────────
|
|
49
39
|
STREAM_CONFIG="${HOME}/.shipwright/stream-config.json"
|
|
50
40
|
STREAM_DIR="${HOME}/.shipwright/streams"
|
package/scripts/sw-swarm.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="
|
|
9
|
+
VERSION="3.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -33,16 +33,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
33
33
|
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
34
34
|
}
|
|
35
35
|
fi
|
|
36
|
-
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
37
|
-
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
38
|
-
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
39
|
-
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
40
|
-
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
41
|
-
RED="${RED:-\033[38;2;248;113;113m}"
|
|
42
|
-
DIM="${DIM:-\033[2m}"
|
|
43
|
-
BOLD="${BOLD:-\033[1m}"
|
|
44
|
-
RESET="${RESET:-\033[0m}"
|
|
45
|
-
|
|
46
36
|
# ─── Constants ──────────────────────────────────────────────────────────────
|
|
47
37
|
SWARM_DIR="${HOME}/.shipwright/swarm"
|
|
48
38
|
REGISTRY_FILE="${SWARM_DIR}/registry.json"
|
|
@@ -197,12 +187,12 @@ cmd_spawn() {
|
|
|
197
187
|
mv "$tmp_file" "$REGISTRY_FILE" || { rm -f "$tmp_file"; error "Failed to update registry"; return 1; }
|
|
198
188
|
record_metric "$agent_id" "spawn" "1" "$agent_type"
|
|
199
189
|
|
|
200
|
-
# Create real tmux session for the agent (
|
|
201
|
-
if command -v tmux
|
|
190
|
+
# Create real tmux session for the agent (run actual daemon)
|
|
191
|
+
if command -v tmux >/dev/null 2>&1; then
|
|
202
192
|
local session_name="swarm-${agent_id}"
|
|
203
193
|
if ! tmux has-session -t "$session_name" 2>/dev/null; then
|
|
204
194
|
tmux new-session -d -s "$session_name" -c "$REPO_DIR" \
|
|
205
|
-
"
|
|
195
|
+
"SW_AGENT_ROLE=builder bash scripts/sw-daemon.sh start --role builder 2>&1 | tee /tmp/sw-swarm-${agent_id}.log" 2>/dev/null && \
|
|
206
196
|
info "Tmux session created: $session_name" || warn "Tmux session creation failed (agent still in registry)"
|
|
207
197
|
fi
|
|
208
198
|
fi
|
|
@@ -250,7 +240,7 @@ cmd_retire() {
|
|
|
250
240
|
|
|
251
241
|
# Kill real tmux session if present
|
|
252
242
|
local session_name="swarm-${agent_id}"
|
|
253
|
-
if command -v tmux
|
|
243
|
+
if command -v tmux >/dev/null 2>&1 && tmux has-session -t "$session_name" 2>/dev/null; then
|
|
254
244
|
tmux kill-session -t "$session_name" 2>/dev/null && info "Tmux session killed: $session_name" || warn "Tmux kill failed for $session_name"
|
|
255
245
|
fi
|
|
256
246
|
|
|
@@ -342,37 +332,87 @@ cmd_health() {
|
|
|
342
332
|
fi
|
|
343
333
|
}
|
|
344
334
|
|
|
335
|
+
# ─── Swarm spawn helper (for cmd_scale) ───────────────────────────────────
|
|
336
|
+
swarm_spawn_agent() {
|
|
337
|
+
local role="${1:-builder}"
|
|
338
|
+
local count="${2:-1}"
|
|
339
|
+
local i
|
|
340
|
+
for i in $(seq 1 "$count"); do
|
|
341
|
+
cmd_spawn "standard" 2>/dev/null || true
|
|
342
|
+
done
|
|
343
|
+
}
|
|
344
|
+
|
|
345
345
|
# ─── Auto-scale logic ────────────────────────────────────────────────────
|
|
346
346
|
cmd_scale() {
|
|
347
|
+
local target="${1:-auto}"
|
|
348
|
+
|
|
347
349
|
ensure_dirs
|
|
348
350
|
init_registry
|
|
349
351
|
init_config
|
|
350
352
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
+
if [[ "$target" == "auto" ]]; then
|
|
354
|
+
# Auto-scale based on queue depth
|
|
355
|
+
local queue_depth=0
|
|
356
|
+
local state_file="$HOME/.shipwright/daemon-state.json"
|
|
357
|
+
if [[ -f "$state_file" ]]; then
|
|
358
|
+
queue_depth=$(jq '.queued | length' "$state_file" 2>/dev/null || echo "0")
|
|
359
|
+
fi
|
|
353
360
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
return 0
|
|
357
|
-
fi
|
|
361
|
+
local current_agents
|
|
362
|
+
current_agents=$(tmux list-sessions -F '#{session_name}' 2>/dev/null | grep -cE '^shipwright-sw-agent|^swarm-' || echo "0")
|
|
358
363
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
364
|
+
local target_agents=1
|
|
365
|
+
if [[ "$queue_depth" -gt 5 ]]; then
|
|
366
|
+
target_agents=3
|
|
367
|
+
elif [[ "$queue_depth" -gt 2 ]]; then
|
|
368
|
+
target_agents=2
|
|
369
|
+
fi
|
|
363
370
|
|
|
364
|
-
|
|
365
|
-
|
|
371
|
+
if [[ "$current_agents" -lt "$target_agents" ]]; then
|
|
372
|
+
local to_spawn=$((target_agents - current_agents))
|
|
373
|
+
echo "Queue depth: $queue_depth, scaling up by $to_spawn"
|
|
374
|
+
swarm_spawn_agent "builder" "$to_spawn"
|
|
375
|
+
elif [[ "$current_agents" -gt "$target_agents" && "$current_agents" -gt 1 ]]; then
|
|
376
|
+
local to_retire=$((current_agents - target_agents))
|
|
377
|
+
echo "Queue depth: $queue_depth, scaling down by $to_retire"
|
|
378
|
+
# Retire oldest agents from registry (tmux sessions are managed by scale down)
|
|
379
|
+
local registry_count
|
|
380
|
+
registry_count=$(jq -r '.agents | length' "$REGISTRY_FILE" 2>/dev/null || echo "0")
|
|
381
|
+
local retired=0
|
|
382
|
+
if [[ "$registry_count" -gt 0 ]] && [[ "$to_retire" -gt 0 ]]; then
|
|
383
|
+
local agent_id
|
|
384
|
+
agent_id=$(jq -r '.agents[0].id // empty' "$REGISTRY_FILE" 2>/dev/null)
|
|
385
|
+
if [[ -n "$agent_id" ]]; then
|
|
386
|
+
cmd_retire "$agent_id" 2>/dev/null && retired=1 || true
|
|
387
|
+
fi
|
|
388
|
+
fi
|
|
389
|
+
else
|
|
390
|
+
echo "Queue depth: $queue_depth, agents: $current_agents (optimal)"
|
|
391
|
+
fi
|
|
392
|
+
else
|
|
393
|
+
# Scale to specific count - show current state
|
|
394
|
+
local auto_scale_enabled
|
|
395
|
+
auto_scale_enabled=$(jq -r '.auto_scaling_enabled' "$CONFIG_FILE")
|
|
366
396
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
397
|
+
if [[ "$auto_scale_enabled" != "true" ]]; then
|
|
398
|
+
warn "Auto-scaling is disabled"
|
|
399
|
+
fi
|
|
400
|
+
|
|
401
|
+
local min_agents max_agents target_util
|
|
402
|
+
min_agents=$(jq -r '.min_agents // 1' "$CONFIG_FILE")
|
|
403
|
+
max_agents=$(jq -r '.max_agents // 8' "$CONFIG_FILE")
|
|
404
|
+
target_util=$(jq -r '.target_utilization // 0.75' "$CONFIG_FILE")
|
|
405
|
+
|
|
406
|
+
local active_count
|
|
407
|
+
active_count=$(jq -r '.active_count // 0' "$REGISTRY_FILE")
|
|
408
|
+
|
|
409
|
+
info "Auto-Scaling Analysis"
|
|
410
|
+
echo ""
|
|
411
|
+
echo -e " Current agents: ${CYAN}${active_count}/${max_agents}${RESET}"
|
|
412
|
+
echo -e " Min agents: ${CYAN}${min_agents}${RESET}"
|
|
413
|
+
echo -e " Target utilization: ${CYAN}${target_util}${RESET}"
|
|
414
|
+
echo ""
|
|
415
|
+
fi
|
|
376
416
|
}
|
|
377
417
|
|
|
378
418
|
# ─── Performance leaderboard ──────────────────────────────────────────────
|
|
@@ -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="
|
|
9
|
+
VERSION="3.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -34,16 +34,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
34
34
|
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
35
35
|
}
|
|
36
36
|
fi
|
|
37
|
-
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
38
|
-
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
39
|
-
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
40
|
-
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
41
|
-
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
42
|
-
RED="${RED:-\033[38;2;248;113;113m}"
|
|
43
|
-
DIM="${DIM:-\033[2m}"
|
|
44
|
-
BOLD="${BOLD:-\033[1m}"
|
|
45
|
-
RESET="${RESET:-\033[0m}"
|
|
46
|
-
|
|
47
37
|
# ─── Structured Event Log ──────────────────────────────────────────────────
|
|
48
38
|
EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
|
|
49
39
|
|
|
@@ -182,7 +172,7 @@ cmd_delegate() {
|
|
|
182
172
|
result: null
|
|
183
173
|
}')
|
|
184
174
|
tasks=$(echo "$tasks" | jq ". += [$task_json]")
|
|
185
|
-
((file_count
|
|
175
|
+
file_count=$((file_count + 1))
|
|
186
176
|
done < <(echo "$changed_files")
|
|
187
177
|
|
|
188
178
|
# Output delegation result
|
|
@@ -216,7 +206,7 @@ cmd_status() {
|
|
|
216
206
|
for team_file in "$TEAM_STATE_DIR"/*.json; do
|
|
217
207
|
[[ -f "$team_file" ]] || continue
|
|
218
208
|
local ts
|
|
219
|
-
ts=$(
|
|
209
|
+
ts=$(file_mtime "$team_file")
|
|
220
210
|
local name
|
|
221
211
|
name=$(basename "$team_file" .json)
|
|
222
212
|
local status
|
|
@@ -255,7 +245,7 @@ cmd_status() {
|
|
|
255
245
|
local spec_status
|
|
256
246
|
spec_status=$(echo "$team_json" | jq -r ".specialist_status[$spec_idx] // \"pending\"" 2>/dev/null || echo "pending")
|
|
257
247
|
printf " ${DIM}%-3d${RESET} %-20s %-15s\n" "$((spec_idx + 1))" "$spec" "$spec_status"
|
|
258
|
-
((spec_idx
|
|
248
|
+
spec_idx=$((spec_idx + 1))
|
|
259
249
|
done < <(echo "$specs")
|
|
260
250
|
echo ""
|
|
261
251
|
}
|
|
@@ -292,11 +282,11 @@ cmd_vote() {
|
|
|
292
282
|
local verdict
|
|
293
283
|
verdict=$(echo "$team_json" | jq -r ".verdicts[\"$spec\"]? // \"neutral\"" 2>/dev/null || echo "neutral")
|
|
294
284
|
case "$verdict" in
|
|
295
|
-
approve) ((approve_count
|
|
296
|
-
reject) ((reject_count
|
|
297
|
-
*) ((neutral_count
|
|
285
|
+
approve) approve_count=$((approve_count + 1)) ;;
|
|
286
|
+
reject) reject_count=$((reject_count + 1)) ;;
|
|
287
|
+
*) neutral_count=$((neutral_count + 1)) ;;
|
|
298
288
|
esac
|
|
299
|
-
((total
|
|
289
|
+
total=$((total + 1))
|
|
300
290
|
done < <(echo "$specs")
|
|
301
291
|
|
|
302
292
|
# Consensus: majority vote with leader tiebreak
|
|
@@ -370,9 +360,9 @@ cmd_aggregate() {
|
|
|
370
360
|
|
|
371
361
|
if [[ -n "$result" ]]; then
|
|
372
362
|
if echo "$result" | jq -e '.success' >/dev/null 2>&1; then
|
|
373
|
-
((success_count
|
|
363
|
+
success_count=$((success_count + 1))
|
|
374
364
|
else
|
|
375
|
-
((failure_count
|
|
365
|
+
failure_count=$((failure_count + 1))
|
|
376
366
|
fi
|
|
377
367
|
results=$(echo "$results" | jq ". += [$result]")
|
|
378
368
|
fi
|