shipwright-cli 1.10.0 → 2.1.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 +221 -55
- package/completions/_shipwright +264 -32
- package/completions/shipwright.bash +118 -26
- package/completions/shipwright.fish +80 -2
- package/dashboard/server.ts +208 -0
- package/docs/strategy/01-market-research.md +619 -0
- package/docs/strategy/02-mission-and-brand.md +587 -0
- package/docs/strategy/03-gtm-and-roadmap.md +759 -0
- package/docs/strategy/QUICK-START.txt +289 -0
- package/docs/strategy/README.md +172 -0
- package/docs/tmux-research/TMUX-ARCHITECTURE.md +567 -0
- package/docs/tmux-research/TMUX-AUDIT.md +925 -0
- package/docs/tmux-research/TMUX-BEST-PRACTICES-2025-2026.md +829 -0
- package/docs/tmux-research/TMUX-QUICK-REFERENCE.md +543 -0
- package/docs/tmux-research/TMUX-RESEARCH-INDEX.md +438 -0
- package/package.json +4 -2
- package/scripts/lib/helpers.sh +7 -0
- package/scripts/sw +323 -2
- package/scripts/sw-activity.sh +500 -0
- package/scripts/sw-adaptive.sh +925 -0
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +613 -0
- package/scripts/sw-autonomous.sh +754 -0
- package/scripts/sw-changelog.sh +704 -0
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +602 -0
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +698 -0
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +605 -0
- package/scripts/sw-cost.sh +44 -3
- package/scripts/sw-daemon.sh +568 -138
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +1380 -0
- package/scripts/sw-decompose.sh +539 -0
- package/scripts/sw-deps.sh +551 -0
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +412 -0
- package/scripts/sw-docs-agent.sh +539 -0
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +107 -1
- package/scripts/sw-dora.sh +615 -0
- package/scripts/sw-durable.sh +710 -0
- package/scripts/sw-e2e-orchestrator.sh +535 -0
- package/scripts/sw-eventbus.sh +393 -0
- package/scripts/sw-feedback.sh +479 -0
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +567 -0
- package/scripts/sw-fleet-viz.sh +404 -0
- package/scripts/sw-fleet.sh +8 -1
- package/scripts/sw-github-app.sh +596 -0
- package/scripts/sw-github-checks.sh +4 -4
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +569 -0
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +559 -0
- package/scripts/sw-incident.sh +656 -0
- package/scripts/sw-init.sh +237 -24
- package/scripts/sw-instrument.sh +699 -0
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +363 -28
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +267 -21
- package/scripts/sw-memory.sh +18 -1
- package/scripts/sw-mission-control.sh +487 -0
- package/scripts/sw-model-router.sh +545 -0
- package/scripts/sw-otel.sh +596 -0
- package/scripts/sw-oversight.sh +764 -0
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +947 -35
- package/scripts/sw-pm.sh +758 -0
- package/scripts/sw-pr-lifecycle.sh +522 -0
- package/scripts/sw-predictive.sh +8 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +798 -0
- package/scripts/sw-quality.sh +595 -0
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +2248 -0
- package/scripts/sw-regression.sh +642 -0
- package/scripts/sw-release-manager.sh +736 -0
- package/scripts/sw-release.sh +706 -0
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +520 -0
- package/scripts/sw-retro.sh +691 -0
- package/scripts/sw-scale.sh +444 -0
- package/scripts/sw-security-audit.sh +505 -0
- package/scripts/sw-self-optimize.sh +1 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +263 -127
- package/scripts/sw-standup.sh +712 -0
- package/scripts/sw-status.sh +44 -2
- package/scripts/sw-strategic.sh +806 -0
- package/scripts/sw-stream.sh +450 -0
- package/scripts/sw-swarm.sh +620 -0
- package/scripts/sw-team-stages.sh +511 -0
- package/scripts/sw-templates.sh +4 -4
- package/scripts/sw-testgen.sh +566 -0
- package/scripts/sw-tmux-pipeline.sh +554 -0
- package/scripts/sw-tmux-role-color.sh +58 -0
- package/scripts/sw-tmux-status.sh +128 -0
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +485 -0
- package/scripts/sw-tracker-github.sh +188 -0
- package/scripts/sw-tracker-jira.sh +172 -0
- package/scripts/sw-tracker-linear.sh +251 -0
- package/scripts/sw-tracker.sh +117 -2
- package/scripts/sw-triage.sh +627 -0
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +677 -0
- package/scripts/sw-webhook.sh +627 -0
- package/scripts/sw-widgets.sh +530 -0
- package/scripts/sw-worktree.sh +1 -1
- package/templates/pipelines/autonomous.json +2 -2
- package/tmux/shipwright-overlay.conf +35 -17
- package/tmux/tmux.conf +23 -21
package/scripts/sw-remote.sh
CHANGED
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright replay — Pipeline run replay, timeline viewing, narratives ║
|
|
4
|
+
# ║ DVR for pipeline execution: list, show, narrative, diff, export, compare ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
|
+
|
|
9
|
+
VERSION="2.1.0"
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
|
|
12
|
+
|
|
13
|
+
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
14
|
+
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
15
|
+
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
16
|
+
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
17
|
+
GREEN='\033[38;2;74;222;128m' # success
|
|
18
|
+
YELLOW='\033[38;2;250;204;21m' # warning
|
|
19
|
+
RED='\033[38;2;248;113;113m' # error
|
|
20
|
+
DIM='\033[2m'
|
|
21
|
+
BOLD='\033[1m'
|
|
22
|
+
RESET='\033[0m'
|
|
23
|
+
|
|
24
|
+
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
25
|
+
# shellcheck source=lib/compat.sh
|
|
26
|
+
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
27
|
+
|
|
28
|
+
# ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
31
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
32
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
33
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
34
|
+
|
|
35
|
+
# Check if jq is available
|
|
36
|
+
check_jq() {
|
|
37
|
+
if ! command -v jq &>/dev/null; then
|
|
38
|
+
error "jq is required. Install with: brew install jq"
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Format duration in seconds to human readable
|
|
44
|
+
format_duration() {
|
|
45
|
+
local seconds=$1
|
|
46
|
+
if [[ $seconds -lt 60 ]]; then
|
|
47
|
+
echo "${seconds}s"
|
|
48
|
+
elif [[ $seconds -lt 3600 ]]; then
|
|
49
|
+
local mins=$((seconds / 60))
|
|
50
|
+
local secs=$((seconds % 60))
|
|
51
|
+
echo "${mins}m${secs}s"
|
|
52
|
+
else
|
|
53
|
+
local hours=$((seconds / 3600))
|
|
54
|
+
local mins=$(((seconds % 3600) / 60))
|
|
55
|
+
echo "${hours}h${mins}m"
|
|
56
|
+
fi
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Status color coding
|
|
60
|
+
color_status() {
|
|
61
|
+
local status="$1"
|
|
62
|
+
case "$status" in
|
|
63
|
+
success) echo -e "${GREEN}${BOLD}✓${RESET} $status" ;;
|
|
64
|
+
failure) echo -e "${RED}${BOLD}✗${RESET} $status" ;;
|
|
65
|
+
retry) echo -e "${YELLOW}${BOLD}⚠${RESET} $status" ;;
|
|
66
|
+
in_progress) echo -e "${CYAN}${BOLD}→${RESET} $status" ;;
|
|
67
|
+
*) echo "$status" ;;
|
|
68
|
+
esac
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# ─── List subcommand ───────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
cmd_list() {
|
|
74
|
+
check_jq
|
|
75
|
+
|
|
76
|
+
if [[ ! -f "$EVENTS_FILE" ]]; then
|
|
77
|
+
warn "No pipeline runs recorded yet (events file not found)"
|
|
78
|
+
exit 0
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
info "Pipeline runs (${DIM}from $EVENTS_FILE${RESET})"
|
|
82
|
+
echo ""
|
|
83
|
+
|
|
84
|
+
# Extract unique pipeline runs, sorted by start time
|
|
85
|
+
# Use --slurpfile to handle parsing errors gracefully
|
|
86
|
+
jq -r 'select(.type == "pipeline.started") | [.ts, .issue, .pipeline, .model, .goal] | @tsv' "$EVENTS_FILE" 2>/dev/null | \
|
|
87
|
+
sort -r | \
|
|
88
|
+
while IFS=$'\t' read -r ts issue pipeline model goal; do
|
|
89
|
+
# Find corresponding completion event
|
|
90
|
+
local completion
|
|
91
|
+
completion=$(jq -r "select(.type == \"pipeline.completed\" and .issue == $issue) | .result, .duration_s" "$EVENTS_FILE" 2>/dev/null | head -2)
|
|
92
|
+
|
|
93
|
+
if [[ -n "$completion" ]]; then
|
|
94
|
+
local result duration
|
|
95
|
+
result=$(echo "$completion" | head -1)
|
|
96
|
+
duration=$(echo "$completion" | tail -1)
|
|
97
|
+
duration="${duration:-0}"
|
|
98
|
+
|
|
99
|
+
# Format date
|
|
100
|
+
local date time
|
|
101
|
+
date=$(echo "$ts" | cut -d'T' -f1)
|
|
102
|
+
time=$(echo "$ts" | cut -d'T' -f2 | cut -d'Z' -f1)
|
|
103
|
+
|
|
104
|
+
# Truncate goal to 40 chars
|
|
105
|
+
local goal_trunc="${goal:0:40}"
|
|
106
|
+
[[ ${#goal} -gt 40 ]] && goal_trunc="${goal_trunc}…"
|
|
107
|
+
|
|
108
|
+
printf " ${CYAN}#%-5s${RESET} ${BOLD}%s${RESET} %s %s ${DIM}%s${RESET} %s\n" \
|
|
109
|
+
"$issue" "$date" "$time" "$(color_status "${result:-success}")" "$(format_duration "$duration")" "$goal_trunc"
|
|
110
|
+
fi
|
|
111
|
+
done
|
|
112
|
+
|
|
113
|
+
echo ""
|
|
114
|
+
success "Use 'shipwright replay show <issue>' to see details"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# ─── Show subcommand ───────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
cmd_show() {
|
|
120
|
+
local issue="${1:-}"
|
|
121
|
+
check_jq
|
|
122
|
+
|
|
123
|
+
if [[ -z "$issue" ]]; then
|
|
124
|
+
error "Usage: shipwright replay show <issue>"
|
|
125
|
+
exit 1
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
if [[ ! -f "$EVENTS_FILE" ]]; then
|
|
129
|
+
error "No events recorded yet"
|
|
130
|
+
exit 1
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
# Find pipeline run for this issue
|
|
134
|
+
local pipeline_start
|
|
135
|
+
pipeline_start=$(jq -r "select(.type == \"pipeline.started\" and .issue == $issue) | [.ts, .pipeline, .model, .goal] | @tsv" "$EVENTS_FILE" 2>/dev/null | head -1)
|
|
136
|
+
|
|
137
|
+
if [[ -z "$pipeline_start" ]]; then
|
|
138
|
+
error "No pipeline run found for issue #$issue"
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
local start_ts pipeline_type model goal
|
|
143
|
+
start_ts=$(echo "$pipeline_start" | cut -f1)
|
|
144
|
+
pipeline_type=$(echo "$pipeline_start" | cut -f2)
|
|
145
|
+
model=$(echo "$pipeline_start" | cut -f3)
|
|
146
|
+
goal=$(echo "$pipeline_start" | cut -f4)
|
|
147
|
+
|
|
148
|
+
info "Pipeline Timeline for Issue #$issue"
|
|
149
|
+
echo ""
|
|
150
|
+
echo -e " ${BOLD}Pipeline Type:${RESET} $pipeline_type"
|
|
151
|
+
echo -e " ${BOLD}Model:${RESET} $model"
|
|
152
|
+
[[ -n "$goal" ]] && echo -e " ${BOLD}Goal:${RESET} $goal"
|
|
153
|
+
echo ""
|
|
154
|
+
|
|
155
|
+
# Find all stage events for this issue
|
|
156
|
+
echo -e " ${BOLD}Stages:${RESET}"
|
|
157
|
+
jq -r "select(.issue == $issue and .type == \"stage.completed\") | [.ts, .stage, .duration_s // 0, .result // \"success\"] | @tsv" "$EVENTS_FILE" 2>/dev/null | \
|
|
158
|
+
while IFS=$'\t' read -r ts stage duration result; do
|
|
159
|
+
local status_icon
|
|
160
|
+
case "$result" in
|
|
161
|
+
success) status_icon="${GREEN}${BOLD}✓${RESET}" ;;
|
|
162
|
+
failure) status_icon="${RED}${BOLD}✗${RESET}" ;;
|
|
163
|
+
retry) status_icon="${YELLOW}${BOLD}⚠${RESET}" ;;
|
|
164
|
+
*) status_icon="•" ;;
|
|
165
|
+
esac
|
|
166
|
+
|
|
167
|
+
local time
|
|
168
|
+
time=$(echo "$ts" | cut -d'T' -f2 | cut -d'Z' -f1)
|
|
169
|
+
|
|
170
|
+
printf " %s ${CYAN}%-20s${RESET} ${DIM}%s${RESET} %s\n" \
|
|
171
|
+
"$status_icon" "$stage" "$(format_duration "$duration")" "$time"
|
|
172
|
+
done
|
|
173
|
+
|
|
174
|
+
# Find pipeline completion
|
|
175
|
+
local completion
|
|
176
|
+
completion=$(jq -r "select(.type == \"pipeline.completed\" and .issue == $issue) | [.result // \"unknown\", .duration_s // 0, .input_tokens // 0, .output_tokens // 0] | @tsv" "$EVENTS_FILE" 2>/dev/null | head -1)
|
|
177
|
+
|
|
178
|
+
if [[ -n "$completion" ]]; then
|
|
179
|
+
echo ""
|
|
180
|
+
local result duration input_tokens output_tokens
|
|
181
|
+
result=$(echo "$completion" | cut -f1)
|
|
182
|
+
duration=$(echo "$completion" | cut -f2)
|
|
183
|
+
input_tokens=$(echo "$completion" | cut -f3)
|
|
184
|
+
output_tokens=$(echo "$completion" | cut -f4)
|
|
185
|
+
|
|
186
|
+
echo -e " ${BOLD}Result:${RESET} $(color_status "$result")"
|
|
187
|
+
echo -e " ${BOLD}Duration:${RESET} $(format_duration "$duration")"
|
|
188
|
+
echo -e " ${BOLD}Tokens:${RESET} in=$input_tokens, out=$output_tokens"
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
echo ""
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# ─── Narrative subcommand ──────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
cmd_narrative() {
|
|
197
|
+
local issue="${1:-}"
|
|
198
|
+
check_jq
|
|
199
|
+
|
|
200
|
+
if [[ -z "$issue" ]]; then
|
|
201
|
+
error "Usage: shipwright replay narrative <issue>"
|
|
202
|
+
exit 1
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
if [[ ! -f "$EVENTS_FILE" ]]; then
|
|
206
|
+
error "No events recorded yet"
|
|
207
|
+
exit 1
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
local pipeline_start
|
|
211
|
+
pipeline_start=$(jq -r "select(.type == \"pipeline.started\" and .issue == $issue) | [.ts, .goal // \"\", .pipeline // \"standard\"] | @tsv" "$EVENTS_FILE" 2>/dev/null | head -1)
|
|
212
|
+
|
|
213
|
+
if [[ -z "$pipeline_start" ]]; then
|
|
214
|
+
error "No pipeline run found for issue #$issue"
|
|
215
|
+
exit 1
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
local start_ts goal pipeline_type
|
|
219
|
+
start_ts=$(echo "$pipeline_start" | cut -f1)
|
|
220
|
+
goal=$(echo "$pipeline_start" | cut -f2)
|
|
221
|
+
pipeline_type=$(echo "$pipeline_start" | cut -f3)
|
|
222
|
+
|
|
223
|
+
# Get pipeline completion
|
|
224
|
+
local completion
|
|
225
|
+
completion=$(jq -r "select(.type == \"pipeline.completed\" and .issue == $issue) | [.result // \"unknown\", .duration_s // 0, .input_tokens // 0, .output_tokens // 0] | @tsv" "$EVENTS_FILE" 2>/dev/null | head -1)
|
|
226
|
+
|
|
227
|
+
local result duration input_tokens output_tokens
|
|
228
|
+
if [[ -n "$completion" ]]; then
|
|
229
|
+
result=$(echo "$completion" | cut -f1)
|
|
230
|
+
duration=$(echo "$completion" | cut -f2)
|
|
231
|
+
input_tokens=$(echo "$completion" | cut -f3)
|
|
232
|
+
output_tokens=$(echo "$completion" | cut -f4)
|
|
233
|
+
else
|
|
234
|
+
result="in_progress"
|
|
235
|
+
duration="0"
|
|
236
|
+
input_tokens="0"
|
|
237
|
+
output_tokens="0"
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
# Count stages
|
|
241
|
+
local stage_count
|
|
242
|
+
stage_count=$(jq -r "select(.issue == $issue and .type == \"stage.completed\") | .stage" "$EVENTS_FILE" 2>/dev/null | wc -l)
|
|
243
|
+
|
|
244
|
+
# Build narrative
|
|
245
|
+
info "Pipeline Narrative"
|
|
246
|
+
echo ""
|
|
247
|
+
echo "Pipeline processed issue #$issue"
|
|
248
|
+
[[ -n "$goal" ]] && echo "Goal: $goal"
|
|
249
|
+
echo "in ${duration}s across $stage_count stages."
|
|
250
|
+
echo ""
|
|
251
|
+
echo "Pipeline Type: $pipeline_type"
|
|
252
|
+
echo "Result: $(color_status "$result")"
|
|
253
|
+
echo "Tokens Used: $input_tokens input, $output_tokens output"
|
|
254
|
+
echo ""
|
|
255
|
+
|
|
256
|
+
# Key events
|
|
257
|
+
local retry_count build_iterations test_failures
|
|
258
|
+
retry_count=$(jq -r "select(.issue == $issue and .type == \"stage.completed\" and .result == \"retry\") | .stage" "$EVENTS_FILE" 2>/dev/null | wc -l)
|
|
259
|
+
build_iterations=$(jq -r "select(.issue == $issue and .type == \"build.iteration\") | .iteration" "$EVENTS_FILE" 2>/dev/null | tail -1)
|
|
260
|
+
test_failures=$(jq -r "select(.issue == $issue and .type == \"test.failed\") | .test" "$EVENTS_FILE" 2>/dev/null | wc -l)
|
|
261
|
+
|
|
262
|
+
echo "Key Events:"
|
|
263
|
+
[[ $retry_count -gt 0 ]] && echo " • $retry_count stage retries"
|
|
264
|
+
[[ -n "$build_iterations" && "$build_iterations" != "null" ]] && echo " • $build_iterations build iterations"
|
|
265
|
+
[[ $test_failures -gt 0 ]] && echo " • $test_failures test failures encountered"
|
|
266
|
+
|
|
267
|
+
echo ""
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
# ─── Diff subcommand ──────────────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
cmd_diff() {
|
|
273
|
+
local issue="${1:-}"
|
|
274
|
+
check_jq
|
|
275
|
+
|
|
276
|
+
if [[ -z "$issue" ]]; then
|
|
277
|
+
error "Usage: shipwright replay diff <issue>"
|
|
278
|
+
exit 1
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
if ! command -v git &>/dev/null; then
|
|
282
|
+
error "git is required for diff subcommand"
|
|
283
|
+
exit 1
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
if [[ ! -f "$EVENTS_FILE" ]]; then
|
|
287
|
+
error "No events recorded yet"
|
|
288
|
+
exit 1
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
# Check if issue was processed
|
|
292
|
+
local found
|
|
293
|
+
found=$(jq -r "select(.issue == $issue and .type == \"pipeline.completed\") | .issue" "$EVENTS_FILE" | head -1)
|
|
294
|
+
|
|
295
|
+
if [[ -z "$found" ]]; then
|
|
296
|
+
error "No pipeline run found for issue #$issue"
|
|
297
|
+
exit 1
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
info "Git commits for issue #$issue"
|
|
301
|
+
echo ""
|
|
302
|
+
|
|
303
|
+
# Try to find commits with issue reference
|
|
304
|
+
git log --all --grep="#$issue" --oneline || true
|
|
305
|
+
|
|
306
|
+
# Also try to find by branch name pattern
|
|
307
|
+
local branch_pattern="issue-${issue}"
|
|
308
|
+
if git show-ref --verify "refs/heads/$branch_pattern" &>/dev/null; then
|
|
309
|
+
echo ""
|
|
310
|
+
info "Commits on branch '$branch_pattern':"
|
|
311
|
+
git log "$branch_pattern" --oneline || true
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
echo ""
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# ─── Export subcommand ──────────────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
cmd_export() {
|
|
320
|
+
local issue="${1:-}"
|
|
321
|
+
check_jq
|
|
322
|
+
|
|
323
|
+
if [[ -z "$issue" ]]; then
|
|
324
|
+
error "Usage: shipwright replay export <issue>"
|
|
325
|
+
exit 1
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
if [[ ! -f "$EVENTS_FILE" ]]; then
|
|
329
|
+
error "No events recorded yet"
|
|
330
|
+
exit 1
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
local pipeline_start
|
|
334
|
+
pipeline_start=$(jq -r "select(.type == \"pipeline.started\" and .issue == $issue) | [.ts, .goal // \"\", .pipeline // \"standard\"] | @tsv" "$EVENTS_FILE" 2>/dev/null | head -1)
|
|
335
|
+
|
|
336
|
+
if [[ -z "$pipeline_start" ]]; then
|
|
337
|
+
error "No pipeline run found for issue #$issue"
|
|
338
|
+
exit 1
|
|
339
|
+
fi
|
|
340
|
+
|
|
341
|
+
local start_ts goal pipeline_type
|
|
342
|
+
start_ts=$(echo "$pipeline_start" | cut -f1)
|
|
343
|
+
goal=$(echo "$pipeline_start" | cut -f2)
|
|
344
|
+
pipeline_type=$(echo "$pipeline_start" | cut -f3)
|
|
345
|
+
|
|
346
|
+
local completion
|
|
347
|
+
completion=$(jq -r "select(.type == \"pipeline.completed\" and .issue == $issue) | [.result // \"unknown\", .duration_s // 0] | @tsv" "$EVENTS_FILE" 2>/dev/null | head -1)
|
|
348
|
+
|
|
349
|
+
local result duration
|
|
350
|
+
if [[ -n "$completion" ]]; then
|
|
351
|
+
result=$(echo "$completion" | cut -f1)
|
|
352
|
+
duration=$(echo "$completion" | cut -f2)
|
|
353
|
+
else
|
|
354
|
+
result="in_progress"
|
|
355
|
+
duration="0"
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
# Output markdown report
|
|
359
|
+
cat << EOF
|
|
360
|
+
# Pipeline Report: Issue #$issue
|
|
361
|
+
|
|
362
|
+
**Date:** $(echo "$start_ts" | cut -d'T' -f1)
|
|
363
|
+
**Type:** $pipeline_type
|
|
364
|
+
**Result:** $result
|
|
365
|
+
**Duration:** $(format_duration "$duration")
|
|
366
|
+
|
|
367
|
+
## Goal
|
|
368
|
+
$goal
|
|
369
|
+
|
|
370
|
+
## Timeline
|
|
371
|
+
|
|
372
|
+
| Stage | Duration | Status |
|
|
373
|
+
|-------|----------|--------|
|
|
374
|
+
EOF
|
|
375
|
+
|
|
376
|
+
# Add stage rows
|
|
377
|
+
jq -r "select(.issue == $issue and .type == \"stage.completed\") | [.stage, .duration_s // 0, .result // \"success\"] | @tsv" "$EVENTS_FILE" 2>/dev/null | \
|
|
378
|
+
while IFS=$'\t' read -r stage duration result; do
|
|
379
|
+
printf "| %s | %s | %s |\n" "$stage" "$(format_duration "$duration")" "$result"
|
|
380
|
+
done
|
|
381
|
+
|
|
382
|
+
cat << EOF
|
|
383
|
+
|
|
384
|
+
## Summary
|
|
385
|
+
|
|
386
|
+
- **Issue Number:** #$issue
|
|
387
|
+
- **Pipeline Type:** $pipeline_type
|
|
388
|
+
- **Overall Result:** $result
|
|
389
|
+
- **Total Duration:** $(format_duration "$duration")
|
|
390
|
+
|
|
391
|
+
## Events
|
|
392
|
+
|
|
393
|
+
$(jq -r "select(.issue == $issue) | [.ts, .type] | @tsv" "$EVENTS_FILE" 2>/dev/null | awk '{print "- " $1 " — " $2}')
|
|
394
|
+
|
|
395
|
+
EOF
|
|
396
|
+
|
|
397
|
+
success "Markdown report generated above"
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
# ─── Compare subcommand ───────────────────────────────────────────────────
|
|
401
|
+
|
|
402
|
+
cmd_compare() {
|
|
403
|
+
local issue1="${1:-}" issue2="${2:-}"
|
|
404
|
+
check_jq
|
|
405
|
+
|
|
406
|
+
if [[ -z "$issue1" || -z "$issue2" ]]; then
|
|
407
|
+
error "Usage: shipwright replay compare <issue1> <issue2>"
|
|
408
|
+
exit 1
|
|
409
|
+
fi
|
|
410
|
+
|
|
411
|
+
if [[ ! -f "$EVENTS_FILE" ]]; then
|
|
412
|
+
error "No events recorded yet"
|
|
413
|
+
exit 1
|
|
414
|
+
fi
|
|
415
|
+
|
|
416
|
+
info "Comparing pipeline runs: #$issue1 vs #$issue2"
|
|
417
|
+
echo ""
|
|
418
|
+
|
|
419
|
+
# Get both runs
|
|
420
|
+
local run1 run2
|
|
421
|
+
run1=$(jq -r "select(.type == \"pipeline.started\" and .issue == $issue1) | [.goal // \"\", .pipeline, .model] | @tsv" "$EVENTS_FILE" 2>/dev/null | head -1)
|
|
422
|
+
run2=$(jq -r "select(.type == \"pipeline.started\" and .issue == $issue2) | [.goal // \"\", .pipeline, .model] | @tsv" "$EVENTS_FILE" 2>/dev/null | head -1)
|
|
423
|
+
|
|
424
|
+
if [[ -z "$run1" || -z "$run2" ]]; then
|
|
425
|
+
error "Could not find both pipeline runs"
|
|
426
|
+
exit 1
|
|
427
|
+
fi
|
|
428
|
+
|
|
429
|
+
# Extract details
|
|
430
|
+
local goal1 type1 model1
|
|
431
|
+
goal1=$(echo "$run1" | cut -f1)
|
|
432
|
+
type1=$(echo "$run1" | cut -f2)
|
|
433
|
+
model1=$(echo "$run1" | cut -f3)
|
|
434
|
+
|
|
435
|
+
local goal2 type2 model2
|
|
436
|
+
goal2=$(echo "$run2" | cut -f1)
|
|
437
|
+
type2=$(echo "$run2" | cut -f2)
|
|
438
|
+
model2=$(echo "$run2" | cut -f3)
|
|
439
|
+
|
|
440
|
+
# Get completions
|
|
441
|
+
local comp1 comp2
|
|
442
|
+
comp1=$(jq -r "select(.type == \"pipeline.completed\" and .issue == $issue1) | [.result // \"unknown\", .duration_s // 0] | @tsv" "$EVENTS_FILE" 2>/dev/null | head -1)
|
|
443
|
+
comp2=$(jq -r "select(.type == \"pipeline.completed\" and .issue == $issue2) | [.result // \"unknown\", .duration_s // 0] | @tsv" "$EVENTS_FILE" 2>/dev/null | head -1)
|
|
444
|
+
|
|
445
|
+
local result1 duration1 result2 duration2
|
|
446
|
+
result1=$(echo "$comp1" | cut -f1)
|
|
447
|
+
duration1=$(echo "$comp1" | cut -f2)
|
|
448
|
+
result2=$(echo "$comp2" | cut -f1)
|
|
449
|
+
duration2=$(echo "$comp2" | cut -f2)
|
|
450
|
+
|
|
451
|
+
# Comparison table
|
|
452
|
+
printf "%-20s | %-15s | %-15s\n" "Metric" "#$issue1" "#$issue2"
|
|
453
|
+
printf "%-20s | %-15s | %-15s\n" "---" "---" "---"
|
|
454
|
+
printf "%-20s | %-15s | %-15s\n" "Type" "$type1" "$type2"
|
|
455
|
+
printf "%-20s | %-15s | %-15s\n" "Model" "$model1" "$model2"
|
|
456
|
+
printf "%-20s | %-15s | %-15s\n" "Result" "$result1" "$result2"
|
|
457
|
+
printf "%-20s | %-15s | %-15s\n" "Duration" "$(format_duration "$duration1")" "$(format_duration "$duration2")"
|
|
458
|
+
|
|
459
|
+
echo ""
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
# ─── Help and main ────────────────────────────────────────────────────────
|
|
463
|
+
|
|
464
|
+
show_help() {
|
|
465
|
+
cat << EOF
|
|
466
|
+
${BOLD}shipwright replay${RESET} — Pipeline DVR: replay, timeline, narrative, and analysis
|
|
467
|
+
|
|
468
|
+
${BOLD}USAGE${RESET}
|
|
469
|
+
shipwright replay <subcommand> [options]
|
|
470
|
+
|
|
471
|
+
${BOLD}SUBCOMMANDS${RESET}
|
|
472
|
+
${CYAN}list${RESET} Show all past pipeline runs
|
|
473
|
+
${CYAN}show${RESET} <issue> Display timeline for a specific run
|
|
474
|
+
${CYAN}narrative${RESET} <issue> Generate AI-readable summary of what happened
|
|
475
|
+
${CYAN}diff${RESET} <issue> Show git commits made during pipeline run
|
|
476
|
+
${CYAN}export${RESET} <issue> Export run as markdown report
|
|
477
|
+
${CYAN}compare${RESET} <issue1> <issue2> Compare two pipeline runs side-by-side
|
|
478
|
+
${CYAN}help${RESET} Show this help message
|
|
479
|
+
|
|
480
|
+
${BOLD}EXAMPLES${RESET}
|
|
481
|
+
shipwright replay list # See all runs
|
|
482
|
+
shipwright replay show 42 # Timeline for #42
|
|
483
|
+
shipwright replay narrative 42 # Summary of #42
|
|
484
|
+
shipwright replay diff 42 # Commits for #42
|
|
485
|
+
shipwright replay export 42 # Markdown report for #42
|
|
486
|
+
shipwright replay compare 42 43 # Compare two runs
|
|
487
|
+
|
|
488
|
+
${DIM}Pipeline events are read from: $EVENTS_FILE${RESET}
|
|
489
|
+
|
|
490
|
+
EOF
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
# ─── Main entry point ────────────────────────────────────────────────────────
|
|
494
|
+
|
|
495
|
+
main() {
|
|
496
|
+
local cmd="${1:-help}"
|
|
497
|
+
shift 2>/dev/null || true
|
|
498
|
+
|
|
499
|
+
case "$cmd" in
|
|
500
|
+
list) cmd_list "$@" ;;
|
|
501
|
+
show) cmd_show "$@" ;;
|
|
502
|
+
narrative) cmd_narrative "$@" ;;
|
|
503
|
+
diff) cmd_diff "$@" ;;
|
|
504
|
+
export) cmd_export "$@" ;;
|
|
505
|
+
compare) cmd_compare "$@" ;;
|
|
506
|
+
help|--help|-h)
|
|
507
|
+
show_help
|
|
508
|
+
;;
|
|
509
|
+
*)
|
|
510
|
+
error "Unknown subcommand: $cmd"
|
|
511
|
+
echo ""
|
|
512
|
+
show_help
|
|
513
|
+
exit 1
|
|
514
|
+
;;
|
|
515
|
+
esac
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
519
|
+
main "$@"
|
|
520
|
+
fi
|