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-doctor.sh
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# ║ ║
|
|
5
5
|
# ║ Checks prerequisites, installed files, PATH, and common issues. ║
|
|
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
|
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
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"
|
|
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
|
|
311
|
-
if gh auth status
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
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
|
|
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
|
|
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"
|
|
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
|
|
825
|
-
if curl -s -m 3 "${DASHBOARD_URL}/api/health"
|
|
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"
|
|
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
|
|
866
|
-
if gh auth status
|
|
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}}'
|
|
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"
|
|
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
|
|
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
|
|
980
|
-
if lsof -i :3000 -sTCP:LISTEN
|
|
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
|
|
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
|
|
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
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
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
|
|
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")
|
package/scripts/sw-dora.sh
CHANGED
|
@@ -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="
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
package/scripts/sw-durable.sh
CHANGED
|
@@ -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="
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
130
|
-
|
|
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
|
-
}'
|
|
112
|
+
}' 2>/dev/null) || return 1
|
|
147
113
|
|
|
148
|
-
|
|
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
|
|