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
@@ -4,7 +4,7 @@
4
4
  # ║ ║
5
5
  # ║ Checks prerequisites, installed files, PATH, and common issues. ║
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
 
@@ -32,25 +32,17 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
32
32
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
33
33
  }
34
34
  fi
35
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
36
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
37
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
38
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
39
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
40
- RED="${RED:-\033[38;2;248;113;113m}"
41
- DIM="${DIM:-\033[2m}"
42
- BOLD="${BOLD:-\033[1m}"
43
- RESET="${RESET:-\033[0m}"
44
-
45
35
  PASS=0
46
36
  WARN=0
47
37
  FAIL=0
48
38
  SKIP_PLATFORM_SCAN=false
49
39
 
50
40
  # Parse doctor flags
41
+ INTELLIGENCE_ONLY=false
51
42
  for _arg in "$@"; do
52
43
  case "$_arg" in
53
44
  --skip-platform-scan) SKIP_PLATFORM_SCAN=true ;;
45
+ --intelligence) INTELLIGENCE_ONLY=true ;;
54
46
  --version|-V) echo "sw-doctor $VERSION"; exit 0 ;;
55
47
  esac
56
48
  done
@@ -66,6 +58,99 @@ echo -e "${DIM} $(date '+%Y-%m-%d %H:%M:%S')${RESET}"
66
58
  echo -e "${DIM} ══════════════════════════════════════════${RESET}"
67
59
  echo ""
68
60
 
61
+ # ─── Intelligence-only mode: run only INTELLIGENCE FEATURES section ─────────
62
+ doctor_check_intelligence() {
63
+ echo -e "${PURPLE}${BOLD} INTELLIGENCE FEATURES${RESET}"
64
+ echo -e "${DIM} ──────────────────────────────────────────${RESET}"
65
+
66
+ # Claude CLI available and authenticated
67
+ if command -v claude >/dev/null 2>&1; then
68
+ if claude --version >/dev/null 2>&1; then
69
+ check_pass "Claude CLI: available and authenticated"
70
+ else
71
+ check_warn "Claude CLI: installed but may need authentication"
72
+ echo -e " ${DIM}Run: claude auth login${RESET}"
73
+ fi
74
+ else
75
+ check_fail "Claude CLI: not found"
76
+ echo -e " ${DIM}npm install -g @anthropic-ai/claude-code${RESET}"
77
+ fi
78
+
79
+ # intelligence.enabled from daemon-config
80
+ DAEMON_CFG=""
81
+ REPO_ROOT_DOC="$(cd "$SCRIPT_DIR/.." 2>/dev/null && pwd)"
82
+ for cfg in "$(pwd)/.claude/daemon-config.json" "$REPO_ROOT_DOC/.claude/daemon-config.json" "$(git rev-parse --show-toplevel 2>/dev/null)/.claude/daemon-config.json" "$HOME/.claude/daemon-config.json"; do
83
+ [[ -n "$cfg" && -f "$cfg" ]] && DAEMON_CFG="$cfg" && break
84
+ done
85
+ if [[ -n "$DAEMON_CFG" && -f "$DAEMON_CFG" ]]; then
86
+ intel_enabled=$(jq -r '.intelligence.enabled // "auto"' "$DAEMON_CFG" 2>/dev/null || echo "auto")
87
+ composer_enabled=$(jq -r '.intelligence.composer_enabled // "auto"' "$DAEMON_CFG" 2>/dev/null || echo "auto")
88
+ if [[ "$intel_enabled" == "true" ]]; then
89
+ check_pass "intelligence.enabled: true"
90
+ elif [[ "$intel_enabled" == "auto" ]]; then
91
+ if command -v claude >/dev/null 2>&1; then
92
+ check_pass "intelligence.enabled: auto (resolved: enabled)"
93
+ else
94
+ check_warn "intelligence.enabled: auto (resolved: disabled — Claude not found)"
95
+ fi
96
+ else
97
+ check_warn "intelligence.enabled: false"
98
+ fi
99
+ if [[ "$composer_enabled" == "true" ]]; then
100
+ check_pass "composer: enabled"
101
+ elif [[ "$composer_enabled" == "auto" ]]; then
102
+ if command -v claude >/dev/null 2>&1; then
103
+ check_pass "composer: auto (resolved: enabled)"
104
+ else
105
+ check_warn "composer: auto (resolved: disabled)"
106
+ fi
107
+ else
108
+ check_warn "composer: disabled"
109
+ fi
110
+ else
111
+ check_warn "daemon-config.json not found — intelligence defaults to auto"
112
+ echo -e " ${DIM}Run: shipwright daemon init${RESET}"
113
+ fi
114
+
115
+ # Adaptive model (has training data)
116
+ ADAPTIVE_MODEL="${HOME}/.shipwright/adaptive-models.json"
117
+ if [[ -f "$ADAPTIVE_MODEL" ]]; then
118
+ sample_count=$(jq '(.models // []) | map(.samples // 0) | add // 0' "$ADAPTIVE_MODEL" 2>/dev/null || echo "0")
119
+ if [[ "${sample_count:-0}" -gt 0 ]]; then
120
+ check_pass "Adaptive model: trained (${sample_count} samples)"
121
+ else
122
+ check_warn "Adaptive model: exists but no training data"
123
+ fi
124
+ else
125
+ check_warn "Adaptive model: not found"
126
+ echo -e " ${DIM}Run pipelines to accumulate training data${RESET}"
127
+ fi
128
+
129
+ # Predictive baselines
130
+ BASELINES_DIR="${HOME}/.shipwright/baselines"
131
+ if [[ -d "$BASELINES_DIR" ]]; then
132
+ baseline_count=$(find "$BASELINES_DIR" -name "*.json" -type f 2>/dev/null | wc -l | tr -d ' ')
133
+ if [[ "${baseline_count:-0}" -gt 0 ]]; then
134
+ check_pass "Predictive baselines: ${baseline_count} file(s)"
135
+ else
136
+ check_warn "Predictive baselines: directory exists but no baseline files"
137
+ fi
138
+ else
139
+ check_warn "Predictive baselines: not found"
140
+ echo -e " ${DIM}Run pipelines to build baselines${RESET}"
141
+ fi
142
+ }
143
+
144
+ if [[ "$INTELLIGENCE_ONLY" == "true" ]]; then
145
+ doctor_check_intelligence
146
+ echo ""
147
+ echo -e "${DIM} ══════════════════════════════════════════${RESET}"
148
+ echo ""
149
+ echo -e " ${GREEN}${BOLD}${PASS}${RESET} passed ${YELLOW}${BOLD}${WARN}${RESET} warnings ${RED}${BOLD}${FAIL}${RESET} failed ${DIM}($((PASS + WARN + FAIL)) checks)${RESET}"
150
+ echo ""
151
+ exit 0
152
+ fi
153
+
69
154
  # ═════════════════════════════════════════════════════════════════════════════
70
155
  # 1. Prerequisites
71
156
  # ═════════════════════════════════════════════════════════════════════════════
@@ -73,7 +158,7 @@ echo -e "${PURPLE}${BOLD} PREREQUISITES${RESET}"
73
158
  echo -e "${DIM} ──────────────────────────────────────────${RESET}"
74
159
 
75
160
  # tmux
76
- if command -v tmux &>/dev/null; then
161
+ if command -v tmux >/dev/null 2>&1; then
77
162
  TMUX_VERSION="$(tmux -V | grep -oE '[0-9]+\.[0-9a-z]+')"
78
163
  TMUX_MAJOR="$(echo "$TMUX_VERSION" | cut -d. -f1)"
79
164
  TMUX_MINOR="$(echo "$TMUX_VERSION" | cut -d. -f2 | tr -dc '0-9')"
@@ -98,7 +183,7 @@ else
98
183
  fi
99
184
 
100
185
  # jq
101
- if command -v jq &>/dev/null; then
186
+ if command -v jq >/dev/null 2>&1; then
102
187
  check_pass "jq $(jq --version 2>&1 | tr -d 'jq-')"
103
188
  else
104
189
  check_fail "jq not installed — required for template parsing"
@@ -107,7 +192,7 @@ else
107
192
  fi
108
193
 
109
194
  # Claude Code CLI
110
- if command -v claude &>/dev/null; then
195
+ if command -v claude >/dev/null 2>&1; then
111
196
  check_pass "Claude Code CLI found"
112
197
  else
113
198
  check_fail "Claude Code CLI not found"
@@ -115,7 +200,7 @@ else
115
200
  fi
116
201
 
117
202
  # Node.js
118
- if command -v node &>/dev/null; then
203
+ if command -v node >/dev/null 2>&1; then
119
204
  NODE_VERSION="$(node -v | tr -d 'v')"
120
205
  NODE_MAJOR="$(echo "$NODE_VERSION" | cut -d. -f1)"
121
206
  if [[ "$NODE_MAJOR" -ge 20 ]]; then
@@ -128,7 +213,7 @@ else
128
213
  fi
129
214
 
130
215
  # Git
131
- if command -v git &>/dev/null; then
216
+ if command -v git >/dev/null 2>&1; then
132
217
  check_pass "git $(git --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')"
133
218
  else
134
219
  check_fail "git not found"
@@ -207,7 +292,7 @@ else
207
292
  fi
208
293
 
209
294
  # Hook wiring validation — check hooks are configured in settings.json
210
- if [[ -d "$HOOKS_DIR" && -f "$HOME/.claude/settings.json" ]] && jq -e '.' "$HOME/.claude/settings.json" &>/dev/null; then
295
+ if [[ -d "$HOOKS_DIR" && -f "$HOME/.claude/settings.json" ]] && jq -e '.' "$HOME/.claude/settings.json" >/dev/null 2>&1; then
211
296
  wired=0 unwired=0 hook_total_check=0
212
297
  # Colon-separated pairs: filename:EventName (Bash 3.2 compatible)
213
298
  for pair in \
@@ -221,7 +306,7 @@ if [[ -d "$HOOKS_DIR" && -f "$HOME/.claude/settings.json" ]] && jq -e '.' "$HOME
221
306
  # Only check hooks that are actually installed
222
307
  [[ -f "$HOOKS_DIR/$hfile" ]] || continue
223
308
  hook_total_check=$((hook_total_check + 1))
224
- if jq -e ".hooks.${hevent}" "$HOME/.claude/settings.json" &>/dev/null; then
309
+ if jq -e ".hooks.${hevent}" "$HOME/.claude/settings.json" >/dev/null 2>&1; then
225
310
  wired=$((wired + 1))
226
311
  else
227
312
  unwired=$((unwired + 1))
@@ -307,8 +392,8 @@ else
307
392
  fi
308
393
 
309
394
  # GitHub CLI
310
- if command -v gh &>/dev/null; then
311
- if gh auth status &>/dev/null; then
395
+ if command -v gh >/dev/null 2>&1; then
396
+ if gh auth status >/dev/null 2>&1; then
312
397
  GH_USER="$(gh api user -q .login 2>/dev/null || echo "authenticated")"
313
398
  check_pass "GitHub CLI: ${GH_USER}"
314
399
  else
@@ -339,7 +424,7 @@ else
339
424
  fi
340
425
 
341
426
  # Check sw subcommands are installed alongside the router
342
- if command -v sw &>/dev/null; then
427
+ if command -v sw >/dev/null 2>&1; then
343
428
  SW_DIR="$(dirname "$(command -v sw)")"
344
429
  # Follow symlinks to find the actual scripts directory
345
430
  _sw_path="$(command -v sw)"
@@ -373,7 +458,7 @@ fi
373
458
  # ═════════════════════════════════════════════════════════════════════════════
374
459
  REPO_ROOT_DOC="$(cd "$SCRIPT_DIR/.." 2>/dev/null && pwd)"
375
460
  if [[ -n "$REPO_ROOT_DOC" && -f "$REPO_ROOT_DOC/package.json" ]] && \
376
- command -v jq &>/dev/null && \
461
+ command -v jq >/dev/null 2>&1 && \
377
462
  [[ "$(jq -r '.name // ""' "$REPO_ROOT_DOC/package.json" 2>/dev/null)" == "shipwright-cli" ]]; then
378
463
  echo ""
379
464
  echo -e "${PURPLE}${BOLD} VERSION CONSISTENCY${RESET} ${DIM}(Shipwright repo)${RESET}"
@@ -513,7 +598,7 @@ for dir in "${EXPECTED_DIRS[@]}"; do
513
598
  done
514
599
 
515
600
  # JSON validation for templates
516
- if command -v jq &>/dev/null; then
601
+ if command -v jq >/dev/null 2>&1; then
517
602
  json_errors=0
518
603
  json_total=0
519
604
  for tpl_dir in "$HOME/.shipwright/templates" "$HOME/.shipwright/pipelines"; do
@@ -521,7 +606,7 @@ if command -v jq &>/dev/null; then
521
606
  while IFS= read -r json_file; do
522
607
  [[ -z "$json_file" ]] && continue
523
608
  json_total=$((json_total + 1))
524
- if ! jq -e . "$json_file" &>/dev/null; then
609
+ if ! jq -e . "$json_file" >/dev/null 2>&1; then
525
610
  check_fail "Invalid JSON: ${json_file/#$HOME/\~}"
526
611
  json_errors=$((json_errors + 1))
527
612
  fi
@@ -631,7 +716,7 @@ case "$TERM_PROGRAM" in
631
716
  esac
632
717
 
633
718
  # Check mouse window clicking (tmux 3.4+ changed the default)
634
- if command -v tmux &>/dev/null && [[ -n "${TMUX:-}" ]]; then
719
+ if command -v tmux >/dev/null 2>&1 && [[ -n "${TMUX:-}" ]]; then
635
720
  MOUSE_BIND="$(tmux list-keys 2>/dev/null | grep 'MouseDown1Status' | head -1 || true)"
636
721
  if echo "$MOUSE_BIND" | grep -q 'select-window'; then
637
722
  check_pass "Mouse window click: select-window (correct)"
@@ -758,7 +843,7 @@ if [[ -f "$MACHINES_FILE" ]]; then
758
843
  if [[ "$machine_count" -gt 0 ]]; then
759
844
  check_pass "Registered machines: ${machine_count}"
760
845
  # Check SSH connectivity (quick check, 5s timeout per machine)
761
- if command -v ssh &>/dev/null; then
846
+ if command -v ssh >/dev/null 2>&1; then
762
847
  while IFS= read -r machine; do
763
848
  [[ -z "$machine" ]] && continue
764
849
  m_name=$(echo "$machine" | jq -r '.name // ""')
@@ -812,7 +897,7 @@ fi
812
897
  # Check team config
813
898
  TEAM_CONFIG="$HOME/.shipwright/team-config.json"
814
899
  if [[ -f "$TEAM_CONFIG" ]]; then
815
- if jq -e . "$TEAM_CONFIG" &>/dev/null; then
900
+ if jq -e . "$TEAM_CONFIG" >/dev/null 2>&1; then
816
901
  check_pass "Team config: valid JSON"
817
902
 
818
903
  # Check dashboard_url field
@@ -821,8 +906,8 @@ if [[ -f "$TEAM_CONFIG" ]]; then
821
906
  check_pass "Dashboard URL: configured"
822
907
 
823
908
  # Try to reach dashboard with 3s timeout
824
- if command -v curl &>/dev/null; then
825
- if curl -s -m 3 "${DASHBOARD_URL}/api/health" &>/dev/null; then
909
+ if command -v curl >/dev/null 2>&1; then
910
+ if curl -s -m 3 "${DASHBOARD_URL}/api/health" >/dev/null 2>&1; then
826
911
  check_pass "Dashboard reachable: ${DASHBOARD_URL}"
827
912
  else
828
913
  check_warn "Dashboard unreachable: ${DASHBOARD_URL}"
@@ -845,7 +930,7 @@ fi
845
930
  # Check developer registry
846
931
  DEVELOPER_REGISTRY="$HOME/.shipwright/developer-registry.json"
847
932
  if [[ -f "$DEVELOPER_REGISTRY" ]]; then
848
- if jq -e . "$DEVELOPER_REGISTRY" &>/dev/null; then
933
+ if jq -e . "$DEVELOPER_REGISTRY" >/dev/null 2>&1; then
849
934
  check_pass "Developer registry: exists and valid"
850
935
  else
851
936
  check_fail "Developer registry: invalid JSON"
@@ -862,8 +947,8 @@ echo ""
862
947
  echo -e "${PURPLE}${BOLD} GITHUB INTEGRATION${RESET}"
863
948
  echo -e "${DIM} ──────────────────────────────────────────${RESET}"
864
949
 
865
- if command -v gh &>/dev/null; then
866
- if gh auth status &>/dev/null 2>&1; then
950
+ if command -v gh >/dev/null 2>&1; then
951
+ if gh auth status >/dev/null 2>&1; then
867
952
  check_pass "gh CLI authenticated"
868
953
 
869
954
  # Check required scopes
@@ -885,7 +970,7 @@ if command -v gh &>/dev/null; then
885
970
  fi
886
971
 
887
972
  # Check GraphQL endpoint
888
- if gh api graphql -f query='{viewer{login}}' &>/dev/null 2>&1; then
973
+ if gh api graphql -f query='{viewer{login}}' >/dev/null 2>&1; then
889
974
  check_pass "GraphQL API accessible"
890
975
  else
891
976
  check_warn "GraphQL API not accessible — intelligence enrichment will use fallbacks"
@@ -897,7 +982,7 @@ if command -v gh &>/dev/null; then
897
982
  dr_repo_owner=$(git remote get-url origin 2>/dev/null | sed -E 's#.*[:/]([^/]+)/[^/]+(\.git)?$#\1#' || echo "")
898
983
  dr_repo_name=$(git remote get-url origin 2>/dev/null | sed -E 's#.*/([^/]+)(\.git)?$#\1#' || echo "")
899
984
  if [[ -n "$dr_repo_owner" && -n "$dr_repo_name" ]]; then
900
- if gh api "repos/$dr_repo_owner/$dr_repo_name/code-scanning/alerts?per_page=1" &>/dev/null 2>&1; then
985
+ if gh api "repos/$dr_repo_owner/$dr_repo_name/code-scanning/alerts?per_page=1" >/dev/null 2>&1; then
901
986
  check_pass "Code scanning API accessible"
902
987
  else
903
988
  info " Code scanning API not available ${DIM}(may need GitHub Advanced Security)${RESET}"
@@ -944,7 +1029,7 @@ echo -e "${PURPLE}${BOLD} DASHBOARD & DEPENDENCIES${RESET}"
944
1029
  echo -e "${DIM} ──────────────────────────────────────────${RESET}"
945
1030
 
946
1031
  # Bun runtime
947
- if command -v bun &>/dev/null; then
1032
+ if command -v bun >/dev/null 2>&1; then
948
1033
  bun_ver="$(bun --version 2>/dev/null || echo "unknown")"
949
1034
  check_pass "bun $bun_ver"
950
1035
  else
@@ -976,15 +1061,15 @@ else
976
1061
  fi
977
1062
 
978
1063
  # Port 3000 availability
979
- if command -v lsof &>/dev/null; then
980
- if lsof -i :3000 -sTCP:LISTEN &>/dev/null 2>&1; then
1064
+ if command -v lsof >/dev/null 2>&1; then
1065
+ if lsof -i :3000 -sTCP:LISTEN >/dev/null 2>&1; then
981
1066
  dr_port_proc="$(lsof -i :3000 -sTCP:LISTEN -t 2>/dev/null | head -1 || echo "unknown")"
982
1067
  check_warn "Port 3000 in use (PID: $dr_port_proc) — dashboard may need a different port"
983
1068
  echo -e " ${DIM}Use: shipwright dashboard start --port 3001${RESET}"
984
1069
  else
985
1070
  check_pass "Port 3000 available"
986
1071
  fi
987
- elif command -v ss &>/dev/null; then
1072
+ elif command -v ss >/dev/null 2>&1; then
988
1073
  if ss -tlnp 2>/dev/null | grep -q ':3000 '; then
989
1074
  check_warn "Port 3000 in use — dashboard may need a different port"
990
1075
  else
@@ -1001,7 +1086,7 @@ echo ""
1001
1086
  echo -e "${PURPLE}${BOLD} DATABASE HEALTH${RESET}"
1002
1087
  echo -e "${DIM} ──────────────────────────────────────────${RESET}"
1003
1088
 
1004
- if command -v sqlite3 &>/dev/null; then
1089
+ if command -v sqlite3 >/dev/null 2>&1; then
1005
1090
  _sqlite_ver="$(sqlite3 --version 2>/dev/null | cut -d' ' -f1 || echo "unknown")"
1006
1091
  check_pass "sqlite3 ${_sqlite_ver}"
1007
1092
 
@@ -1034,14 +1119,20 @@ if command -v sqlite3 &>/dev/null; then
1034
1119
  _event_count=$(sqlite3 "$_db_file" "SELECT COUNT(*) FROM events;" 2>/dev/null || echo "0")
1035
1120
  _run_count=$(sqlite3 "$_db_file" "SELECT COUNT(*) FROM pipeline_runs;" 2>/dev/null || echo "0")
1036
1121
  info " Tables: events=${_event_count} pipeline_runs=${_run_count}"
1037
- else
1038
- check_warn "Database not initialized — run: shipwright db init"
1039
- fi
1122
+ else
1123
+ check_warn "Database not initialized — run: shipwright db init"
1124
+ fi
1040
1125
  else
1041
1126
  check_warn "sqlite3 not installed — DB features disabled"
1042
1127
  echo -e " ${DIM}Install: brew install sqlite (macOS) or apt install sqlite3 (Linux)${RESET}"
1043
1128
  fi
1044
1129
 
1130
+ # ═════════════════════════════════════════════════════════════════════════════
1131
+ # 15b. Intelligence Features
1132
+ # ═════════════════════════════════════════════════════════════════════════════
1133
+ echo ""
1134
+ doctor_check_intelligence
1135
+
1045
1136
  # ═════════════════════════════════════════════════════════════════════════════
1046
1137
  # 14. Platform health (AGI-level self-improvement)
1047
1138
  # ═════════════════════════════════════════════════════════════════════════════
@@ -1057,7 +1148,7 @@ if [[ ! -f "$PH_FILE" ]] && [[ "$SKIP_PLATFORM_SCAN" != "true" ]]; then
1057
1148
  bash "$SCRIPT_DIR_DOC/sw-hygiene.sh" platform-refactor >/dev/null 2>&1 || true
1058
1149
  fi
1059
1150
  fi
1060
- if [[ -f "$PH_FILE" ]] && command -v jq &>/dev/null; then
1151
+ if [[ -f "$PH_FILE" ]] && command -v jq >/dev/null 2>&1; then
1061
1152
  hc=$(jq -r '.counts.hardcoded // 0' "$PH_FILE" 2>/dev/null || echo "0")
1062
1153
  fb=$(jq -r '.counts.fallback // 0' "$PH_FILE" 2>/dev/null || echo "0")
1063
1154
  todo=$(jq -r '.counts.todo // 0' "$PH_FILE" 2>/dev/null || echo "0")
@@ -8,7 +8,7 @@
8
8
  set -euo pipefail
9
9
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
 
11
- VERSION="2.4.0"
11
+ VERSION="3.0.0"
12
12
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
13
 
14
14
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -20,6 +20,8 @@ _COMPAT="$SCRIPT_DIR/lib/compat.sh"
20
20
  [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
21
21
  # Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
22
22
  [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
23
+ # Color fallbacks when helpers not loaded
24
+ : "${CYAN:=}" "${BOLD:=}" "${RESET:=}" "${DIM:=}" "${GREEN:=}" "${RED:=}" "${YELLOW:=}" "${PURPLE:=}" "${WHITE:=}" "${BLUE:=}"
23
25
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
24
26
  [[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
25
27
  [[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
@@ -35,16 +37,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
35
37
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
36
38
  }
37
39
  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
40
  # ─── DORA Metrics Calculation ────────────────────────────────────────────────
49
41
 
50
42
  # Classify performance band per DORA standards
@@ -126,7 +118,7 @@ calculate_dora() {
126
118
  return 0
127
119
  fi
128
120
 
129
- if ! command -v jq &>/dev/null; then
121
+ if ! command -v jq >/dev/null 2>&1; then
130
122
  echo '{"deploy_freq":0,"cycle_time":0,"cfr":0,"mttr":0,"total":0}'
131
123
  return 0
132
124
  fi
@@ -179,7 +171,7 @@ show_dora_dashboard() {
179
171
  current=$(calculate_dora 7 0)
180
172
  previous=$(calculate_dora 7 7)
181
173
 
182
- if ! command -v jq &>/dev/null; then
174
+ if ! command -v jq >/dev/null 2>&1; then
183
175
  error "jq is required for dashboard display"
184
176
  exit 1
185
177
  fi
@@ -281,7 +273,7 @@ show_dx_metrics() {
281
273
  return 0
282
274
  fi
283
275
 
284
- if ! command -v jq &>/dev/null; then
276
+ if ! command -v jq >/dev/null 2>&1; then
285
277
  error "jq is required"
286
278
  exit 1
287
279
  fi
@@ -342,7 +334,7 @@ show_ai_metrics() {
342
334
  return 0
343
335
  fi
344
336
 
345
- if ! command -v jq &>/dev/null; then
337
+ if ! command -v jq >/dev/null 2>&1; then
346
338
  error "jq is required"
347
339
  exit 1
348
340
  fi
@@ -404,7 +396,7 @@ show_trends() {
404
396
  return 0
405
397
  fi
406
398
 
407
- if ! command -v jq &>/dev/null; then
399
+ if ! command -v jq >/dev/null 2>&1; then
408
400
  error "jq is required"
409
401
  exit 1
410
402
  fi
@@ -430,7 +422,7 @@ show_trends() {
430
422
  printf " %-3s %d %.1fh %.1f%% %.1fh\n" \
431
423
  "$date_str" "$deploys" "$ct" "$cfr" "$mttr"
432
424
 
433
- ((day++))
425
+ day=$((day + 1))
434
426
  done
435
427
 
436
428
  echo ""
@@ -451,7 +443,7 @@ show_comparison() {
451
443
  curr=$(calculate_dora "$current_period" 0)
452
444
  prev=$(calculate_dora "$previous_period" "$current_period")
453
445
 
454
- if ! command -v jq &>/dev/null; then
446
+ if ! command -v jq >/dev/null 2>&1; then
455
447
  error "jq is required"
456
448
  exit 1
457
449
  fi
@@ -498,7 +490,7 @@ export_metrics() {
498
490
  current=$(calculate_dora 7 0)
499
491
  previous=$(calculate_dora 7 7)
500
492
 
501
- if ! command -v jq &>/dev/null; then
493
+ if ! command -v jq >/dev/null 2>&1; then
502
494
  error "jq is required for JSON export"
503
495
  exit 1
504
496
  fi
@@ -7,7 +7,7 @@
7
7
  set -euo pipefail
8
8
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
9
9
 
10
- VERSION="2.4.0"
10
+ VERSION="3.0.0"
11
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
12
 
13
13
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -17,6 +17,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
17
  # Canonical helpers (colors, output, events)
18
18
  # shellcheck source=lib/helpers.sh
19
19
  [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
20
+ # shellcheck source=sw-db.sh
21
+ [[ -f "$SCRIPT_DIR/sw-db.sh" ]] && source "$SCRIPT_DIR/sw-db.sh"
20
22
  # Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
21
23
  [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
22
24
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
@@ -34,16 +36,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
34
36
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
35
37
  }
36
38
  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
39
  # ─── Durable State Directory ────────────────────────────────────────────────
48
40
  DURABLE_DIR="${HOME}/.shipwright/durable"
49
41
 
@@ -68,7 +60,7 @@ event_log_file() {
68
60
  echo "${DURABLE_DIR}/event-log/events.jsonl"
69
61
  }
70
62
 
71
- # Append event to WAL with sequence number
63
+ # Publish event to unified event store (durable WAL + emit_event for global events.jsonl)
72
64
  publish_event() {
73
65
  local event_type="$1"
74
66
  local payload="$2"
@@ -76,36 +68,12 @@ publish_event() {
76
68
  event_id="$(generate_event_id "evt")"
77
69
 
78
70
  ensure_durable_dir
71
+ local wal
72
+ wal="$(event_log_file)"
73
+ echo "{\"ts\":\"$(now_iso)\",\"type\":\"$event_type\",\"event_id\":\"$event_id\",\"payload\":$payload}" >> "$wal"
79
74
 
80
- # Get next sequence number (count existing lines + 1)
81
- local seq=1
82
- local log_file
83
- log_file="$(event_log_file)"
84
- if [[ -f "$log_file" ]]; then
85
- seq=$(($(wc -l < "$log_file" || true) + 1))
86
- fi
87
-
88
- # Build event JSON atomically
89
- local tmp_file
90
- tmp_file="$(mktemp "${DURABLE_DIR}/.tmp.XXXXXX")"
75
+ emit_event "$event_type" "event_id=$event_id" "payload=$payload"
91
76
 
92
- jq -n \
93
- --argjson sequence "$seq" \
94
- --arg event_id "$event_id" \
95
- --arg event_type "$event_type" \
96
- --argjson payload "$(echo "$payload" | jq . 2>/dev/null || echo '{}')" \
97
- --arg timestamp "$(now_iso)" \
98
- --arg status "published" \
99
- '{
100
- sequence: $sequence,
101
- event_id: $event_id,
102
- event_type: $event_type,
103
- payload: $payload,
104
- timestamp: $timestamp,
105
- status: $status
106
- }' >> "$log_file" || { rm -f "$tmp_file"; return 1; }
107
-
108
- rm -f "$tmp_file"
109
77
  echo "$event_id"
110
78
  }
111
79
 
@@ -126,10 +94,8 @@ save_checkpoint() {
126
94
  local cp_file
127
95
  cp_file="$(checkpoint_file "$workflow_id")"
128
96
 
129
- local tmp_file
130
- tmp_file="$(mktemp "${DURABLE_DIR}/.tmp.XXXXXX")"
131
-
132
- jq -n \
97
+ local cp_data
98
+ cp_data=$(jq -n \
133
99
  --arg workflow_id "$workflow_id" \
134
100
  --arg stage "$stage" \
135
101
  --argjson sequence "$seq" \
@@ -143,9 +109,15 @@ save_checkpoint() {
143
109
  state: $state,
144
110
  checkpoint_id: $checkpoint_id,
145
111
  created_at: $created_at
146
- }' > "$tmp_file" || { rm -f "$tmp_file"; return 1; }
112
+ }' 2>/dev/null) || return 1
147
113
 
148
- mv "$tmp_file" "$cp_file"
114
+ # DB storage (when available)
115
+ if type db_save_checkpoint >/dev/null 2>&1 && db_available 2>/dev/null; then
116
+ db_save_checkpoint "$workflow_id" "$cp_data" 2>/dev/null || true
117
+ fi
118
+
119
+ # File storage (backup)
120
+ echo "$cp_data" > "$cp_file"
149
121
  success "Checkpoint saved for workflow $workflow_id at stage $stage (seq: $seq)"
150
122
  }
151
123
 
@@ -154,6 +126,17 @@ restore_checkpoint() {
154
126
  local cp_file
155
127
  cp_file="$(checkpoint_file "$workflow_id")"
156
128
 
129
+ # Try DB first
130
+ if type db_load_checkpoint >/dev/null 2>&1 && db_available 2>/dev/null; then
131
+ local db_data
132
+ db_data=$(db_load_checkpoint "$workflow_id" 2>/dev/null)
133
+ if [[ -n "$db_data" ]]; then
134
+ echo "$db_data"
135
+ return 0
136
+ fi
137
+ fi
138
+
139
+ # Fallback to file
157
140
  if [[ ! -f "$cp_file" ]]; then
158
141
  error "No checkpoint found for workflow: $workflow_id"
159
142
  return 1
@@ -383,7 +366,7 @@ cmd_consume() {
383
366
  local failed=0
384
367
 
385
368
  while IFS= read -r line; do
386
- ((line_num++))
369
+ line_num=$((line_num + 1))
387
370
 
388
371
  if (( line_num <= offset )); then
389
372
  continue
@@ -395,14 +378,14 @@ cmd_consume() {
395
378
 
396
379
  if [[ -z "$event_id" ]]; then
397
380
  error "Invalid event format at line $line_num"
398
- ((failed++))
381
+ failed=$((failed + 1))
399
382
  continue
400
383
  fi
401
384
 
402
385
  # Check if already processed (exactly-once)
403
386
  if is_operation_completed "$event_id"; then
404
387
  info "Event $event_id already processed, skipping"
405
- ((processed++))
388
+ processed=$((processed + 1))
406
389
  save_consumer_offset "$consumer_id" "$line_num"
407
390
  continue
408
391
  fi
@@ -411,11 +394,11 @@ cmd_consume() {
411
394
  if echo "$line" | bash -c "$handler_cmd" 2>/dev/null; then
412
395
  mark_operation_completed "$event_id" '{"status":"success"}'
413
396
  success "Event $event_id processed"
414
- ((processed++))
397
+ processed=$((processed + 1))
415
398
  else
416
399
  error "Handler failed for event $event_id"
417
400
  send_to_dlq "$event_id" "handler_failed" 1
418
- ((failed++))
401
+ failed=$((failed + 1))
419
402
  fi
420
403
 
421
404
  # Update offset after successful processing
@@ -447,7 +430,7 @@ cmd_replay() {
447
430
 
448
431
  if (( seq >= start_seq )); then
449
432
  echo "$line" | bash -c "$handler_cmd"
450
- ((replayed++))
433
+ replayed=$((replayed + 1))
451
434
  fi
452
435
  done < "$log_file"
453
436