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-replay.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="
|
|
9
|
+
VERSION="3.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
|
|
12
12
|
|
|
@@ -17,6 +17,9 @@ EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
|
|
|
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
|
+
# DB layer for dual-read (SQLite + JSONL fallback)
|
|
21
|
+
# shellcheck source=sw-db.sh
|
|
22
|
+
[[ -f "$SCRIPT_DIR/sw-db.sh" ]] && source "$SCRIPT_DIR/sw-db.sh"
|
|
20
23
|
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
21
24
|
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
22
25
|
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
@@ -34,19 +37,9 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
34
37
|
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
35
38
|
}
|
|
36
39
|
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
40
|
# Check if jq is available
|
|
48
41
|
check_jq() {
|
|
49
|
-
if ! command -v jq
|
|
42
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
50
43
|
error "jq is required. Install with: brew install jq"
|
|
51
44
|
exit 1
|
|
52
45
|
fi
|
|
@@ -85,22 +78,23 @@ color_status() {
|
|
|
85
78
|
cmd_list() {
|
|
86
79
|
check_jq
|
|
87
80
|
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
local events_json
|
|
82
|
+
events_json=$(db_query_events "" 5000 2>/dev/null || echo "[]")
|
|
83
|
+
if [[ "$events_json" == "[]" ]] || [[ -z "$events_json" ]]; then
|
|
84
|
+
warn "No pipeline runs recorded yet (events not found)"
|
|
90
85
|
exit 0
|
|
91
86
|
fi
|
|
92
87
|
|
|
93
|
-
info "Pipeline runs (${DIM}
|
|
88
|
+
info "Pipeline runs (${DIM}DB + JSONL${RESET})"
|
|
94
89
|
echo ""
|
|
95
90
|
|
|
96
91
|
# Extract unique pipeline runs, sorted by start time
|
|
97
|
-
|
|
98
|
-
jq -r 'select(.type == "pipeline.started") | [.ts, .issue, .pipeline, .model, .goal] | @tsv' "$EVENTS_FILE" 2>/dev/null | \
|
|
92
|
+
echo "$events_json" | jq -r '.[] | select(.type == "pipeline.started") | [.ts, .issue, .pipeline, .model, .goal] | @tsv' 2>/dev/null | \
|
|
99
93
|
sort -r | \
|
|
100
94
|
while IFS=$'\t' read -r ts issue pipeline model goal; do
|
|
101
95
|
# Find corresponding completion event
|
|
102
96
|
local completion
|
|
103
|
-
completion=$(jq -r "select(.type == \"pipeline.completed\" and .issue == $issue) | .result, .duration_s"
|
|
97
|
+
completion=$(echo "$events_json" | jq -r ".[] | select(.type == \"pipeline.completed\" and .issue == $issue) | .result, .duration_s" 2>/dev/null | head -2)
|
|
104
98
|
|
|
105
99
|
if [[ -n "$completion" ]]; then
|
|
106
100
|
local result duration
|
|
@@ -137,14 +131,16 @@ cmd_show() {
|
|
|
137
131
|
exit 1
|
|
138
132
|
fi
|
|
139
133
|
|
|
140
|
-
|
|
134
|
+
local events_json
|
|
135
|
+
events_json=$(db_query_events "" 5000 2>/dev/null || echo "[]")
|
|
136
|
+
if [[ "$events_json" == "[]" ]] || [[ -z "$events_json" ]]; then
|
|
141
137
|
error "No events recorded yet"
|
|
142
138
|
exit 1
|
|
143
139
|
fi
|
|
144
140
|
|
|
145
141
|
# Find pipeline run for this issue
|
|
146
142
|
local pipeline_start
|
|
147
|
-
pipeline_start=$(jq -r "select(.type == \"pipeline.started\" and .issue == $issue) | [.ts, .pipeline, .model, .goal] | @tsv"
|
|
143
|
+
pipeline_start=$(echo "$events_json" | jq -r ".[] | select(.type == \"pipeline.started\" and .issue == $issue) | [.ts, .pipeline, .model, .goal] | @tsv" 2>/dev/null | head -1)
|
|
148
144
|
|
|
149
145
|
if [[ -z "$pipeline_start" ]]; then
|
|
150
146
|
error "No pipeline run found for issue #$issue"
|
|
@@ -166,7 +162,7 @@ cmd_show() {
|
|
|
166
162
|
|
|
167
163
|
# Find all stage events for this issue
|
|
168
164
|
echo -e " ${BOLD}Stages:${RESET}"
|
|
169
|
-
jq -r "select(.issue == $issue and .type == \"stage.completed\") | [.ts, .stage, .duration_s // 0, .result // \"success\"] | @tsv"
|
|
165
|
+
echo "$events_json" | jq -r ".[] | select(.issue == $issue and .type == \"stage.completed\") | [.ts, .stage, .duration_s // 0, .result // \"success\"] | @tsv" 2>/dev/null | \
|
|
170
166
|
while IFS=$'\t' read -r ts stage duration result; do
|
|
171
167
|
local status_icon
|
|
172
168
|
case "$result" in
|
|
@@ -185,7 +181,7 @@ cmd_show() {
|
|
|
185
181
|
|
|
186
182
|
# Find pipeline completion
|
|
187
183
|
local completion
|
|
188
|
-
completion=$(jq -r "select(.type == \"pipeline.completed\" and .issue == $issue) | [.result // \"unknown\", .duration_s // 0, .input_tokens // 0, .output_tokens // 0] | @tsv"
|
|
184
|
+
completion=$(echo "$events_json" | jq -r ".[] | select(.type == \"pipeline.completed\" and .issue == $issue) | [.result // \"unknown\", .duration_s // 0, .input_tokens // 0, .output_tokens // 0] | @tsv" 2>/dev/null | head -1)
|
|
189
185
|
|
|
190
186
|
if [[ -n "$completion" ]]; then
|
|
191
187
|
echo ""
|
|
@@ -214,13 +210,15 @@ cmd_narrative() {
|
|
|
214
210
|
exit 1
|
|
215
211
|
fi
|
|
216
212
|
|
|
217
|
-
|
|
213
|
+
local events_json
|
|
214
|
+
events_json=$(db_query_events "" 5000 2>/dev/null || echo "[]")
|
|
215
|
+
if [[ "$events_json" == "[]" ]] || [[ -z "$events_json" ]]; then
|
|
218
216
|
error "No events recorded yet"
|
|
219
217
|
exit 1
|
|
220
218
|
fi
|
|
221
219
|
|
|
222
220
|
local pipeline_start
|
|
223
|
-
pipeline_start=$(jq -r "select(.type == \"pipeline.started\" and .issue == $issue) | [.ts, .goal // \"\", .pipeline // \"standard\"] | @tsv"
|
|
221
|
+
pipeline_start=$(echo "$events_json" | jq -r ".[] | select(.type == \"pipeline.started\" and .issue == $issue) | [.ts, .goal // \"\", .pipeline // \"standard\"] | @tsv" 2>/dev/null | head -1)
|
|
224
222
|
|
|
225
223
|
if [[ -z "$pipeline_start" ]]; then
|
|
226
224
|
error "No pipeline run found for issue #$issue"
|
|
@@ -234,7 +232,7 @@ cmd_narrative() {
|
|
|
234
232
|
|
|
235
233
|
# Get pipeline completion
|
|
236
234
|
local completion
|
|
237
|
-
completion=$(jq -r "select(.type == \"pipeline.completed\" and .issue == $issue) | [.result // \"unknown\", .duration_s // 0, .input_tokens // 0, .output_tokens // 0] | @tsv"
|
|
235
|
+
completion=$(echo "$events_json" | jq -r ".[] | select(.type == \"pipeline.completed\" and .issue == $issue) | [.result // \"unknown\", .duration_s // 0, .input_tokens // 0, .output_tokens // 0] | @tsv" 2>/dev/null | head -1)
|
|
238
236
|
|
|
239
237
|
local result duration input_tokens output_tokens
|
|
240
238
|
if [[ -n "$completion" ]]; then
|
|
@@ -251,7 +249,7 @@ cmd_narrative() {
|
|
|
251
249
|
|
|
252
250
|
# Count stages
|
|
253
251
|
local stage_count
|
|
254
|
-
stage_count=$(jq -r "select(.issue == $issue and .type == \"stage.completed\") | .stage"
|
|
252
|
+
stage_count=$(echo "$events_json" | jq -r ".[] | select(.issue == $issue and .type == \"stage.completed\") | .stage" 2>/dev/null | wc -l)
|
|
255
253
|
|
|
256
254
|
# Build narrative
|
|
257
255
|
info "Pipeline Narrative"
|
|
@@ -267,9 +265,9 @@ cmd_narrative() {
|
|
|
267
265
|
|
|
268
266
|
# Key events
|
|
269
267
|
local retry_count build_iterations test_failures
|
|
270
|
-
retry_count=$(jq -r "select(.issue == $issue and .type == \"stage.completed\" and .result == \"retry\") | .stage"
|
|
271
|
-
build_iterations=$(jq -r "select(.issue == $issue and .type == \"build.iteration\") | .iteration"
|
|
272
|
-
test_failures=$(jq -r "select(.issue == $issue and .type == \"test.failed\") | .test"
|
|
268
|
+
retry_count=$(echo "$events_json" | jq -r ".[] | select(.issue == $issue and .type == \"stage.completed\" and .result == \"retry\") | .stage" 2>/dev/null | wc -l)
|
|
269
|
+
build_iterations=$(echo "$events_json" | jq -r ".[] | select(.issue == $issue and .type == \"build.iteration\") | .iteration" 2>/dev/null | tail -1)
|
|
270
|
+
test_failures=$(echo "$events_json" | jq -r ".[] | select(.issue == $issue and .type == \"test.failed\") | .test" 2>/dev/null | wc -l)
|
|
273
271
|
|
|
274
272
|
echo "Key Events:"
|
|
275
273
|
[[ $retry_count -gt 0 ]] && echo " • $retry_count stage retries"
|
|
@@ -290,19 +288,21 @@ cmd_diff() {
|
|
|
290
288
|
exit 1
|
|
291
289
|
fi
|
|
292
290
|
|
|
293
|
-
if ! command -v git
|
|
291
|
+
if ! command -v git >/dev/null 2>&1; then
|
|
294
292
|
error "git is required for diff subcommand"
|
|
295
293
|
exit 1
|
|
296
294
|
fi
|
|
297
295
|
|
|
298
|
-
|
|
296
|
+
local events_json
|
|
297
|
+
events_json=$(db_query_events "" 5000 2>/dev/null || echo "[]")
|
|
298
|
+
if [[ "$events_json" == "[]" ]] || [[ -z "$events_json" ]]; then
|
|
299
299
|
error "No events recorded yet"
|
|
300
300
|
exit 1
|
|
301
301
|
fi
|
|
302
302
|
|
|
303
303
|
# Check if issue was processed
|
|
304
304
|
local found
|
|
305
|
-
found=$(jq -r "select(.issue == $issue and .type == \"pipeline.completed\") | .issue"
|
|
305
|
+
found=$(echo "$events_json" | jq -r ".[] | select(.issue == $issue and .type == \"pipeline.completed\") | .issue" 2>/dev/null | head -1)
|
|
306
306
|
|
|
307
307
|
if [[ -z "$found" ]]; then
|
|
308
308
|
error "No pipeline run found for issue #$issue"
|
|
@@ -317,7 +317,7 @@ cmd_diff() {
|
|
|
317
317
|
|
|
318
318
|
# Also try to find by branch name pattern
|
|
319
319
|
local branch_pattern="issue-${issue}"
|
|
320
|
-
if git show-ref --verify "refs/heads/$branch_pattern"
|
|
320
|
+
if git show-ref --verify "refs/heads/$branch_pattern" >/dev/null 2>&1; then
|
|
321
321
|
echo ""
|
|
322
322
|
info "Commits on branch '$branch_pattern':"
|
|
323
323
|
git log "$branch_pattern" --oneline || true
|
|
@@ -337,13 +337,15 @@ cmd_export() {
|
|
|
337
337
|
exit 1
|
|
338
338
|
fi
|
|
339
339
|
|
|
340
|
-
|
|
340
|
+
local events_json
|
|
341
|
+
events_json=$(db_query_events "" 5000 2>/dev/null || echo "[]")
|
|
342
|
+
if [[ "$events_json" == "[]" ]] || [[ -z "$events_json" ]]; then
|
|
341
343
|
error "No events recorded yet"
|
|
342
344
|
exit 1
|
|
343
345
|
fi
|
|
344
346
|
|
|
345
347
|
local pipeline_start
|
|
346
|
-
pipeline_start=$(jq -r "select(.type == \"pipeline.started\" and .issue == $issue) | [.ts, .goal // \"\", .pipeline // \"standard\"] | @tsv"
|
|
348
|
+
pipeline_start=$(echo "$events_json" | jq -r ".[] | select(.type == \"pipeline.started\" and .issue == $issue) | [.ts, .goal // \"\", .pipeline // \"standard\"] | @tsv" 2>/dev/null | head -1)
|
|
347
349
|
|
|
348
350
|
if [[ -z "$pipeline_start" ]]; then
|
|
349
351
|
error "No pipeline run found for issue #$issue"
|
|
@@ -356,7 +358,7 @@ cmd_export() {
|
|
|
356
358
|
pipeline_type=$(echo "$pipeline_start" | cut -f3)
|
|
357
359
|
|
|
358
360
|
local completion
|
|
359
|
-
completion=$(jq -r "select(.type == \"pipeline.completed\" and .issue == $issue) | [.result // \"unknown\", .duration_s // 0] | @tsv"
|
|
361
|
+
completion=$(echo "$events_json" | jq -r ".[] | select(.type == \"pipeline.completed\" and .issue == $issue) | [.result // \"unknown\", .duration_s // 0] | @tsv" 2>/dev/null | head -1)
|
|
360
362
|
|
|
361
363
|
local result duration
|
|
362
364
|
if [[ -n "$completion" ]]; then
|
|
@@ -386,7 +388,7 @@ $goal
|
|
|
386
388
|
EOF
|
|
387
389
|
|
|
388
390
|
# Add stage rows
|
|
389
|
-
jq -r "select(.issue == $issue and .type == \"stage.completed\") | [.stage, .duration_s // 0, .result // \"success\"] | @tsv"
|
|
391
|
+
echo "$events_json" | jq -r ".[] | select(.issue == $issue and .type == \"stage.completed\") | [.stage, .duration_s // 0, .result // \"success\"] | @tsv" 2>/dev/null | \
|
|
390
392
|
while IFS=$'\t' read -r stage duration result; do
|
|
391
393
|
printf "| %s | %s | %s |\n" "$stage" "$(format_duration "$duration")" "$result"
|
|
392
394
|
done
|
|
@@ -402,7 +404,7 @@ EOF
|
|
|
402
404
|
|
|
403
405
|
## Events
|
|
404
406
|
|
|
405
|
-
$(jq -r "select(.issue == $issue) | [.ts, .type] | @tsv"
|
|
407
|
+
$(echo "$events_json" | jq -r ".[] | select(.issue == $issue) | [.ts, .type] | @tsv" 2>/dev/null | awk '{print "- " $1 " — " $2}')
|
|
406
408
|
|
|
407
409
|
EOF
|
|
408
410
|
|
|
@@ -420,7 +422,9 @@ cmd_compare() {
|
|
|
420
422
|
exit 1
|
|
421
423
|
fi
|
|
422
424
|
|
|
423
|
-
|
|
425
|
+
local events_json
|
|
426
|
+
events_json=$(db_query_events "" 5000 2>/dev/null || echo "[]")
|
|
427
|
+
if [[ "$events_json" == "[]" ]] || [[ -z "$events_json" ]]; then
|
|
424
428
|
error "No events recorded yet"
|
|
425
429
|
exit 1
|
|
426
430
|
fi
|
|
@@ -430,8 +434,8 @@ cmd_compare() {
|
|
|
430
434
|
|
|
431
435
|
# Get both runs
|
|
432
436
|
local run1 run2
|
|
433
|
-
run1=$(jq -r "select(.type == \"pipeline.started\" and .issue == $issue1) | [.goal // \"\", .pipeline, .model] | @tsv"
|
|
434
|
-
run2=$(jq -r "select(.type == \"pipeline.started\" and .issue == $issue2) | [.goal // \"\", .pipeline, .model] | @tsv"
|
|
437
|
+
run1=$(echo "$events_json" | jq -r ".[] | select(.type == \"pipeline.started\" and .issue == $issue1) | [.goal // \"\", .pipeline, .model] | @tsv" 2>/dev/null | head -1)
|
|
438
|
+
run2=$(echo "$events_json" | jq -r ".[] | select(.type == \"pipeline.started\" and .issue == $issue2) | [.goal // \"\", .pipeline, .model] | @tsv" 2>/dev/null | head -1)
|
|
435
439
|
|
|
436
440
|
if [[ -z "$run1" || -z "$run2" ]]; then
|
|
437
441
|
error "Could not find both pipeline runs"
|
|
@@ -451,8 +455,8 @@ cmd_compare() {
|
|
|
451
455
|
|
|
452
456
|
# Get completions
|
|
453
457
|
local comp1 comp2
|
|
454
|
-
comp1=$(jq -r "select(.type == \"pipeline.completed\" and .issue == $issue1) | [.result // \"unknown\", .duration_s // 0] | @tsv"
|
|
455
|
-
comp2=$(jq -r "select(.type == \"pipeline.completed\" and .issue == $issue2) | [.result // \"unknown\", .duration_s // 0] | @tsv"
|
|
458
|
+
comp1=$(echo "$events_json" | jq -r ".[] | select(.type == \"pipeline.completed\" and .issue == $issue1) | [.result // \"unknown\", .duration_s // 0] | @tsv" 2>/dev/null | head -1)
|
|
459
|
+
comp2=$(echo "$events_json" | jq -r ".[] | select(.type == \"pipeline.completed\" and .issue == $issue2) | [.result // \"unknown\", .duration_s // 0] | @tsv" 2>/dev/null | head -1)
|
|
456
460
|
|
|
457
461
|
local result1 duration1 result2 duration2
|
|
458
462
|
result1=$(echo "$comp1" | cut -f1)
|
|
@@ -497,7 +501,7 @@ ${BOLD}EXAMPLES${RESET}
|
|
|
497
501
|
shipwright replay export 42 # Markdown report for #42
|
|
498
502
|
shipwright replay compare 42 43 # Compare two runs
|
|
499
503
|
|
|
500
|
-
${DIM}Pipeline events are read from: $
|
|
504
|
+
${DIM}Pipeline events are read from: DB + JSONL fallback${RESET}
|
|
501
505
|
|
|
502
506
|
EOF
|
|
503
507
|
}
|
package/scripts/sw-retro.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="
|
|
9
|
+
VERSION="3.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
|
|
12
12
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
@@ -16,6 +16,9 @@ _COMPAT="$SCRIPT_DIR/lib/compat.sh"
|
|
|
16
16
|
# Canonical helpers (colors, output, events)
|
|
17
17
|
# shellcheck source=lib/helpers.sh
|
|
18
18
|
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
19
|
+
# DB layer for dual-read (SQLite + JSONL fallback)
|
|
20
|
+
# shellcheck source=sw-db.sh
|
|
21
|
+
[[ -f "$SCRIPT_DIR/sw-db.sh" ]] && source "$SCRIPT_DIR/sw-db.sh"
|
|
19
22
|
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
20
23
|
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
21
24
|
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
@@ -25,24 +28,7 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
|
25
28
|
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
26
29
|
now_epoch() { date +%s; }
|
|
27
30
|
fi
|
|
28
|
-
|
|
29
|
-
emit_event() {
|
|
30
|
-
local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
|
|
31
|
-
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
32
|
-
while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
|
|
33
|
-
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
34
|
-
}
|
|
35
|
-
fi
|
|
36
|
-
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
37
|
-
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
38
|
-
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
39
|
-
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
40
|
-
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
41
|
-
RED="${RED:-\033[38;2;248;113;113m}"
|
|
42
|
-
DIM="${DIM:-\033[2m}"
|
|
43
|
-
BOLD="${BOLD:-\033[1m}"
|
|
44
|
-
RESET="${RESET:-\033[0m}"
|
|
45
|
-
|
|
31
|
+
# epoch_to_iso from compat.sh (cross-platform: BSD date -r first, then GNU -d)
|
|
46
32
|
epoch_to_iso() {
|
|
47
33
|
local epoch="$1"
|
|
48
34
|
date -u -r "$epoch" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
|
|
@@ -74,11 +60,9 @@ get_sprint_dates() {
|
|
|
74
60
|
local to_date="${2:-}"
|
|
75
61
|
|
|
76
62
|
if [[ -z "$from_date" ]]; then
|
|
77
|
-
# Default: last 7 days
|
|
63
|
+
# Default: last 7 days (cross-platform: date_days_ago from compat.sh)
|
|
78
64
|
to_date=$(date -u +"%Y-%m-%d")
|
|
79
|
-
from_date=$(
|
|
80
|
-
date -u -d "7 days ago" +"%Y-%m-%d" 2>/dev/null || \
|
|
81
|
-
python3 -c "from datetime import datetime, timedelta; print((datetime.utcnow() - timedelta(days=7)).strftime('%Y-%m-%d'))")
|
|
65
|
+
from_date=$(date_days_ago 7 2>/dev/null || python3 -c "from datetime import datetime, timedelta; print((datetime.utcnow() - timedelta(days=7)).strftime('%Y-%m-%d'))")
|
|
82
66
|
elif [[ -z "$to_date" ]]; then
|
|
83
67
|
to_date=$(date -u +"%Y-%m-%d")
|
|
84
68
|
fi
|
|
@@ -91,26 +75,19 @@ analyze_sprint_data() {
|
|
|
91
75
|
local from_date="$1"
|
|
92
76
|
local to_date="$2"
|
|
93
77
|
|
|
94
|
-
|
|
95
|
-
if [[ ! -f "$events_file" ]]; then
|
|
96
|
-
echo '{"pipelines":0,"succeeded":0,"failed":0,"retries":0,"avg_duration":0,"avg_stages":0,"slowest_stage":"","quality_score":0}'
|
|
97
|
-
return 0
|
|
98
|
-
fi
|
|
99
|
-
|
|
100
|
-
if ! command -v jq &>/dev/null; then
|
|
78
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
101
79
|
error "jq is required for sprint analysis"
|
|
102
80
|
return 1
|
|
103
81
|
fi
|
|
104
82
|
|
|
105
|
-
# Convert dates to epoch
|
|
83
|
+
# Convert dates to epoch (cross-platform: date_to_epoch from compat.sh)
|
|
106
84
|
local from_epoch to_epoch
|
|
107
|
-
from_epoch=$(
|
|
108
|
-
|
|
109
|
-
to_epoch=$(date -u -d "${to_date}T23:59:59Z" +%s 2>/dev/null || \
|
|
110
|
-
date -u -r "$(date -d "${to_date}T23:59:59Z" +%s 2>/dev/null || echo 0)" +%s || echo 0)
|
|
85
|
+
from_epoch=$(date_to_epoch "${from_date}T00:00:00Z")
|
|
86
|
+
to_epoch=$(date_to_epoch "${to_date}T23:59:59Z")
|
|
111
87
|
|
|
112
|
-
|
|
113
|
-
|
|
88
|
+
# Use db_query_events_since (SQLite + JSONL fallback)
|
|
89
|
+
db_query_events_since "$from_epoch" "" "$to_epoch" | jq --argjson from "$from_epoch" --argjson to "$to_epoch" '
|
|
90
|
+
(if type == "array" then . else [] end) as $events |
|
|
114
91
|
[$events[] | select(.type == "pipeline.completed")] as $completed |
|
|
115
92
|
($completed | length) as $total_pipelines |
|
|
116
93
|
[$completed[] | select(.result == "success")] as $successes |
|
|
@@ -132,7 +109,7 @@ analyze_sprint_data() {
|
|
|
132
109
|
slowest_stage: $slowest,
|
|
133
110
|
quality_score: $quality
|
|
134
111
|
}
|
|
135
|
-
'
|
|
112
|
+
' 2>/dev/null || echo '{"pipelines":0,"succeeded":0,"failed":0,"retries":0,"avg_duration":0,"avg_stages":0,"slowest_stage":"","quality_score":0}'
|
|
136
113
|
}
|
|
137
114
|
|
|
138
115
|
# ─── Agent Performance Analysis ─────────────────────────────────────────────
|
|
@@ -140,23 +117,17 @@ analyze_agent_performance() {
|
|
|
140
117
|
local from_date="$1"
|
|
141
118
|
local to_date="$2"
|
|
142
119
|
|
|
143
|
-
|
|
144
|
-
if [[ ! -f "$events_file" ]]; then
|
|
145
|
-
echo '{"agents":[]}'
|
|
146
|
-
return 0
|
|
147
|
-
fi
|
|
148
|
-
|
|
149
|
-
if ! command -v jq &>/dev/null; then
|
|
120
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
150
121
|
echo '{"agents":[]}'
|
|
151
122
|
return 0
|
|
152
123
|
fi
|
|
153
124
|
|
|
154
125
|
local from_epoch to_epoch
|
|
155
|
-
from_epoch=$(
|
|
156
|
-
to_epoch=$(
|
|
126
|
+
from_epoch=$(date_to_epoch "${from_date}T00:00:00Z")
|
|
127
|
+
to_epoch=$(date_to_epoch "${to_date}T23:59:59Z")
|
|
157
128
|
|
|
158
|
-
jq
|
|
159
|
-
|
|
129
|
+
db_query_events_since "$from_epoch" "" "$to_epoch" | jq --argjson from "$from_epoch" --argjson to "$to_epoch" '
|
|
130
|
+
(if type == "array" then . else [] end) as $events |
|
|
160
131
|
[$events[] | select(.type == "pipeline.completed" and (.agent_id // .agent))] as $completions |
|
|
161
132
|
$completions | group_by(.agent_id // .agent) | map({
|
|
162
133
|
agent: .[0].agent_id // .[0].agent,
|
|
@@ -166,7 +137,7 @@ analyze_agent_performance() {
|
|
|
166
137
|
avg_duration: (([.[].duration_s // 0] | add / length) | floor)
|
|
167
138
|
}) | sort_by(-.completed) as $agent_stats |
|
|
168
139
|
{ agents: $agent_stats }
|
|
169
|
-
'
|
|
140
|
+
' 2>/dev/null || echo '{"agents":[]}'
|
|
170
141
|
}
|
|
171
142
|
|
|
172
143
|
# ─── Velocity & Trends ──────────────────────────────────────────────────────
|
|
@@ -174,21 +145,15 @@ analyze_velocity() {
|
|
|
174
145
|
local from_date="$1"
|
|
175
146
|
local to_date="$2"
|
|
176
147
|
|
|
177
|
-
|
|
178
|
-
if [[ ! -f "$events_file" ]]; then
|
|
148
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
179
149
|
echo '{"current":0,"previous":0,"trend":"→"}'
|
|
180
150
|
return 0
|
|
181
151
|
fi
|
|
182
152
|
|
|
183
|
-
|
|
184
|
-
echo '{"current":0,"previous":0,"trend":"→"}'
|
|
185
|
-
return 0
|
|
186
|
-
fi
|
|
187
|
-
|
|
188
|
-
# Get current period
|
|
153
|
+
# Get current period (cross-platform: date_to_epoch from compat.sh)
|
|
189
154
|
local from_epoch to_epoch prev_from_epoch prev_to_epoch
|
|
190
|
-
from_epoch=$(
|
|
191
|
-
to_epoch=$(
|
|
155
|
+
from_epoch=$(date_to_epoch "${from_date}T00:00:00Z")
|
|
156
|
+
to_epoch=$(date_to_epoch "${to_date}T23:59:59Z")
|
|
192
157
|
|
|
193
158
|
# Get previous period (same duration before current)
|
|
194
159
|
local duration_days
|
|
@@ -196,24 +161,26 @@ analyze_velocity() {
|
|
|
196
161
|
prev_to_epoch=$from_epoch
|
|
197
162
|
prev_from_epoch=$((from_epoch - (duration_days * 86400)))
|
|
198
163
|
|
|
199
|
-
|
|
164
|
+
# Need full event range for current + previous; use db_query_events since prev_from may be before from
|
|
165
|
+
db_query_events "" 10000 | jq --argjson curr_from "$from_epoch" --argjson curr_to "$to_epoch" \
|
|
200
166
|
--argjson prev_from "$prev_from_epoch" --argjson prev_to "$prev_to_epoch" '
|
|
201
|
-
|
|
202
|
-
[
|
|
167
|
+
(if type == "array" then . else [] end) as $all |
|
|
168
|
+
[$all[] | select(.ts_epoch >= $curr_from and .ts_epoch <= $curr_to and .type == "pipeline.completed" and .result == "success")] | length as $current |
|
|
169
|
+
[$all[] | select(.ts_epoch >= $prev_from and .ts_epoch <= $prev_to and .type == "pipeline.completed" and .result == "success")] | length as $previous |
|
|
203
170
|
(if $previous > 0 and $current > $previous then "↑" elif $current < $previous then "↓" else "→" end) as $trend |
|
|
204
171
|
{
|
|
205
172
|
current: $current,
|
|
206
173
|
previous: $previous,
|
|
207
174
|
trend: $trend
|
|
208
175
|
}
|
|
209
|
-
'
|
|
176
|
+
' 2>/dev/null || echo '{"current":0,"previous":0,"trend":"→"}'
|
|
210
177
|
}
|
|
211
178
|
|
|
212
179
|
# ─── Generate Insights & Actions ────────────────────────────────────────────
|
|
213
180
|
generate_improvement_actions() {
|
|
214
181
|
local analysis_json="$1"
|
|
215
182
|
|
|
216
|
-
if ! command -v jq
|
|
183
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
217
184
|
echo '{"actions":[]}'
|
|
218
185
|
return 0
|
|
219
186
|
fi
|
|
@@ -253,18 +220,18 @@ generate_improvement_actions() {
|
|
|
253
220
|
create_action_issues() {
|
|
254
221
|
local actions_json="$1"
|
|
255
222
|
|
|
256
|
-
if ! command -v gh
|
|
223
|
+
if ! command -v gh >/dev/null 2>&1; then
|
|
257
224
|
warn "GitHub CLI (gh) not found. Skipping issue creation."
|
|
258
225
|
return 1
|
|
259
226
|
fi
|
|
260
227
|
|
|
261
|
-
if ! command -v jq
|
|
228
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
262
229
|
warn "jq not found. Skipping issue creation."
|
|
263
230
|
return 1
|
|
264
231
|
fi
|
|
265
232
|
|
|
266
233
|
local action_count
|
|
267
|
-
action_count=$(echo "$actions_json" | jq '.actions | length')
|
|
234
|
+
action_count=$(echo "$actions_json" | jq '.actions | length' 2>/dev/null || echo "0")
|
|
268
235
|
|
|
269
236
|
for ((i = 0; i < action_count; i++)); do
|
|
270
237
|
local title description label priority
|
|
@@ -367,7 +334,7 @@ generate_retro_report() {
|
|
|
367
334
|
} > "$report_file"
|
|
368
335
|
|
|
369
336
|
# Add agent stats
|
|
370
|
-
if command -v jq
|
|
337
|
+
if command -v jq >/dev/null 2>&1; then
|
|
371
338
|
local agent_count
|
|
372
339
|
agent_count=$(echo "$agent_json" | jq '.agents | length' 2>/dev/null || echo 0)
|
|
373
340
|
for ((i = 0; i < agent_count; i++)); do
|
|
@@ -446,12 +413,12 @@ cmd_run() {
|
|
|
446
413
|
|
|
447
414
|
# Display summary
|
|
448
415
|
echo -e "${BOLD}Sprint Summary${RESET}"
|
|
449
|
-
if command -v jq
|
|
416
|
+
if command -v jq >/dev/null 2>&1; then
|
|
450
417
|
local pipelines succeeded failed quality_score
|
|
451
|
-
pipelines=$(echo "$analysis" | jq -r '.pipelines')
|
|
452
|
-
succeeded=$(echo "$analysis" | jq -r '.succeeded')
|
|
453
|
-
failed=$(echo "$analysis" | jq -r '.failed')
|
|
454
|
-
quality_score=$(echo "$analysis" | jq -r '.quality_score')
|
|
418
|
+
pipelines=$(echo "$analysis" | jq -r '.pipelines // 0')
|
|
419
|
+
succeeded=$(echo "$analysis" | jq -r '.succeeded // 0')
|
|
420
|
+
failed=$(echo "$analysis" | jq -r '.failed // 0')
|
|
421
|
+
quality_score=$(echo "$analysis" | jq -r '.quality_score // 0')
|
|
455
422
|
|
|
456
423
|
echo "Pipelines: $pipelines total | ${GREEN}$succeeded succeeded${RESET} | ${RED}$failed failed${RESET}"
|
|
457
424
|
echo "Success Rate: ${quality_score}%"
|
|
@@ -469,7 +436,7 @@ cmd_run() {
|
|
|
469
436
|
[[ -f "$SCRIPT_DIR/sw-self-optimize.sh" ]] && "$SCRIPT_DIR/sw-self-optimize.sh" ingest-retro || true
|
|
470
437
|
|
|
471
438
|
# Offer to create issues
|
|
472
|
-
if command -v gh
|
|
439
|
+
if command -v gh >/dev/null 2>&1; then
|
|
473
440
|
echo ""
|
|
474
441
|
info "Create improvement issues? (y/n)"
|
|
475
442
|
read -r -t 5 response || response="n"
|
|
@@ -492,7 +459,7 @@ cmd_summary() {
|
|
|
492
459
|
local analysis
|
|
493
460
|
analysis=$(analyze_sprint_data "$from_date" "$to_date")
|
|
494
461
|
|
|
495
|
-
if command -v jq
|
|
462
|
+
if command -v jq >/dev/null 2>&1; then
|
|
496
463
|
echo "$analysis" | jq '.'
|
|
497
464
|
else
|
|
498
465
|
echo "$analysis"
|
|
@@ -503,8 +470,9 @@ cmd_trends() {
|
|
|
503
470
|
info "Multi-Sprint Trend Analysis"
|
|
504
471
|
echo ""
|
|
505
472
|
|
|
506
|
-
local
|
|
507
|
-
|
|
473
|
+
local event_check
|
|
474
|
+
event_check=$(db_query_events "" 1 2>/dev/null || echo "[]")
|
|
475
|
+
if [[ "$event_check" == "[]" ]] || [[ -z "$event_check" ]]; then
|
|
508
476
|
error "No event data found. Run pipelines first."
|
|
509
477
|
return 1
|
|
510
478
|
fi
|
|
@@ -518,15 +486,13 @@ cmd_trends() {
|
|
|
518
486
|
offset_end=$((i * 7))
|
|
519
487
|
offset_start=$(((i + 1) * 7))
|
|
520
488
|
|
|
521
|
-
end_date=$(
|
|
522
|
-
|
|
523
|
-
start_date=$(date -u -v-${offset_start}d +"%Y-%m-%d" 2>/dev/null || \
|
|
524
|
-
date -u -d "${offset_start} days ago" +"%Y-%m-%d" 2>/dev/null || echo "$today")
|
|
489
|
+
end_date=$(date_days_ago "$offset_end" 2>/dev/null || echo "$today")
|
|
490
|
+
start_date=$(date_days_ago "$offset_start" 2>/dev/null || echo "$today")
|
|
525
491
|
|
|
526
492
|
local analysis
|
|
527
493
|
analysis=$(analyze_sprint_data "$start_date" "$end_date")
|
|
528
494
|
|
|
529
|
-
if command -v jq
|
|
495
|
+
if command -v jq >/dev/null 2>&1; then
|
|
530
496
|
local quality pipelines
|
|
531
497
|
quality=$(echo "$analysis" | jq -r '.quality_score')
|
|
532
498
|
pipelines=$(echo "$analysis" | jq -r '.pipelines')
|
|
@@ -548,7 +514,7 @@ cmd_agents() {
|
|
|
548
514
|
local agent_perf
|
|
549
515
|
agent_perf=$(analyze_agent_performance "$from_date" "$to_date")
|
|
550
516
|
|
|
551
|
-
if command -v jq
|
|
517
|
+
if command -v jq >/dev/null 2>&1; then
|
|
552
518
|
echo "$agent_perf" | jq '.agents[] | "\(.agent): \(.completed) completed, \(.succeeded) succeeded, \(.failed) failed"' -r
|
|
553
519
|
else
|
|
554
520
|
echo "$agent_perf"
|
|
@@ -569,7 +535,7 @@ cmd_actions() {
|
|
|
569
535
|
analysis=$(analyze_sprint_data "$from_date" "$to_date")
|
|
570
536
|
improvements=$(generate_improvement_actions "$analysis")
|
|
571
537
|
|
|
572
|
-
if command -v jq
|
|
538
|
+
if command -v jq >/dev/null 2>&1; then
|
|
573
539
|
echo "$improvements" | jq '.actions[] | "\(.priority | ascii_upcase): \(.title)\n \(.description)"' -r
|
|
574
540
|
else
|
|
575
541
|
echo "$improvements"
|
|
@@ -589,10 +555,10 @@ cmd_compare() {
|
|
|
589
555
|
echo ""
|
|
590
556
|
|
|
591
557
|
local analysis1 analysis2
|
|
592
|
-
analysis1=$(analyze_sprint_data "$period1" "$(
|
|
593
|
-
analysis2=$(analyze_sprint_data "$period2" "$(
|
|
558
|
+
analysis1=$(analyze_sprint_data "$period1" "$(date_add_days "$period1" 7)")
|
|
559
|
+
analysis2=$(analyze_sprint_data "$period2" "$(date_add_days "$period2" 7)")
|
|
594
560
|
|
|
595
|
-
if command -v jq
|
|
561
|
+
if command -v jq >/dev/null 2>&1; then
|
|
596
562
|
echo "Sprint 1 (${period1}):"
|
|
597
563
|
echo "$analysis1" | jq '.'
|
|
598
564
|
echo ""
|
|
@@ -623,12 +589,13 @@ Usage: shipwright retro <subcommand> [options]
|
|
|
623
589
|
Subcommands:
|
|
624
590
|
run [--from DATE] [--to DATE] Run retrospective for sprint (default: last 7 days)
|
|
625
591
|
summary [DATE1] [DATE2] Quick sprint summary stats
|
|
626
|
-
trends
|
|
592
|
+
trends Multi-sprint trend analysis (last 4 sprints)
|
|
627
593
|
agents [DATE1] [DATE2] Agent performance breakdown
|
|
628
594
|
actions [DATE1] [DATE2] List generated improvement actions
|
|
629
|
-
compare DATE1 DATE2
|
|
630
|
-
history
|
|
631
|
-
|
|
595
|
+
compare DATE1 DATE2 Compare two sprint periods
|
|
596
|
+
history Show past retrospective reports
|
|
597
|
+
quality Show quality index trend (longitudinal)
|
|
598
|
+
help Show this help message
|
|
632
599
|
|
|
633
600
|
Options:
|
|
634
601
|
--from DATE Start date (YYYY-MM-DD)
|
|
@@ -673,6 +640,17 @@ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
|
673
640
|
history)
|
|
674
641
|
cmd_history "$@"
|
|
675
642
|
;;
|
|
643
|
+
quality)
|
|
644
|
+
if [[ -f "$SCRIPT_DIR/sw-self-optimize.sh" ]]; then
|
|
645
|
+
source "$SCRIPT_DIR/sw-self-optimize.sh" 2>/dev/null || true
|
|
646
|
+
cmd_quality_index 2>/dev/null || {
|
|
647
|
+
echo "No quality data yet. Run some pipelines first."
|
|
648
|
+
}
|
|
649
|
+
else
|
|
650
|
+
error "sw-self-optimize.sh not found"
|
|
651
|
+
exit 1
|
|
652
|
+
fi
|
|
653
|
+
;;
|
|
676
654
|
help|--help|-h)
|
|
677
655
|
cmd_help
|
|
678
656
|
;;
|