shipwright-cli 2.3.1 → 2.4.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 +82 -20
- package/config/policy.json +160 -2
- package/config/policy.schema.json +162 -1
- package/package.json +14 -2
- package/scripts/sw +1 -1
- package/scripts/sw-activity.sh +1 -1
- package/scripts/sw-adaptive.sh +1 -1
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +1 -1
- package/scripts/sw-autonomous.sh +1 -1
- package/scripts/sw-changelog.sh +1 -1
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +1 -1
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +1 -1
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +1 -1
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +1 -1
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +1 -1
- package/scripts/sw-decompose.sh +1 -1
- package/scripts/sw-deps.sh +1 -1
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +1 -1
- package/scripts/sw-doc-fleet.sh +1 -1
- package/scripts/sw-docs-agent.sh +1 -1
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +1 -1
- package/scripts/sw-dora.sh +1 -1
- package/scripts/sw-durable.sh +1 -1
- package/scripts/sw-e2e-orchestrator.sh +1 -1
- package/scripts/sw-eventbus.sh +1 -1
- package/scripts/sw-evidence.sh +664 -0
- package/scripts/sw-feedback.sh +1 -1
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +1 -1
- package/scripts/sw-fleet-viz.sh +1 -1
- package/scripts/sw-fleet.sh +1 -1
- package/scripts/sw-github-app.sh +1 -1
- 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 +1 -1
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +1 -1
- package/scripts/sw-incident.sh +244 -1
- package/scripts/sw-init.sh +1 -1
- package/scripts/sw-instrument.sh +1 -1
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +1 -1
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +1 -1
- package/scripts/sw-memory.sh +1 -1
- package/scripts/sw-mission-control.sh +1 -1
- package/scripts/sw-model-router.sh +1 -1
- package/scripts/sw-otel.sh +1 -1
- package/scripts/sw-oversight.sh +1 -1
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +1 -1
- package/scripts/sw-pm.sh +1 -1
- package/scripts/sw-pr-lifecycle.sh +177 -5
- 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 +1 -1
- package/scripts/sw-quality.sh +1 -1
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-regression.sh +1 -1
- package/scripts/sw-release-manager.sh +1 -1
- package/scripts/sw-release.sh +1 -1
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +1 -1
- package/scripts/sw-retro.sh +1 -1
- package/scripts/sw-review-rerun.sh +220 -0
- package/scripts/sw-scale.sh +1 -1
- package/scripts/sw-security-audit.sh +1 -1
- 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 +1 -1
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +1 -1
- package/scripts/sw-stream.sh +1 -1
- package/scripts/sw-swarm.sh +1 -1
- package/scripts/sw-team-stages.sh +1 -1
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-testgen.sh +1 -1
- package/scripts/sw-tmux-pipeline.sh +1 -1
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +1 -1
- package/scripts/sw-tracker.sh +1 -1
- package/scripts/sw-triage.sh +1 -1
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +1 -1
- package/scripts/sw-webhook.sh +1 -1
- package/scripts/sw-widgets.sh +1 -1
- package/scripts/sw-worktree.sh +1 -1
|
@@ -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="2.
|
|
9
|
+
VERSION="2.4.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -56,7 +56,12 @@ get_pr_config() {
|
|
|
56
56
|
|
|
57
57
|
get_pr_info() {
|
|
58
58
|
local pr_number="$1"
|
|
59
|
-
gh pr view "$pr_number" --json number,title,body,state,headRefName,baseRefName,statusCheckRollup,reviews,commits,createdAt,updatedAt 2>/dev/null || return 1
|
|
59
|
+
gh pr view "$pr_number" --json number,title,body,state,headRefName,baseRefName,statusCheckRollup,reviews,commits,createdAt,updatedAt,headRefOid 2>/dev/null || return 1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get_pr_head_sha() {
|
|
63
|
+
local pr_number="$1"
|
|
64
|
+
gh pr view "$pr_number" --json headRefOid --jq '.headRefOid' 2>/dev/null || return 1
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
get_pr_checks_status() {
|
|
@@ -95,6 +100,144 @@ get_pr_originating_issue() {
|
|
|
95
100
|
echo "$body" | grep -oiE '(closes|fixes|resolves) #[0-9]+' | grep -oE '[0-9]+' | head -1
|
|
96
101
|
}
|
|
97
102
|
|
|
103
|
+
# ─── Current-Head SHA Discipline ─────────────────────────────────────────────
|
|
104
|
+
# All check results and review approvals MUST correspond to the current PR head
|
|
105
|
+
# SHA. Stale evidence from older commits is never trusted. This is the single
|
|
106
|
+
# most important safety invariant in the Code Factory pattern.
|
|
107
|
+
|
|
108
|
+
validate_checks_for_head_sha() {
|
|
109
|
+
local pr_number="$1"
|
|
110
|
+
local head_sha="$2"
|
|
111
|
+
|
|
112
|
+
if [[ -z "$head_sha" ]]; then
|
|
113
|
+
error "No head SHA provided — cannot validate check freshness"
|
|
114
|
+
return 1
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
local short_sha="${head_sha:0:7}"
|
|
118
|
+
|
|
119
|
+
# Get check runs for the current head SHA
|
|
120
|
+
local owner_repo
|
|
121
|
+
owner_repo=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || echo "")
|
|
122
|
+
if [[ -z "$owner_repo" ]]; then
|
|
123
|
+
warn "Could not detect repo — skipping SHA discipline check"
|
|
124
|
+
return 0
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
local check_runs
|
|
128
|
+
check_runs=$(gh api "repos/${owner_repo}/commits/${head_sha}/check-runs" --jq '.check_runs' 2>/dev/null || echo "[]")
|
|
129
|
+
|
|
130
|
+
local total_checks
|
|
131
|
+
total_checks=$(echo "$check_runs" | jq 'length' 2>/dev/null || echo "0")
|
|
132
|
+
|
|
133
|
+
if [[ "$total_checks" -eq 0 ]]; then
|
|
134
|
+
warn "No check runs found for head SHA ${short_sha}"
|
|
135
|
+
return 0
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
local failed_checks
|
|
139
|
+
failed_checks=$(echo "$check_runs" | jq '[.[] | select(.conclusion == "failure" or .conclusion == "cancelled")] | length' 2>/dev/null || echo "0")
|
|
140
|
+
|
|
141
|
+
local pending_checks
|
|
142
|
+
pending_checks=$(echo "$check_runs" | jq '[.[] | select(.status != "completed")] | length' 2>/dev/null || echo "0")
|
|
143
|
+
|
|
144
|
+
if [[ "$failed_checks" -gt 0 ]]; then
|
|
145
|
+
error "PR #${pr_number} has ${failed_checks} failed check(s) on current head ${short_sha}"
|
|
146
|
+
return 1
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
if [[ "$pending_checks" -gt 0 ]]; then
|
|
150
|
+
warn "PR #${pr_number} has ${pending_checks} pending check(s) on head ${short_sha}"
|
|
151
|
+
return 1
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
info "All ${total_checks} checks passed for current head SHA ${short_sha}"
|
|
155
|
+
return 0
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
validate_reviews_for_head_sha() {
|
|
159
|
+
local pr_number="$1"
|
|
160
|
+
local head_sha="$2"
|
|
161
|
+
|
|
162
|
+
if [[ -z "$head_sha" ]]; then
|
|
163
|
+
return 0
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
local short_sha="${head_sha:0:7}"
|
|
167
|
+
|
|
168
|
+
# Get reviews and check they're not stale (submitted before the latest push)
|
|
169
|
+
local reviews_json
|
|
170
|
+
reviews_json=$(gh pr view "$pr_number" --json reviews --jq '.reviews' 2>/dev/null || echo "[]")
|
|
171
|
+
|
|
172
|
+
local latest_commit_date
|
|
173
|
+
latest_commit_date=$(gh pr view "$pr_number" --json commits --jq '.commits[-1].committedDate' 2>/dev/null || echo "")
|
|
174
|
+
|
|
175
|
+
if [[ -z "$latest_commit_date" ]]; then
|
|
176
|
+
return 0
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# Check if any approvals are stale (submitted before last commit)
|
|
180
|
+
local stale_approvals
|
|
181
|
+
stale_approvals=$(echo "$reviews_json" | jq --arg cutoff "$latest_commit_date" \
|
|
182
|
+
'[.[] | select(.state == "APPROVED" and .submittedAt < $cutoff)] | length' 2>/dev/null || echo "0")
|
|
183
|
+
|
|
184
|
+
if [[ "$stale_approvals" -gt 0 ]]; then
|
|
185
|
+
warn "PR #${pr_number} has ${stale_approvals} stale approval(s) from before head ${short_sha} — reviews should be refreshed"
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
return 0
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
compute_risk_tier_for_pr() {
|
|
192
|
+
local pr_number="$1"
|
|
193
|
+
local policy_file="${REPO_DIR}/config/policy.json"
|
|
194
|
+
|
|
195
|
+
if [[ ! -f "$policy_file" ]]; then
|
|
196
|
+
echo "medium"
|
|
197
|
+
return
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
local changed_files
|
|
201
|
+
changed_files=$(gh pr diff "$pr_number" --name-only 2>/dev/null || echo "")
|
|
202
|
+
|
|
203
|
+
if [[ -z "$changed_files" ]]; then
|
|
204
|
+
echo "low"
|
|
205
|
+
return
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
local tier="low"
|
|
209
|
+
|
|
210
|
+
check_tier_match() {
|
|
211
|
+
local check_tier="$1"
|
|
212
|
+
local patterns
|
|
213
|
+
patterns=$(jq -r ".riskTierRules.${check_tier}[]? // empty" "$policy_file" 2>/dev/null)
|
|
214
|
+
[[ -z "$patterns" ]] && return 1
|
|
215
|
+
|
|
216
|
+
while IFS= read -r pattern; do
|
|
217
|
+
[[ -z "$pattern" ]] && continue
|
|
218
|
+
local regex
|
|
219
|
+
regex=$(echo "$pattern" | sed 's/\./\\./g; s/\*\*/DOUBLESTAR/g; s/\*/[^\/]*/g; s/DOUBLESTAR/.*/g')
|
|
220
|
+
while IFS= read -r file; do
|
|
221
|
+
[[ -z "$file" ]] && continue
|
|
222
|
+
if echo "$file" | grep -qE "^${regex}$"; then
|
|
223
|
+
return 0
|
|
224
|
+
fi
|
|
225
|
+
done <<< "$changed_files"
|
|
226
|
+
done <<< "$patterns"
|
|
227
|
+
return 1
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if check_tier_match "critical"; then
|
|
231
|
+
tier="critical"
|
|
232
|
+
elif check_tier_match "high"; then
|
|
233
|
+
tier="high"
|
|
234
|
+
elif check_tier_match "medium"; then
|
|
235
|
+
tier="medium"
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
echo "$tier"
|
|
239
|
+
}
|
|
240
|
+
|
|
98
241
|
# ─── Review Pass ────────────────────────────────────────────────────────────
|
|
99
242
|
|
|
100
243
|
pr_review() {
|
|
@@ -220,6 +363,35 @@ pr_merge() {
|
|
|
220
363
|
return 1
|
|
221
364
|
fi
|
|
222
365
|
|
|
366
|
+
# ── Current-head SHA discipline ──────────────────────────────────────────
|
|
367
|
+
# All evidence (checks, reviews) must be validated against the current head.
|
|
368
|
+
# Never merge on stale evidence from an older commit.
|
|
369
|
+
local head_sha
|
|
370
|
+
head_sha=$(echo "$pr_info" | jq -r '.headRefOid // empty' 2>/dev/null)
|
|
371
|
+
if [[ -z "$head_sha" ]]; then
|
|
372
|
+
head_sha=$(get_pr_head_sha "$pr_number")
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
if [[ -n "$head_sha" ]]; then
|
|
376
|
+
local short_sha="${head_sha:0:7}"
|
|
377
|
+
info "Validating evidence for current head SHA: ${short_sha}"
|
|
378
|
+
|
|
379
|
+
if ! validate_checks_for_head_sha "$pr_number" "$head_sha"; then
|
|
380
|
+
error "PR #${pr_number} blocked — checks not passing for current head ${short_sha}"
|
|
381
|
+
emit_event "pr.merge_failed" "pr=${pr_number}" "reason=stale_checks" "head_sha=${short_sha}"
|
|
382
|
+
return 1
|
|
383
|
+
fi
|
|
384
|
+
|
|
385
|
+
validate_reviews_for_head_sha "$pr_number" "$head_sha"
|
|
386
|
+
else
|
|
387
|
+
warn "Could not determine head SHA — falling back to legacy check"
|
|
388
|
+
fi
|
|
389
|
+
|
|
390
|
+
# ── Risk tier enforcement ────────────────────────────────────────────────
|
|
391
|
+
local risk_tier
|
|
392
|
+
risk_tier=$(compute_risk_tier_for_pr "$pr_number")
|
|
393
|
+
info "Risk tier: ${risk_tier}"
|
|
394
|
+
|
|
223
395
|
# Check for merge conflicts
|
|
224
396
|
if has_merge_conflicts "$pr_number"; then
|
|
225
397
|
error "PR #${pr_number} has merge conflicts — manual intervention required"
|
|
@@ -227,7 +399,7 @@ pr_merge() {
|
|
|
227
399
|
return 1
|
|
228
400
|
fi
|
|
229
401
|
|
|
230
|
-
# Check CI status
|
|
402
|
+
# Check CI status (legacy check, supplementary to SHA-based validation)
|
|
231
403
|
local status_check_rollup
|
|
232
404
|
status_check_rollup=$(echo "$pr_info" | jq -r '.statusCheckRollup[].state' 2>/dev/null | sort | uniq)
|
|
233
405
|
if [[ -z "$status_check_rollup" ]] || echo "$status_check_rollup" | grep -qi "failure\|error"; then
|
|
@@ -246,10 +418,10 @@ pr_merge() {
|
|
|
246
418
|
fi
|
|
247
419
|
|
|
248
420
|
# Perform squash merge and delete branch
|
|
249
|
-
info "Merging PR #${pr_number} with squash..."
|
|
421
|
+
info "Merging PR #${pr_number} with squash (tier: ${risk_tier}, head: ${head_sha:0:7})..."
|
|
250
422
|
if gh pr merge "$pr_number" --squash --delete-branch 2>/dev/null; then
|
|
251
423
|
success "PR #${pr_number} merged and branch deleted"
|
|
252
|
-
emit_event "pr.merged" "pr=${pr_number}"
|
|
424
|
+
emit_event "pr.merged" "pr=${pr_number}" "risk_tier=${risk_tier}" "head_sha=${head_sha:0:7}"
|
|
253
425
|
|
|
254
426
|
# Post feedback to originating issue
|
|
255
427
|
local issue_number
|
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="2.
|
|
9
|
+
VERSION="2.4.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="2.
|
|
8
|
+
VERSION="2.4.0"
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
11
11
|
|
package/scripts/sw-quality.sh
CHANGED
package/scripts/sw-reaper.sh
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# ║ shipwright reaper --watch Continuous loop (default: 5s) ║
|
|
12
12
|
# ║ shipwright reaper --dry-run Preview what would be reaped ║
|
|
13
13
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
14
|
-
VERSION="2.
|
|
14
|
+
VERSION="2.4.0"
|
|
15
15
|
set -euo pipefail
|
|
16
16
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
17
17
|
|
package/scripts/sw-regression.sh
CHANGED
package/scripts/sw-release.sh
CHANGED
|
@@ -7,7 +7,7 @@ set -euo pipefail
|
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
trap 'rm -f "${tmp_file:-}"' EXIT
|
|
9
9
|
|
|
10
|
-
VERSION="2.
|
|
10
|
+
VERSION="2.4.0"
|
|
11
11
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
12
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
13
13
|
|
package/scripts/sw-remote.sh
CHANGED
package/scripts/sw-replay.sh
CHANGED
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="2.
|
|
9
|
+
VERSION="2.4.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
|
|
12
12
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright review-rerun — Canonical Rerun Comment Writer ║
|
|
4
|
+
# ║ SHA-deduped rerun requests · Single writer · No duplicate bot comments ║
|
|
5
|
+
# ║ Part of the Code Factory pattern for deterministic agent review loops ║
|
|
6
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
9
|
+
|
|
10
|
+
VERSION="2.4.0"
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
13
|
+
|
|
14
|
+
# shellcheck source=lib/compat.sh
|
|
15
|
+
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
16
|
+
# shellcheck source=lib/helpers.sh
|
|
17
|
+
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
18
|
+
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
19
|
+
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
20
|
+
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
|
|
21
|
+
[[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
|
|
22
|
+
if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
23
|
+
emit_event() {
|
|
24
|
+
local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
|
|
25
|
+
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
26
|
+
while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
|
|
27
|
+
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
28
|
+
}
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Load marker from policy or use default
|
|
32
|
+
get_rerun_marker() {
|
|
33
|
+
local policy="${REPO_DIR}/config/policy.json"
|
|
34
|
+
if [[ -f "$policy" ]]; then
|
|
35
|
+
jq -r '.codeReviewAgent.rerunMarker // "<!-- shipwright-review-rerun -->"' "$policy" 2>/dev/null
|
|
36
|
+
else
|
|
37
|
+
echo "<!-- shipwright-review-rerun -->"
|
|
38
|
+
fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Check if a rerun was already requested for this SHA on this PR
|
|
42
|
+
rerun_already_requested() {
|
|
43
|
+
local pr_number="$1"
|
|
44
|
+
local head_sha="$2"
|
|
45
|
+
local marker
|
|
46
|
+
marker=$(get_rerun_marker)
|
|
47
|
+
local trigger="sha:${head_sha}"
|
|
48
|
+
|
|
49
|
+
local comments
|
|
50
|
+
comments=$(gh pr view "$pr_number" --json comments --jq '.comments[].body' 2>/dev/null || echo "")
|
|
51
|
+
|
|
52
|
+
if echo "$comments" | grep -qF "$marker" && echo "$comments" | grep -qF "$trigger"; then
|
|
53
|
+
return 0
|
|
54
|
+
fi
|
|
55
|
+
return 1
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Post a SHA-deduped rerun comment to a PR
|
|
59
|
+
request_rerun() {
|
|
60
|
+
local pr_number="$1"
|
|
61
|
+
local head_sha="$2"
|
|
62
|
+
local review_agent="${3:-shipwright}"
|
|
63
|
+
|
|
64
|
+
if [[ -z "$pr_number" || -z "$head_sha" ]]; then
|
|
65
|
+
error "Usage: sw-review-rerun.sh request <pr_number> <head_sha> [review_agent]"
|
|
66
|
+
return 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
local marker
|
|
70
|
+
marker=$(get_rerun_marker)
|
|
71
|
+
local trigger="sha:${head_sha}"
|
|
72
|
+
local short_sha="${head_sha:0:7}"
|
|
73
|
+
|
|
74
|
+
if rerun_already_requested "$pr_number" "$head_sha"; then
|
|
75
|
+
info "Rerun already requested for PR #${pr_number} at SHA ${short_sha} — skipping"
|
|
76
|
+
return 0
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
local body="${marker}
|
|
80
|
+
**Review Rerun Requested** (${short_sha})
|
|
81
|
+
|
|
82
|
+
@${review_agent} please re-review this PR at the current head.
|
|
83
|
+
|
|
84
|
+
${trigger}
|
|
85
|
+
---
|
|
86
|
+
*Canonical rerun request by Shipwright Code Factory. One writer, SHA-deduped.*"
|
|
87
|
+
|
|
88
|
+
if gh pr comment "$pr_number" --body "$body" 2>/dev/null; then
|
|
89
|
+
success "Rerun requested for PR #${pr_number} at SHA ${short_sha}"
|
|
90
|
+
emit_event "review.rerun_requested" "pr=${pr_number}" "head_sha=${short_sha}" "agent=${review_agent}"
|
|
91
|
+
return 0
|
|
92
|
+
else
|
|
93
|
+
error "Failed to post rerun comment on PR #${pr_number}"
|
|
94
|
+
return 1
|
|
95
|
+
fi
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Check current rerun state for a PR
|
|
99
|
+
check_rerun_state() {
|
|
100
|
+
local pr_number="$1"
|
|
101
|
+
|
|
102
|
+
local head_sha
|
|
103
|
+
head_sha=$(gh pr view "$pr_number" --json headRefOid --jq '.headRefOid' 2>/dev/null || echo "")
|
|
104
|
+
|
|
105
|
+
if [[ -z "$head_sha" ]]; then
|
|
106
|
+
error "Could not get head SHA for PR #${pr_number}"
|
|
107
|
+
return 1
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
local short_sha="${head_sha:0:7}"
|
|
111
|
+
|
|
112
|
+
if rerun_already_requested "$pr_number" "$head_sha"; then
|
|
113
|
+
info "Rerun already requested for current head ${short_sha}"
|
|
114
|
+
else
|
|
115
|
+
info "No rerun requested for current head ${short_sha}"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
echo "head_sha=${head_sha}"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# Wait for a review agent check to complete on the current head
|
|
122
|
+
wait_for_review() {
|
|
123
|
+
local pr_number="$1"
|
|
124
|
+
local head_sha="$2"
|
|
125
|
+
local timeout_minutes="${3:-20}"
|
|
126
|
+
|
|
127
|
+
local policy="${REPO_DIR}/config/policy.json"
|
|
128
|
+
if [[ -f "$policy" ]]; then
|
|
129
|
+
timeout_minutes=$(jq -r ".codeReviewAgent.timeoutMinutes // ${timeout_minutes}" "$policy" 2>/dev/null || echo "$timeout_minutes")
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
local short_sha="${head_sha:0:7}"
|
|
133
|
+
local deadline=$(($(date +%s) + timeout_minutes * 60))
|
|
134
|
+
|
|
135
|
+
info "Waiting for review completion on ${short_sha} (timeout: ${timeout_minutes}m)..."
|
|
136
|
+
|
|
137
|
+
while [[ $(date +%s) -lt "$deadline" ]]; do
|
|
138
|
+
local owner_repo
|
|
139
|
+
owner_repo=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || echo "")
|
|
140
|
+
[[ -z "$owner_repo" ]] && { warn "Cannot detect repo"; return 1; }
|
|
141
|
+
|
|
142
|
+
local review_checks
|
|
143
|
+
review_checks=$(gh api "repos/${owner_repo}/commits/${head_sha}/check-runs" \
|
|
144
|
+
--jq '.check_runs[] | select(.name | test("review|code.review"; "i")) | {name: .name, status: .status, conclusion: .conclusion}' 2>/dev/null || echo "")
|
|
145
|
+
|
|
146
|
+
if [[ -n "$review_checks" ]]; then
|
|
147
|
+
local all_complete="true"
|
|
148
|
+
local any_failure="false"
|
|
149
|
+
while IFS= read -r check; do
|
|
150
|
+
[[ -z "$check" ]] && continue
|
|
151
|
+
local status conclusion
|
|
152
|
+
status=$(echo "$check" | jq -r '.status' 2>/dev/null || echo "")
|
|
153
|
+
conclusion=$(echo "$check" | jq -r '.conclusion' 2>/dev/null || echo "")
|
|
154
|
+
if [[ "$status" != "completed" ]]; then
|
|
155
|
+
all_complete="false"
|
|
156
|
+
fi
|
|
157
|
+
if [[ "$conclusion" == "failure" || "$conclusion" == "action_required" ]]; then
|
|
158
|
+
any_failure="true"
|
|
159
|
+
fi
|
|
160
|
+
done <<< "$review_checks"
|
|
161
|
+
|
|
162
|
+
if [[ "$all_complete" == "true" ]]; then
|
|
163
|
+
if [[ "$any_failure" == "true" ]]; then
|
|
164
|
+
error "Review check failed for SHA ${short_sha}"
|
|
165
|
+
return 1
|
|
166
|
+
fi
|
|
167
|
+
success "Review check passed for SHA ${short_sha}"
|
|
168
|
+
return 0
|
|
169
|
+
fi
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
sleep 30
|
|
173
|
+
done
|
|
174
|
+
|
|
175
|
+
error "Review timed out after ${timeout_minutes}m for SHA ${short_sha}"
|
|
176
|
+
return 1
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
show_help() {
|
|
180
|
+
cat << 'EOF'
|
|
181
|
+
Usage: shipwright review-rerun <command> [args]
|
|
182
|
+
|
|
183
|
+
Commands:
|
|
184
|
+
request <pr#> <sha> [agent] Post SHA-deduped rerun comment
|
|
185
|
+
check <pr#> Check rerun state for current head
|
|
186
|
+
wait <pr#> <sha> [timeout] Wait for review completion on SHA
|
|
187
|
+
|
|
188
|
+
Part of the Code Factory pattern — single canonical rerun writer
|
|
189
|
+
with SHA deduplication to prevent duplicate bot comments.
|
|
190
|
+
EOF
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
main() {
|
|
194
|
+
local subcommand="${1:-help}"
|
|
195
|
+
shift || true
|
|
196
|
+
|
|
197
|
+
case "$subcommand" in
|
|
198
|
+
request)
|
|
199
|
+
request_rerun "$@"
|
|
200
|
+
;;
|
|
201
|
+
check)
|
|
202
|
+
check_rerun_state "$@"
|
|
203
|
+
;;
|
|
204
|
+
wait)
|
|
205
|
+
wait_for_review "$@"
|
|
206
|
+
;;
|
|
207
|
+
help|--help|-h)
|
|
208
|
+
show_help
|
|
209
|
+
;;
|
|
210
|
+
*)
|
|
211
|
+
error "Unknown subcommand: $subcommand"
|
|
212
|
+
show_help
|
|
213
|
+
return 1
|
|
214
|
+
;;
|
|
215
|
+
esac
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
219
|
+
main "$@"
|
|
220
|
+
fi
|
package/scripts/sw-scale.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="2.
|
|
9
|
+
VERSION="2.4.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
|
|
12
12
|
# ─── Dependency check ─────────────────────────────────────────────────────────
|
package/scripts/sw-session.sh
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# ║ Supports --template to scaffold from a team template and --terminal ║
|
|
9
9
|
# ║ to select a terminal adapter (tmux, iterm2, wezterm). ║
|
|
10
10
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
11
|
-
VERSION="2.
|
|
11
|
+
VERSION="2.4.0"
|
|
12
12
|
set -euo pipefail
|
|
13
13
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
14
14
|
|
package/scripts/sw-setup.sh
CHANGED
package/scripts/sw-standup.sh
CHANGED
package/scripts/sw-status.sh
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# ║ ║
|
|
5
5
|
# ║ Shows running teams, agent windows, and task progress. ║
|
|
6
6
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
7
|
-
VERSION="2.
|
|
7
|
+
VERSION="2.4.0"
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
10
10
|
|
package/scripts/sw-strategic.sh
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# When sourced, do NOT add set -euo pipefail — the parent handles that.
|
|
8
8
|
# When run directly, main() sets up the error handling.
|
|
9
9
|
|
|
10
|
-
VERSION="2.
|
|
10
|
+
VERSION="2.4.0"
|
|
11
11
|
|
|
12
12
|
# ─── Paths (set defaults if not provided by parent) ──────────────────────────
|
|
13
13
|
SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
|
package/scripts/sw-stream.sh
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# ║ Streams tmux pane output in real-time to the dashboard or CLI. ║
|
|
6
6
|
# ║ Captures output periodically, tags by agent/team, supports replay. ║
|
|
7
7
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
8
|
-
VERSION="2.
|
|
8
|
+
VERSION="2.4.0"
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
11
11
|
|