shipwright-cli 2.4.0 → 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.
Files changed (161) hide show
  1. package/README.md +16 -11
  2. package/completions/_shipwright +1 -1
  3. package/completions/shipwright.bash +3 -8
  4. package/completions/shipwright.fish +1 -1
  5. package/config/defaults.json +111 -0
  6. package/config/event-schema.json +81 -0
  7. package/config/policy.json +13 -18
  8. package/dashboard/coverage/coverage-summary.json +14 -0
  9. package/dashboard/public/index.html +1 -1
  10. package/dashboard/server.ts +306 -17
  11. package/dashboard/src/components/charts/bar.test.ts +79 -0
  12. package/dashboard/src/components/charts/donut.test.ts +68 -0
  13. package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
  14. package/dashboard/src/components/charts/sparkline.test.ts +125 -0
  15. package/dashboard/src/core/api.test.ts +309 -0
  16. package/dashboard/src/core/helpers.test.ts +301 -0
  17. package/dashboard/src/core/router.test.ts +307 -0
  18. package/dashboard/src/core/router.ts +7 -0
  19. package/dashboard/src/core/sse.test.ts +144 -0
  20. package/dashboard/src/views/metrics.test.ts +186 -0
  21. package/dashboard/src/views/overview.test.ts +173 -0
  22. package/dashboard/src/views/pipelines.test.ts +183 -0
  23. package/dashboard/src/views/team.test.ts +253 -0
  24. package/dashboard/vitest.config.ts +14 -5
  25. package/docs/TIPS.md +1 -1
  26. package/docs/patterns/README.md +1 -1
  27. package/package.json +5 -7
  28. package/scripts/adapters/docker-deploy.sh +1 -1
  29. package/scripts/adapters/tmux-adapter.sh +11 -1
  30. package/scripts/adapters/wezterm-adapter.sh +1 -1
  31. package/scripts/check-version-consistency.sh +1 -1
  32. package/scripts/lib/architecture.sh +126 -0
  33. package/scripts/lib/bootstrap.sh +75 -0
  34. package/scripts/lib/compat.sh +89 -6
  35. package/scripts/lib/config.sh +91 -0
  36. package/scripts/lib/daemon-adaptive.sh +3 -3
  37. package/scripts/lib/daemon-dispatch.sh +39 -16
  38. package/scripts/lib/daemon-health.sh +1 -1
  39. package/scripts/lib/daemon-patrol.sh +24 -12
  40. package/scripts/lib/daemon-poll.sh +37 -25
  41. package/scripts/lib/daemon-state.sh +115 -23
  42. package/scripts/lib/daemon-triage.sh +30 -8
  43. package/scripts/lib/fleet-failover.sh +63 -0
  44. package/scripts/lib/helpers.sh +30 -6
  45. package/scripts/lib/pipeline-detection.sh +2 -2
  46. package/scripts/lib/pipeline-github.sh +9 -9
  47. package/scripts/lib/pipeline-intelligence.sh +85 -35
  48. package/scripts/lib/pipeline-quality-checks.sh +16 -16
  49. package/scripts/lib/pipeline-quality.sh +1 -1
  50. package/scripts/lib/pipeline-stages.sh +242 -28
  51. package/scripts/lib/pipeline-state.sh +40 -4
  52. package/scripts/lib/test-helpers.sh +247 -0
  53. package/scripts/postinstall.mjs +3 -11
  54. package/scripts/sw +10 -4
  55. package/scripts/sw-activity.sh +1 -11
  56. package/scripts/sw-adaptive.sh +109 -85
  57. package/scripts/sw-adversarial.sh +4 -14
  58. package/scripts/sw-architecture-enforcer.sh +1 -11
  59. package/scripts/sw-auth.sh +8 -17
  60. package/scripts/sw-autonomous.sh +111 -49
  61. package/scripts/sw-changelog.sh +1 -11
  62. package/scripts/sw-checkpoint.sh +144 -20
  63. package/scripts/sw-ci.sh +2 -12
  64. package/scripts/sw-cleanup.sh +13 -17
  65. package/scripts/sw-code-review.sh +16 -36
  66. package/scripts/sw-connect.sh +5 -12
  67. package/scripts/sw-context.sh +9 -26
  68. package/scripts/sw-cost.sh +6 -16
  69. package/scripts/sw-daemon.sh +75 -70
  70. package/scripts/sw-dashboard.sh +57 -17
  71. package/scripts/sw-db.sh +506 -15
  72. package/scripts/sw-decompose.sh +1 -11
  73. package/scripts/sw-deps.sh +15 -25
  74. package/scripts/sw-developer-simulation.sh +1 -11
  75. package/scripts/sw-discovery.sh +112 -30
  76. package/scripts/sw-doc-fleet.sh +7 -17
  77. package/scripts/sw-docs-agent.sh +6 -16
  78. package/scripts/sw-docs.sh +4 -12
  79. package/scripts/sw-doctor.sh +134 -43
  80. package/scripts/sw-dora.sh +11 -19
  81. package/scripts/sw-durable.sh +35 -52
  82. package/scripts/sw-e2e-orchestrator.sh +11 -27
  83. package/scripts/sw-eventbus.sh +115 -115
  84. package/scripts/sw-evidence.sh +114 -30
  85. package/scripts/sw-feedback.sh +3 -13
  86. package/scripts/sw-fix.sh +2 -20
  87. package/scripts/sw-fleet-discover.sh +1 -11
  88. package/scripts/sw-fleet-viz.sh +10 -18
  89. package/scripts/sw-fleet.sh +13 -17
  90. package/scripts/sw-github-app.sh +6 -16
  91. package/scripts/sw-github-checks.sh +1 -11
  92. package/scripts/sw-github-deploy.sh +1 -11
  93. package/scripts/sw-github-graphql.sh +2 -12
  94. package/scripts/sw-guild.sh +1 -11
  95. package/scripts/sw-heartbeat.sh +49 -12
  96. package/scripts/sw-hygiene.sh +45 -43
  97. package/scripts/sw-incident.sh +48 -74
  98. package/scripts/sw-init.sh +35 -37
  99. package/scripts/sw-instrument.sh +1 -11
  100. package/scripts/sw-intelligence.sh +362 -51
  101. package/scripts/sw-jira.sh +5 -14
  102. package/scripts/sw-launchd.sh +2 -12
  103. package/scripts/sw-linear.sh +8 -17
  104. package/scripts/sw-logs.sh +4 -12
  105. package/scripts/sw-loop.sh +641 -90
  106. package/scripts/sw-memory.sh +243 -17
  107. package/scripts/sw-mission-control.sh +2 -12
  108. package/scripts/sw-model-router.sh +73 -34
  109. package/scripts/sw-otel.sh +11 -21
  110. package/scripts/sw-oversight.sh +1 -11
  111. package/scripts/sw-patrol-meta.sh +5 -11
  112. package/scripts/sw-pipeline-composer.sh +7 -17
  113. package/scripts/sw-pipeline-vitals.sh +1 -11
  114. package/scripts/sw-pipeline.sh +478 -122
  115. package/scripts/sw-pm.sh +2 -12
  116. package/scripts/sw-pr-lifecycle.sh +27 -25
  117. package/scripts/sw-predictive.sh +16 -22
  118. package/scripts/sw-prep.sh +6 -16
  119. package/scripts/sw-ps.sh +1 -11
  120. package/scripts/sw-public-dashboard.sh +2 -12
  121. package/scripts/sw-quality.sh +77 -10
  122. package/scripts/sw-reaper.sh +1 -11
  123. package/scripts/sw-recruit.sh +15 -25
  124. package/scripts/sw-regression.sh +11 -21
  125. package/scripts/sw-release-manager.sh +19 -28
  126. package/scripts/sw-release.sh +8 -16
  127. package/scripts/sw-remote.sh +1 -11
  128. package/scripts/sw-replay.sh +48 -44
  129. package/scripts/sw-retro.sh +70 -92
  130. package/scripts/sw-review-rerun.sh +1 -1
  131. package/scripts/sw-scale.sh +109 -32
  132. package/scripts/sw-security-audit.sh +12 -22
  133. package/scripts/sw-self-optimize.sh +239 -23
  134. package/scripts/sw-session.sh +3 -13
  135. package/scripts/sw-setup.sh +8 -18
  136. package/scripts/sw-standup.sh +5 -15
  137. package/scripts/sw-status.sh +32 -23
  138. package/scripts/sw-strategic.sh +129 -13
  139. package/scripts/sw-stream.sh +1 -11
  140. package/scripts/sw-swarm.sh +76 -36
  141. package/scripts/sw-team-stages.sh +10 -20
  142. package/scripts/sw-templates.sh +4 -14
  143. package/scripts/sw-testgen.sh +3 -13
  144. package/scripts/sw-tmux-pipeline.sh +1 -19
  145. package/scripts/sw-tmux-role-color.sh +0 -10
  146. package/scripts/sw-tmux-status.sh +3 -11
  147. package/scripts/sw-tmux.sh +2 -20
  148. package/scripts/sw-trace.sh +1 -19
  149. package/scripts/sw-tracker-github.sh +0 -10
  150. package/scripts/sw-tracker-jira.sh +1 -11
  151. package/scripts/sw-tracker-linear.sh +1 -11
  152. package/scripts/sw-tracker.sh +7 -24
  153. package/scripts/sw-triage.sh +24 -34
  154. package/scripts/sw-upgrade.sh +5 -23
  155. package/scripts/sw-ux.sh +1 -19
  156. package/scripts/sw-webhook.sh +18 -32
  157. package/scripts/sw-widgets.sh +3 -21
  158. package/scripts/sw-worktree.sh +11 -27
  159. package/scripts/update-homebrew-sha.sh +67 -0
  160. package/templates/pipelines/tdd.json +72 -0
  161. package/scripts/sw-pipeline.sh.mock +0 -7
@@ -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="2.4.0"
11
+ VERSION="3.0.0"
12
12
  set -euo pipefail
13
13
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
14
14
 
@@ -38,16 +38,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
38
38
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
39
39
  }
40
40
  fi
41
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
42
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
43
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
44
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
45
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
46
- RED="${RED:-\033[38;2;248;113;113m}"
47
- DIM="${DIM:-\033[2m}"
48
- BOLD="${BOLD:-\033[1m}"
49
- RESET="${RESET:-\033[0m}"
50
-
51
41
  # ─── Parse Arguments ────────────────────────────────────────────────────────
52
42
 
53
43
  TEAM_NAME=""
@@ -207,7 +197,7 @@ if [[ -n "$TEMPLATE_NAME" ]]; then
207
197
  info "Loading template: ${PURPLE}${BOLD}${TEMPLATE_NAME}${RESET}"
208
198
 
209
199
  # Parse template — single jq call extracts all fields + agents in one pass
210
- if command -v jq &>/dev/null; then
200
+ if command -v jq >/dev/null 2>&1; then
211
201
  # Single jq call: outputs metadata lines then agent lines
212
202
  # Format: META<tab>field<tab>value for metadata, AGENT<tab>name|role|focus for agents
213
203
  while IFS=$'\t' read -r tag key value; do
@@ -508,7 +498,7 @@ LAUNCHER_STATIC
508
498
  tmux select-pane -t "$WINDOW_NAME" -P 'bg=#1a1a2e,fg=#e4e4e7' 2>/dev/null || true
509
499
  } &
510
500
 
511
- elif [[ -f "$ADAPTER_FILE" ]] && type -t spawn_agent &>/dev/null; then
501
+ elif [[ -f "$ADAPTER_FILE" ]] && type -t spawn_agent >/dev/null 2>&1; then
512
502
  # ─── Non-tmux adapter session (iterm2, wezterm, etc.) ──────────────────
513
503
  info "Creating team session: ${CYAN}${BOLD}${TEAM_NAME}${RESET} ${DIM}(${TERMINAL_ADAPTER})${RESET}"
514
504
 
@@ -10,7 +10,7 @@
10
10
  set -euo pipefail
11
11
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
12
12
 
13
- VERSION="2.4.0"
13
+ VERSION="3.0.0"
14
14
 
15
15
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
16
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
@@ -39,16 +39,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
39
39
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
40
40
  }
41
41
  fi
42
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
43
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
44
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
45
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
46
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
47
- RED="${RED:-\033[38;2;248;113;113m}"
48
- DIM="${DIM:-\033[2m}"
49
- BOLD="${BOLD:-\033[1m}"
50
- RESET="${RESET:-\033[0m}"
51
-
52
42
  PASS=0
53
43
  WARN=0
54
44
  FAIL=0
@@ -128,7 +118,7 @@ OPTIONAL_TOOLS=("bun")
128
118
  for tool in "${REQUIRED_TOOLS[@]}"; do
129
119
  case "$tool" in
130
120
  tmux)
131
- if command -v tmux &>/dev/null; then
121
+ if command -v tmux >/dev/null 2>&1; then
132
122
  TMUX_VERSION="$(tmux -V | grep -oE '[0-9]+\.[0-9a-z]+')"
133
123
  TMUX_MAJOR="$(echo "$TMUX_VERSION" | cut -d. -f1)"
134
124
  TMUX_MINOR="$(echo "$TMUX_VERSION" | cut -d. -f2 | tr -dc '0-9')"
@@ -153,7 +143,7 @@ for tool in "${REQUIRED_TOOLS[@]}"; do
153
143
  fi
154
144
  ;;
155
145
  git)
156
- if command -v git &>/dev/null; then
146
+ if command -v git >/dev/null 2>&1; then
157
147
  check_pass "git $(git --version | awk '{print $3}')"
158
148
  else
159
149
  check_fail "git not installed"
@@ -161,7 +151,7 @@ for tool in "${REQUIRED_TOOLS[@]}"; do
161
151
  fi
162
152
  ;;
163
153
  jq)
164
- if command -v jq &>/dev/null; then
154
+ if command -v jq >/dev/null 2>&1; then
165
155
  check_pass "jq $(jq --version 2>&1 | tr -d 'jq-')"
166
156
  else
167
157
  check_fail "jq not installed"
@@ -169,8 +159,8 @@ for tool in "${REQUIRED_TOOLS[@]}"; do
169
159
  fi
170
160
  ;;
171
161
  gh)
172
- if command -v gh &>/dev/null; then
173
- if gh auth status &>/dev/null 2>&1; then
162
+ if command -v gh >/dev/null 2>&1; then
163
+ if gh auth status >/dev/null 2>&1; then
174
164
  GH_USER="$(gh api user -q .login 2>/dev/null || echo "authenticated")"
175
165
  check_pass "GitHub CLI: ${GH_USER}"
176
166
  else
@@ -183,7 +173,7 @@ for tool in "${REQUIRED_TOOLS[@]}"; do
183
173
  fi
184
174
  ;;
185
175
  claude)
186
- if command -v claude &>/dev/null; then
176
+ if command -v claude >/dev/null 2>&1; then
187
177
  check_pass "Claude Code CLI"
188
178
  else
189
179
  check_fail "Claude Code CLI not found"
@@ -199,7 +189,7 @@ echo ""
199
189
  for tool in "${OPTIONAL_TOOLS[@]}"; do
200
190
  case "$tool" in
201
191
  bun)
202
- if command -v bun &>/dev/null; then
192
+ if command -v bun >/dev/null 2>&1; then
203
193
  check_pass "Bun (dashboard server)"
204
194
  else
205
195
  check_warn "Bun not installed (optional — for dashboard)"
@@ -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="2.4.0"
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
  # ─── Constants ──────────────────────────────────────────────────────────────
48
38
  STANDUP_DIR="${HOME}/.shipwright/standups"
49
39
  EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
@@ -62,7 +52,7 @@ ensure_dirs() {
62
52
  # Convert ISO 8601 timestamp to epoch seconds (works on macOS and Linux)
63
53
  iso_to_epoch() {
64
54
  local iso="$1"
65
- if TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$iso" +%s &>/dev/null 2>&1; then
55
+ if TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$iso" +%s >/dev/null 2>&1; then
66
56
  TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$iso" +%s 2>/dev/null || echo 0
67
57
  else
68
58
  date -d "$iso" +%s 2>/dev/null || echo 0
@@ -533,9 +523,9 @@ cmd_notify() {
533
523
  ]
534
524
  }')
535
525
 
536
- if command -v curl &>/dev/null; then
537
- if curl -s -X POST -H 'Content-type: application/json' \
538
- --data "$payload" "$webhook_url" &>/dev/null; then
526
+ if command -v curl >/dev/null 2>&1; then
527
+ if curl -s --connect-timeout 10 --max-time 30 -X POST -H 'Content-type: application/json' \
528
+ --data "$payload" "$webhook_url" >/dev/null 2>&1; then
539
529
  success "Standup delivered to webhook"
540
530
  else
541
531
  error "Failed to deliver standup to webhook"
@@ -4,7 +4,7 @@
4
4
  # ║ ║
5
5
  # ║ Shows running teams, agent windows, and task progress. ║
6
6
  # ╚═══════════════════════════════════════════════════════════════════════════╝
7
- VERSION="2.4.0"
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 &>/dev/null; then
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 &>/dev/null; then
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 &>/dev/null; then
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 &>/dev/null && [[ -f "$_db_file" ]]; then
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=$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$started_at" +%s 2>/dev/null || echo 0)
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=$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$a_started" +%s 2>/dev/null || echo 0)
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=$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$evt_ts" +"%H:%M" 2>/dev/null || echo "")
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
- local_job_id="$(basename "$hb_file" .json)"
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=$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$hb_updated" +%s 2>/dev/null || echo 0)
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}${local_job_id}${RESET} ${DIM}pid:${hb_pid}${RESET}"
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 &>/dev/null && [[ -f "$_DB_FILE" ]]; then
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 &>/dev/null && command -v jq &>/dev/null; then
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:8767"
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)
@@ -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="2.4.0"
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
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
38
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
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
-
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 &>/dev/null 2>&1; then
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 &>/dev/null; then
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 Show last run stats and cooldown"
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"
@@ -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="2.4.0"
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"