shipwright-cli 1.10.0 → 2.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 +114 -36
- package/completions/_shipwright +212 -32
- package/completions/shipwright.bash +97 -25
- 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/package.json +4 -2
- package/scripts/sw +208 -1
- 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 +664 -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 +637 -0
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +605 -0
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +432 -130
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +540 -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 +59 -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 +471 -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 +1 -1
- 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 +617 -0
- package/scripts/sw-init.sh +88 -1
- 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 +64 -3
- package/scripts/sw-memory.sh +1 -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 +689 -0
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +687 -24
- package/scripts/sw-pm.sh +693 -0
- package/scripts/sw-pr-lifecycle.sh +522 -0
- package/scripts/sw-predictive.sh +1 -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 +573 -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 +1 -1
- package/scripts/sw-standup.sh +712 -0
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +658 -0
- package/scripts/sw-stream.sh +450 -0
- package/scripts/sw-swarm.sh +583 -0
- package/scripts/sw-team-stages.sh +511 -0
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-testgen.sh +515 -0
- package/scripts/sw-tmux-pipeline.sh +554 -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 +603 -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
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright pr-lifecycle — Autonomous PR Management ║
|
|
4
|
+
# ║ Auto-review · Auto-merge · Stale cleanup · Issue feedback ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
|
+
|
|
9
|
+
VERSION="2.0.0"
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
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
|
+
# ─── Output Helpers ─────────────────────────────────────────────────────────
|
|
29
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
30
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
31
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
32
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
33
|
+
|
|
34
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
35
|
+
now_epoch() { date +%s; }
|
|
36
|
+
|
|
37
|
+
# ─── Structured Event Log ──────────────────────────────────────────────────
|
|
38
|
+
EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
|
|
39
|
+
|
|
40
|
+
emit_event() {
|
|
41
|
+
local event_type="$1"
|
|
42
|
+
shift
|
|
43
|
+
local json_fields=""
|
|
44
|
+
for kv in "$@"; do
|
|
45
|
+
local key="${kv%%=*}"
|
|
46
|
+
local val="${kv#*=}"
|
|
47
|
+
if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
|
|
48
|
+
json_fields="${json_fields},\"${key}\":${val}"
|
|
49
|
+
else
|
|
50
|
+
local escaped_val
|
|
51
|
+
escaped_val=$(printf '%s' "$val" | jq -Rs '.' 2>/dev/null || printf '"%s"' "${val//\"/\\\"}")
|
|
52
|
+
json_fields="${json_fields},\"${key}\":${escaped_val}"
|
|
53
|
+
fi
|
|
54
|
+
done
|
|
55
|
+
mkdir -p "${HOME}/.shipwright"
|
|
56
|
+
echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# ─── Configuration Helpers ──────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
get_pr_config() {
|
|
62
|
+
local key="$1"
|
|
63
|
+
local default="${2:-}"
|
|
64
|
+
jq -r ".pr_lifecycle.${key} // \"${default}\"" "$REPO_DIR/.claude/daemon-config.json" 2>/dev/null || echo "$default"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# ─── GitHub API Wrappers ────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
get_pr_info() {
|
|
70
|
+
local pr_number="$1"
|
|
71
|
+
gh pr view "$pr_number" --json number,title,body,state,headRefName,baseRefName,statusCheckRollup,reviews,commits,createdAt,updatedAt 2>/dev/null || return 1
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get_pr_checks_status() {
|
|
75
|
+
local pr_number="$1"
|
|
76
|
+
# Returns: success, failure, pending, or unknown
|
|
77
|
+
gh pr checks "$pr_number" 2>/dev/null | jq -r '.[] | select(.status == "completed") | .conclusion' | sort | uniq -c | sort -rn | head -1 || echo "unknown"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
has_merge_conflicts() {
|
|
81
|
+
local pr_number="$1"
|
|
82
|
+
gh pr view "$pr_number" --json mergeStateStatus 2>/dev/null | jq -r '.mergeStateStatus' | grep -qi "conflicting" && return 0 || return 1
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get_pr_reviews() {
|
|
86
|
+
local pr_number="$1"
|
|
87
|
+
# Returns approved, changes_requested, pending, or none
|
|
88
|
+
gh pr view "$pr_number" --json reviews 2>/dev/null | jq -r '.reviews[] | .state' 2>/dev/null | sort | uniq || echo "none"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get_pr_age_seconds() {
|
|
92
|
+
local pr_number="$1"
|
|
93
|
+
local created_at
|
|
94
|
+
created_at=$(gh pr view "$pr_number" --json createdAt 2>/dev/null | jq -r '.createdAt' | head -1)
|
|
95
|
+
[[ -z "$created_at" ]] && return 1
|
|
96
|
+
local created_epoch
|
|
97
|
+
created_epoch=$(date -d "$created_at" +%s 2>/dev/null || date -jf "%Y-%m-%dT%H:%M:%SZ" "$created_at" +%s 2>/dev/null || echo "0")
|
|
98
|
+
[[ "$created_epoch" -eq 0 ]] && return 1
|
|
99
|
+
echo $(($(now_epoch) - created_epoch))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get_pr_originating_issue() {
|
|
103
|
+
local pr_number="$1"
|
|
104
|
+
# Search PR body for issue reference (closes #N, fixes #N, etc.)
|
|
105
|
+
local body
|
|
106
|
+
body=$(gh pr view "$pr_number" --json body 2>/dev/null | jq -r '.body')
|
|
107
|
+
echo "$body" | grep -oiE '(closes|fixes|resolves) #[0-9]+' | grep -oE '[0-9]+' | head -1
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# ─── Review Pass ────────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
pr_review() {
|
|
113
|
+
local pr_number="$1"
|
|
114
|
+
info "Running review pass on PR #${pr_number}..."
|
|
115
|
+
|
|
116
|
+
# Get PR info
|
|
117
|
+
local pr_info
|
|
118
|
+
pr_info=$(get_pr_info "$pr_number") || {
|
|
119
|
+
error "Failed to fetch PR #${pr_number}"
|
|
120
|
+
return 1
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
local title branch
|
|
124
|
+
title=$(echo "$pr_info" | jq -r '.title')
|
|
125
|
+
branch=$(echo "$pr_info" | jq -r '.headRefName')
|
|
126
|
+
|
|
127
|
+
info "PR: ${title} (branch: ${branch})"
|
|
128
|
+
|
|
129
|
+
# Get diff
|
|
130
|
+
local diff_output
|
|
131
|
+
diff_output=$(gh pr diff "$pr_number" 2>/dev/null) || {
|
|
132
|
+
error "Failed to get PR diff"
|
|
133
|
+
return 1
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if [[ -z "$diff_output" ]]; then
|
|
137
|
+
warn "No diff found for PR #${pr_number}"
|
|
138
|
+
return 1
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# Evaluate quality criteria
|
|
142
|
+
local issues_found=0
|
|
143
|
+
local file_count
|
|
144
|
+
file_count=$(echo "$diff_output" | grep -c '^diff --git' || echo "0")
|
|
145
|
+
|
|
146
|
+
local line_additions
|
|
147
|
+
line_additions=$(echo "$diff_output" | grep -c '^+' || echo "0")
|
|
148
|
+
|
|
149
|
+
local line_deletions
|
|
150
|
+
line_deletions=$(echo "$diff_output" | grep -c '^-' || echo "0")
|
|
151
|
+
|
|
152
|
+
info "Diff analysis: ${file_count} files, +${line_additions}/-${line_deletions} lines"
|
|
153
|
+
|
|
154
|
+
# Check for concerning patterns
|
|
155
|
+
local warnings=""
|
|
156
|
+
if echo "$diff_output" | grep -qE '(HACK|TODO|FIXME|XXX|BROKEN|DEBUG)'; then
|
|
157
|
+
warnings="${warnings}
|
|
158
|
+
- Found HACK/TODO/FIXME markers in code"
|
|
159
|
+
((issues_found++))
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
if echo "$diff_output" | grep -qE 'console\.(log|warn|error)\('; then
|
|
163
|
+
warnings="${warnings}
|
|
164
|
+
- Found console.log statements (should use proper logging)"
|
|
165
|
+
((issues_found++))
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
if [[ $line_additions -gt 500 ]]; then
|
|
169
|
+
warnings="${warnings}
|
|
170
|
+
- Large addition (${line_additions} lines) — consider splitting into smaller PRs"
|
|
171
|
+
((issues_found++))
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
if [[ $file_count -gt 20 ]]; then
|
|
175
|
+
warnings="${warnings}
|
|
176
|
+
- Many files changed (${file_count}) — consider splitting"
|
|
177
|
+
((issues_found++))
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# Post review comment to PR
|
|
181
|
+
local review_body="## Shipwright Auto-Review
|
|
182
|
+
|
|
183
|
+
**Status:** Review complete
|
|
184
|
+
**Files changed:** ${file_count}
|
|
185
|
+
**Lines added/removed:** +${line_additions}/-${line_deletions}"
|
|
186
|
+
|
|
187
|
+
if [[ $issues_found -gt 0 ]]; then
|
|
188
|
+
review_body="${review_body}
|
|
189
|
+
|
|
190
|
+
**Issues found:** ${issues_found}
|
|
191
|
+
${warnings}"
|
|
192
|
+
else
|
|
193
|
+
review_body="${review_body}
|
|
194
|
+
|
|
195
|
+
**Issues found:** 0
|
|
196
|
+
✓ No concerning patterns detected"
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
gh pr comment "$pr_number" --body "$review_body" 2>/dev/null || warn "Failed to post review comment"
|
|
200
|
+
|
|
201
|
+
success "Review complete for PR #${pr_number} (${issues_found} issues found)"
|
|
202
|
+
emit_event "pr.review_complete" "pr=${pr_number}" "issues_found=${issues_found}"
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# ─── Auto-Merge ──────────────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
pr_merge() {
|
|
208
|
+
local pr_number="$1"
|
|
209
|
+
info "Attempting auto-merge of PR #${pr_number}..."
|
|
210
|
+
|
|
211
|
+
# Check if auto-merge is enabled
|
|
212
|
+
local auto_merge_enabled
|
|
213
|
+
auto_merge_enabled=$(get_pr_config "auto_merge_enabled" "false")
|
|
214
|
+
if [[ "$auto_merge_enabled" != "true" ]]; then
|
|
215
|
+
warn "Auto-merge is disabled in configuration"
|
|
216
|
+
return 1
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
# Get PR info
|
|
220
|
+
local pr_info
|
|
221
|
+
pr_info=$(get_pr_info "$pr_number") || {
|
|
222
|
+
error "Failed to fetch PR #${pr_number}"
|
|
223
|
+
return 1
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
local state branch
|
|
227
|
+
state=$(echo "$pr_info" | jq -r '.state')
|
|
228
|
+
branch=$(echo "$pr_info" | jq -r '.headRefName')
|
|
229
|
+
|
|
230
|
+
if [[ "$state" != "OPEN" ]]; then
|
|
231
|
+
warn "PR #${pr_number} is not open (state: ${state})"
|
|
232
|
+
return 1
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
# Check for merge conflicts
|
|
236
|
+
if has_merge_conflicts "$pr_number"; then
|
|
237
|
+
error "PR #${pr_number} has merge conflicts — manual intervention required"
|
|
238
|
+
emit_event "pr.merge_failed" "pr=${pr_number}" "reason=merge_conflicts"
|
|
239
|
+
return 1
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
# Check CI status
|
|
243
|
+
local status_check_rollup
|
|
244
|
+
status_check_rollup=$(echo "$pr_info" | jq -r '.statusCheckRollup[].state' 2>/dev/null | sort | uniq)
|
|
245
|
+
if [[ -z "$status_check_rollup" ]] || echo "$status_check_rollup" | grep -qi "failure\|error"; then
|
|
246
|
+
error "PR #${pr_number} has failing CI checks"
|
|
247
|
+
emit_event "pr.merge_failed" "pr=${pr_number}" "reason=ci_failure"
|
|
248
|
+
return 1
|
|
249
|
+
fi
|
|
250
|
+
|
|
251
|
+
# Check reviews
|
|
252
|
+
local reviews
|
|
253
|
+
reviews=$(get_pr_reviews "$pr_number")
|
|
254
|
+
if [[ "$reviews" == *"CHANGES_REQUESTED"* ]]; then
|
|
255
|
+
error "PR #${pr_number} has requested changes"
|
|
256
|
+
emit_event "pr.merge_failed" "pr=${pr_number}" "reason=changes_requested"
|
|
257
|
+
return 1
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
# Perform squash merge and delete branch
|
|
261
|
+
info "Merging PR #${pr_number} with squash..."
|
|
262
|
+
if gh pr merge "$pr_number" --squash --delete-branch 2>/dev/null; then
|
|
263
|
+
success "PR #${pr_number} merged and branch deleted"
|
|
264
|
+
emit_event "pr.merged" "pr=${pr_number}"
|
|
265
|
+
|
|
266
|
+
# Post feedback to originating issue
|
|
267
|
+
local issue_number
|
|
268
|
+
issue_number=$(get_pr_originating_issue "$pr_number")
|
|
269
|
+
if [[ -n "$issue_number" ]]; then
|
|
270
|
+
pr_feedback_to_issue "$issue_number" "$pr_number"
|
|
271
|
+
fi
|
|
272
|
+
return 0
|
|
273
|
+
else
|
|
274
|
+
error "Failed to merge PR #${pr_number}"
|
|
275
|
+
emit_event "pr.merge_failed" "pr=${pr_number}" "reason=merge_command_failed"
|
|
276
|
+
return 1
|
|
277
|
+
fi
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# ─── Stale PR Cleanup ────────────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
pr_cleanup() {
|
|
283
|
+
info "Cleaning up stale pull requests..."
|
|
284
|
+
|
|
285
|
+
local stale_days
|
|
286
|
+
stale_days=$(get_pr_config "stale_days" "14")
|
|
287
|
+
|
|
288
|
+
local stale_seconds
|
|
289
|
+
stale_seconds=$((stale_days * 86400))
|
|
290
|
+
|
|
291
|
+
# List all open Shipwright PRs
|
|
292
|
+
local pr_list
|
|
293
|
+
pr_list=$(gh pr list --state open --search "author:@me" --json number,title,createdAt 2>/dev/null || echo "[]")
|
|
294
|
+
|
|
295
|
+
local closed_count=0
|
|
296
|
+
while IFS= read -r line; do
|
|
297
|
+
[[ -z "$line" ]] && continue
|
|
298
|
+
|
|
299
|
+
local pr_number
|
|
300
|
+
pr_number=$(echo "$line" | jq -r '.number')
|
|
301
|
+
local title
|
|
302
|
+
title=$(echo "$line" | jq -r '.title')
|
|
303
|
+
|
|
304
|
+
# Check if this is a Shipwright PR (look for issue references or pipeline markers)
|
|
305
|
+
if ! echo "$title" | grep -qiE '(pipeline|issue|shipwright)'; then
|
|
306
|
+
continue
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
local age_seconds
|
|
310
|
+
age_seconds=$(get_pr_age_seconds "$pr_number") || continue
|
|
311
|
+
|
|
312
|
+
if [[ $age_seconds -gt $stale_seconds ]]; then
|
|
313
|
+
local age_days=$((age_seconds / 86400))
|
|
314
|
+
info "Closing stale PR #${pr_number}: ${title} (${age_days} days old)"
|
|
315
|
+
|
|
316
|
+
local close_comment="## Auto-Closed: Stale PR
|
|
317
|
+
|
|
318
|
+
This PR was automatically closed because it has been open for ${age_days} days without activity.
|
|
319
|
+
|
|
320
|
+
If this PR is still needed:
|
|
321
|
+
1. Reopen it with \`gh pr reopen ${pr_number}\`
|
|
322
|
+
2. Update the branch
|
|
323
|
+
3. Request review
|
|
324
|
+
|
|
325
|
+
${DIM}— Shipwright auto-lifecycle manager${RESET}"
|
|
326
|
+
|
|
327
|
+
gh pr comment "$pr_number" --body "$close_comment" 2>/dev/null || true
|
|
328
|
+
gh pr close "$pr_number" 2>/dev/null && {
|
|
329
|
+
success "Closed PR #${pr_number}"
|
|
330
|
+
((closed_count++))
|
|
331
|
+
emit_event "pr.closed_stale" "pr=${pr_number}" "age_days=${age_days}"
|
|
332
|
+
}
|
|
333
|
+
fi
|
|
334
|
+
done < <(echo "$pr_list" | jq -c '.[]' 2>/dev/null || true)
|
|
335
|
+
|
|
336
|
+
if [[ $closed_count -eq 0 ]]; then
|
|
337
|
+
info "No stale PRs found"
|
|
338
|
+
else
|
|
339
|
+
success "Cleaned up ${closed_count} stale PR(s)"
|
|
340
|
+
fi
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
# ─── Issue Feedback on Merge ──────────────────────────────────────────────────
|
|
344
|
+
|
|
345
|
+
pr_feedback_to_issue() {
|
|
346
|
+
local issue_number="$1"
|
|
347
|
+
local pr_number="$2"
|
|
348
|
+
|
|
349
|
+
[[ -z "$issue_number" ]] && return 0
|
|
350
|
+
|
|
351
|
+
info "Posting merge feedback to issue #${issue_number}..."
|
|
352
|
+
|
|
353
|
+
local feedback_body="## PR Merged
|
|
354
|
+
|
|
355
|
+
The pull request #${pr_number} has been successfully merged into the base branch.
|
|
356
|
+
|
|
357
|
+
**Summary:**
|
|
358
|
+
- Squash merged with clean history
|
|
359
|
+
- Feature branch deleted
|
|
360
|
+
- Ready for deployment
|
|
361
|
+
|
|
362
|
+
${DIM}— Shipwright PR Lifecycle Manager${RESET}"
|
|
363
|
+
|
|
364
|
+
if gh issue comment "$issue_number" --body "$feedback_body" 2>/dev/null; then
|
|
365
|
+
success "Posted feedback to issue #${issue_number}"
|
|
366
|
+
emit_event "issue.pr_merged_feedback" "issue=${issue_number}" "pr=${pr_number}"
|
|
367
|
+
else
|
|
368
|
+
warn "Failed to post feedback to issue #${issue_number}"
|
|
369
|
+
fi
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
# ─── Status Dashboard ────────────────────────────────────────────────────────
|
|
373
|
+
|
|
374
|
+
pr_status() {
|
|
375
|
+
info "Shipwright Pull Requests Status"
|
|
376
|
+
echo ""
|
|
377
|
+
|
|
378
|
+
# Get all open Shipwright PRs
|
|
379
|
+
local pr_list
|
|
380
|
+
pr_list=$(gh pr list --state open --search "author:@me" --json number,title,state,createdAt,reviewDecision,statusCheckRollup 2>/dev/null || echo "[]")
|
|
381
|
+
|
|
382
|
+
if [[ $(echo "$pr_list" | jq 'length') -eq 0 ]]; then
|
|
383
|
+
echo "No open pull requests"
|
|
384
|
+
return 0
|
|
385
|
+
fi
|
|
386
|
+
|
|
387
|
+
echo -e "${BOLD}PR #${TAB}Title${TAB}Age${TAB}Reviews${TAB}CI Status${RESET}"
|
|
388
|
+
echo "─────────────────────────────────────────────────────────"
|
|
389
|
+
|
|
390
|
+
while IFS= read -r line; do
|
|
391
|
+
[[ -z "$line" ]] && continue
|
|
392
|
+
|
|
393
|
+
local pr_number title created_at review_decision
|
|
394
|
+
pr_number=$(echo "$line" | jq -r '.number')
|
|
395
|
+
title=$(echo "$line" | jq -r '.title' | cut -c1-40)
|
|
396
|
+
created_at=$(echo "$line" | jq -r '.createdAt')
|
|
397
|
+
review_decision=$(echo "$line" | jq -r '.reviewDecision // "PENDING"')
|
|
398
|
+
|
|
399
|
+
# Calculate age
|
|
400
|
+
local created_epoch
|
|
401
|
+
created_epoch=$(date -d "$created_at" +%s 2>/dev/null || date -jf "%Y-%m-%dT%H:%M:%SZ" "$created_at" +%s 2>/dev/null || echo "0")
|
|
402
|
+
local age_hours=$((( $(now_epoch) - created_epoch) / 3600))
|
|
403
|
+
local age_str="${age_hours}h"
|
|
404
|
+
[[ $age_hours -gt 24 ]] && age_str="$((age_hours / 24))d"
|
|
405
|
+
|
|
406
|
+
# Get checks status
|
|
407
|
+
local checks_status="unknown"
|
|
408
|
+
if echo "$line" | jq -e '.statusCheckRollup[0]' > /dev/null 2>&1; then
|
|
409
|
+
checks_status=$(echo "$line" | jq -r '.statusCheckRollup[0].state' 2>/dev/null)
|
|
410
|
+
fi
|
|
411
|
+
|
|
412
|
+
# Color-code output
|
|
413
|
+
local status_color="$DIM"
|
|
414
|
+
case "$checks_status" in
|
|
415
|
+
success) status_color="$GREEN" ;;
|
|
416
|
+
failure) status_color="$RED" ;;
|
|
417
|
+
pending) status_color="$YELLOW" ;;
|
|
418
|
+
esac
|
|
419
|
+
|
|
420
|
+
printf "%s%-5s${DIM}│${RESET} %-40s %-5s %-10s %s${status_color}%s${RESET}\n" \
|
|
421
|
+
"$status_color" "#${pr_number}" "$title" "$age_str" "$review_decision" "$DIM" "$checks_status"
|
|
422
|
+
done < <(echo "$pr_list" | jq -c '.[]' 2>/dev/null || true)
|
|
423
|
+
|
|
424
|
+
echo ""
|
|
425
|
+
echo "Legend: APPROVED = Ready to merge | PENDING = Waiting for review | CHANGES_REQUESTED = Needs work"
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
# ─── Patrol Mode (for daemon integration) ────────────────────────────────────
|
|
429
|
+
|
|
430
|
+
pr_patrol() {
|
|
431
|
+
info "Running PR lifecycle patrol..."
|
|
432
|
+
|
|
433
|
+
# Review all open PRs
|
|
434
|
+
info "Phase 1: Reviewing open PRs..."
|
|
435
|
+
local pr_list
|
|
436
|
+
pr_list=$(gh pr list --state open --search "author:@me" --json number 2>/dev/null || echo "[]")
|
|
437
|
+
while IFS= read -r line; do
|
|
438
|
+
[[ -z "$line" ]] && continue
|
|
439
|
+
local pr_number
|
|
440
|
+
pr_number=$(echo "$line" | jq -r '.number')
|
|
441
|
+
pr_review "$pr_number" || true
|
|
442
|
+
done < <(echo "$pr_list" | jq -c '.[]' 2>/dev/null || true)
|
|
443
|
+
|
|
444
|
+
# Attempt merges
|
|
445
|
+
info "Phase 2: Attempting auto-merges..."
|
|
446
|
+
pr_list=$(gh pr list --state open --search "author:@me" --json number 2>/dev/null || echo "[]")
|
|
447
|
+
while IFS= read -r line; do
|
|
448
|
+
[[ -z "$line" ]] && continue
|
|
449
|
+
local pr_number
|
|
450
|
+
pr_number=$(echo "$line" | jq -r '.number')
|
|
451
|
+
pr_merge "$pr_number" || true
|
|
452
|
+
done < <(echo "$pr_list" | jq -c '.[]' 2>/dev/null || true)
|
|
453
|
+
|
|
454
|
+
# Cleanup stale PRs
|
|
455
|
+
info "Phase 3: Cleaning up stale PRs..."
|
|
456
|
+
pr_cleanup || true
|
|
457
|
+
|
|
458
|
+
success "PR lifecycle patrol complete"
|
|
459
|
+
emit_event "pr_patrol.complete"
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
# ─── Help ────────────────────────────────────────────────────────────────────
|
|
463
|
+
|
|
464
|
+
show_help() {
|
|
465
|
+
echo ""
|
|
466
|
+
echo -e "${BOLD}shipwright pr <command>${RESET}"
|
|
467
|
+
echo ""
|
|
468
|
+
echo -e "${BOLD}COMMANDS${RESET}"
|
|
469
|
+
echo -e " ${CYAN}review <number>${RESET} Run review pass on a PR (checks code quality, posts findings)"
|
|
470
|
+
echo -e " ${CYAN}merge <number>${RESET} Attempt auto-merge (checks CI, conflicts, reviews, then merges)"
|
|
471
|
+
echo -e " ${CYAN}cleanup${RESET} Close stale PRs (older than configured days, default 14)"
|
|
472
|
+
echo -e " ${CYAN}status${RESET} Show all open Shipwright PRs with lifecycle state"
|
|
473
|
+
echo -e " ${CYAN}patrol${RESET} Run full PR lifecycle patrol (review + merge + cleanup)"
|
|
474
|
+
echo -e " ${CYAN}help${RESET} Show this help"
|
|
475
|
+
echo ""
|
|
476
|
+
echo -e "${BOLD}EXAMPLES${RESET}"
|
|
477
|
+
echo -e " ${DIM}shipwright pr review 42${RESET} # Review PR #42"
|
|
478
|
+
echo -e " ${DIM}shipwright pr merge 42${RESET} # Try to merge PR #42"
|
|
479
|
+
echo -e " ${DIM}shipwright pr cleanup${RESET} # Close stale PRs"
|
|
480
|
+
echo -e " ${DIM}shipwright pr status${RESET} # Show all open PRs"
|
|
481
|
+
echo -e " ${DIM}shipwright pr patrol${RESET} # Full lifecycle management"
|
|
482
|
+
echo ""
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
# ─── Main ────────────────────────────────────────────────────────────────────
|
|
486
|
+
|
|
487
|
+
main() {
|
|
488
|
+
local cmd="${1:-help}"
|
|
489
|
+
shift 2>/dev/null || true
|
|
490
|
+
|
|
491
|
+
case "$cmd" in
|
|
492
|
+
review)
|
|
493
|
+
[[ -z "${1:-}" ]] && { error "PR number required"; show_help; exit 1; }
|
|
494
|
+
pr_review "$1"
|
|
495
|
+
;;
|
|
496
|
+
merge)
|
|
497
|
+
[[ -z "${1:-}" ]] && { error "PR number required"; show_help; exit 1; }
|
|
498
|
+
pr_merge "$1"
|
|
499
|
+
;;
|
|
500
|
+
cleanup)
|
|
501
|
+
pr_cleanup
|
|
502
|
+
;;
|
|
503
|
+
status)
|
|
504
|
+
pr_status
|
|
505
|
+
;;
|
|
506
|
+
patrol)
|
|
507
|
+
pr_patrol
|
|
508
|
+
;;
|
|
509
|
+
help|--help|-h)
|
|
510
|
+
show_help
|
|
511
|
+
;;
|
|
512
|
+
*)
|
|
513
|
+
error "Unknown command: ${cmd}"
|
|
514
|
+
show_help
|
|
515
|
+
exit 1
|
|
516
|
+
;;
|
|
517
|
+
esac
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
521
|
+
main "$@"
|
|
522
|
+
fi
|
package/scripts/sw-predictive.sh
CHANGED
package/scripts/sw-prep.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="2.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
|
|
12
12
|
# ─── Handle subcommands ───────────────────────────────────────────────────────
|
package/scripts/sw-ps.sh
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# ║ Displays a table of agents running in claude-* tmux windows with ║
|
|
6
6
|
# ║ PID, status, idle time, and pane references. ║
|
|
7
7
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
8
|
-
VERSION="
|
|
8
|
+
VERSION="2.0.0"
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
11
11
|
|