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,603 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright triage — Intelligent Issue Labeling & Prioritization ║
|
|
4
|
+
# ║ Auto-analyze issues, assign labels, score priority, recommend team size ║
|
|
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
|
+
# ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
31
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
32
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
33
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
34
|
+
|
|
35
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
36
|
+
|
|
37
|
+
# ─── Structured Event Log ──────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
emit_event() {
|
|
40
|
+
local event_type="$1"; shift
|
|
41
|
+
local events_file="${HOME}/.shipwright/events.jsonl"
|
|
42
|
+
mkdir -p "$(dirname "$events_file")"
|
|
43
|
+
local payload="{\"ts\":\"$(now_iso)\",\"type\":\"$event_type\""
|
|
44
|
+
while [[ $# -gt 0 ]]; do
|
|
45
|
+
local key="${1%%=*}" val="${1#*=}"
|
|
46
|
+
val="${val//\"/\\\"}"
|
|
47
|
+
payload="${payload},\"${key}\":\"${val}\""
|
|
48
|
+
shift
|
|
49
|
+
done
|
|
50
|
+
payload="${payload}}"
|
|
51
|
+
echo "$payload" >> "$events_file"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# ─── GitHub API (safe when NO_GITHUB set) ──────────────────────────────────
|
|
55
|
+
|
|
56
|
+
check_gh() {
|
|
57
|
+
if [[ "${NO_GITHUB:-}" == "1" ]]; then
|
|
58
|
+
error "GitHub access disabled (NO_GITHUB=1)"
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
if ! command -v gh &>/dev/null; then
|
|
62
|
+
error "gh CLI not found. Install: https://cli.github.com"
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# ─── Analysis Functions ────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
# analyze_type <issue_body>
|
|
70
|
+
# Detects issue type from title/body keywords
|
|
71
|
+
analyze_type() {
|
|
72
|
+
local body="$1"
|
|
73
|
+
local lower_body
|
|
74
|
+
lower_body=$(echo "$body" | tr '[:upper:]' '[:lower:]')
|
|
75
|
+
|
|
76
|
+
if echo "$lower_body" | grep -qE "(security|vulnerability|cve|exploit|breach)"; then
|
|
77
|
+
echo "security"
|
|
78
|
+
elif echo "$lower_body" | grep -qE "(performance|speed|latency|slow|optimize|memory)"; then
|
|
79
|
+
echo "performance"
|
|
80
|
+
elif echo "$lower_body" | grep -qE "(bug|broken|crash|error|fail|issue)"; then
|
|
81
|
+
echo "bug"
|
|
82
|
+
elif echo "$lower_body" | grep -qE "(refactor|reorgan|rewrite|clean|improve)"; then
|
|
83
|
+
echo "refactor"
|
|
84
|
+
elif echo "$lower_body" | grep -qE "(doc|guide|readme|tutorial|howto)"; then
|
|
85
|
+
echo "docs"
|
|
86
|
+
elif echo "$lower_body" | grep -qE "(chore|maintain|update|bump|dependencies)"; then
|
|
87
|
+
echo "chore"
|
|
88
|
+
else
|
|
89
|
+
echo "feature"
|
|
90
|
+
fi
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# analyze_complexity <issue_body>
|
|
94
|
+
# Estimates complexity from body length, keywords, mentions
|
|
95
|
+
analyze_complexity() {
|
|
96
|
+
local body="$1"
|
|
97
|
+
local score=0
|
|
98
|
+
|
|
99
|
+
# Longer issues = more complex (rough heuristic)
|
|
100
|
+
local body_lines
|
|
101
|
+
body_lines=$(echo "$body" | wc -l)
|
|
102
|
+
if [[ $body_lines -gt 50 ]]; then
|
|
103
|
+
score=$((score + 2))
|
|
104
|
+
elif [[ $body_lines -gt 20 ]]; then
|
|
105
|
+
score=$((score + 1))
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
local lower_body
|
|
109
|
+
lower_body=$(echo "$body" | tr '[:upper:]' '[:lower:]')
|
|
110
|
+
|
|
111
|
+
# Complexity keywords
|
|
112
|
+
if echo "$lower_body" | grep -qE "(epic|major|rewrite|redesign|architecture)"; then
|
|
113
|
+
score=$((score + 3))
|
|
114
|
+
elif echo "$lower_body" | grep -qE "(multiple|several|cascade|dependencies|breaking)"; then
|
|
115
|
+
score=$((score + 2))
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# Mentions of tests/specs suggest complexity
|
|
119
|
+
if echo "$lower_body" | grep -qE "(test|spec|coverage|validation)"; then
|
|
120
|
+
score=$((score + 1))
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
case $score in
|
|
124
|
+
0) echo "trivial" ;;
|
|
125
|
+
1) echo "simple" ;;
|
|
126
|
+
2|3) echo "moderate" ;;
|
|
127
|
+
4|5) echo "complex" ;;
|
|
128
|
+
*) echo "epic" ;;
|
|
129
|
+
esac
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# analyze_risk <issue_body>
|
|
133
|
+
# Assesses risk from keywords and scope
|
|
134
|
+
analyze_risk() {
|
|
135
|
+
local body="$1"
|
|
136
|
+
local score=0
|
|
137
|
+
local lower_body
|
|
138
|
+
lower_body=$(echo "$body" | tr '[:upper:]' '[:lower:]')
|
|
139
|
+
|
|
140
|
+
if echo "$lower_body" | grep -qE "(security|vulnerability|exploit|critical)"; then
|
|
141
|
+
score=$((score + 3))
|
|
142
|
+
elif echo "$lower_body" | grep -qE "(breaking|migration|deprecat)"; then
|
|
143
|
+
score=$((score + 2))
|
|
144
|
+
elif echo "$lower_body" | grep -qE "(production|staging|database)"; then
|
|
145
|
+
score=$((score + 1))
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
if echo "$lower_body" | grep -qE "(infrastructure|deploy|release)"; then
|
|
149
|
+
score=$((score + 1))
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
case $score in
|
|
153
|
+
0) echo "low" ;;
|
|
154
|
+
1|2) echo "medium" ;;
|
|
155
|
+
3) echo "high" ;;
|
|
156
|
+
*) echo "critical" ;;
|
|
157
|
+
esac
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# analyze_effort <complexity> <risk>
|
|
161
|
+
# Maps complexity+risk → effort estimate
|
|
162
|
+
analyze_effort() {
|
|
163
|
+
local complexity="$1"
|
|
164
|
+
local risk="$2"
|
|
165
|
+
|
|
166
|
+
case "${complexity}-${risk}" in
|
|
167
|
+
trivial-low) echo "xs" ;;
|
|
168
|
+
trivial-*|simple-low) echo "s" ;;
|
|
169
|
+
simple-medium|moderate-low) echo "m" ;;
|
|
170
|
+
moderate-*|complex-low) echo "l" ;;
|
|
171
|
+
complex-*|epic-*) echo "xl" ;;
|
|
172
|
+
*) echo "m" ;;
|
|
173
|
+
esac
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# suggest_labels <type> <complexity> <risk> <effort>
|
|
177
|
+
# Generates label recommendations
|
|
178
|
+
suggest_labels() {
|
|
179
|
+
local type="$1"
|
|
180
|
+
local complexity="$2"
|
|
181
|
+
local risk="$3"
|
|
182
|
+
local effort="$4"
|
|
183
|
+
local labels=""
|
|
184
|
+
|
|
185
|
+
# Type label
|
|
186
|
+
labels="${labels}type:${type}"
|
|
187
|
+
|
|
188
|
+
# Complexity label
|
|
189
|
+
labels="${labels} complexity:${complexity}"
|
|
190
|
+
|
|
191
|
+
# Priority label (derived from risk)
|
|
192
|
+
case "$risk" in
|
|
193
|
+
critical) labels="${labels} priority:urgent" ;;
|
|
194
|
+
high) labels="${labels} priority:high" ;;
|
|
195
|
+
medium) labels="${labels} priority:medium" ;;
|
|
196
|
+
*) labels="${labels} priority:low" ;;
|
|
197
|
+
esac
|
|
198
|
+
|
|
199
|
+
# Effort label
|
|
200
|
+
labels="${labels} effort:${effort}"
|
|
201
|
+
|
|
202
|
+
# Risk label
|
|
203
|
+
labels="${labels} risk:${risk}"
|
|
204
|
+
|
|
205
|
+
echo "$labels"
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# ─── Subcommand: analyze ──────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
cmd_analyze() {
|
|
211
|
+
local issue="${1:-}"
|
|
212
|
+
[[ -z "$issue" ]] && { error "Usage: triage analyze <issue>"; exit 1; }
|
|
213
|
+
|
|
214
|
+
check_gh
|
|
215
|
+
|
|
216
|
+
info "Analyzing issue ${CYAN}${issue}${RESET}..."
|
|
217
|
+
|
|
218
|
+
# Fetch issue via gh CLI
|
|
219
|
+
local issue_json
|
|
220
|
+
issue_json=$(gh issue view "$issue" --json title,body,labels --format json 2>/dev/null || echo "{}")
|
|
221
|
+
|
|
222
|
+
if [[ "$issue_json" == "{}" ]]; then
|
|
223
|
+
error "Failed to fetch issue ${CYAN}${issue}${RESET}"
|
|
224
|
+
exit 1
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
local title body existing_labels
|
|
228
|
+
title=$(echo "$issue_json" | jq -r '.title')
|
|
229
|
+
body=$(echo "$issue_json" | jq -r '.body // ""')
|
|
230
|
+
existing_labels=$(echo "$issue_json" | jq -r '.labels[].name' | tr '\n' ',' | sed 's/,$//')
|
|
231
|
+
|
|
232
|
+
local combined_text="${title} ${body}"
|
|
233
|
+
|
|
234
|
+
# Analyze
|
|
235
|
+
local type complexity risk effort labels
|
|
236
|
+
type=$(analyze_type "$combined_text")
|
|
237
|
+
complexity=$(analyze_complexity "$combined_text")
|
|
238
|
+
risk=$(analyze_risk "$combined_text")
|
|
239
|
+
effort=$(analyze_effort "$complexity" "$risk")
|
|
240
|
+
labels=$(suggest_labels "$type" "$complexity" "$risk" "$effort")
|
|
241
|
+
|
|
242
|
+
# Output as structured JSON
|
|
243
|
+
cat << EOF
|
|
244
|
+
{
|
|
245
|
+
"issue": "$issue",
|
|
246
|
+
"title": $(jq -R . <<< "$title"),
|
|
247
|
+
"type": "$type",
|
|
248
|
+
"complexity": "$complexity",
|
|
249
|
+
"risk": "$risk",
|
|
250
|
+
"effort": "$effort",
|
|
251
|
+
"suggested_labels": $(echo "$labels" | jq -R 'split(" ")'),
|
|
252
|
+
"existing_labels": $(echo "$existing_labels" | jq -R 'split(",")')
|
|
253
|
+
}
|
|
254
|
+
EOF
|
|
255
|
+
|
|
256
|
+
emit_event "triage_analyzed" "issue=$issue" "type=$type" "complexity=$complexity" "risk=$risk"
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
# ─── Subcommand: label ────────────────────────────────────────────────────
|
|
260
|
+
|
|
261
|
+
cmd_label() {
|
|
262
|
+
local issue="${1:-}"
|
|
263
|
+
[[ -z "$issue" ]] && { error "Usage: triage label <issue>"; exit 1; }
|
|
264
|
+
|
|
265
|
+
check_gh
|
|
266
|
+
|
|
267
|
+
info "Labeling issue ${CYAN}${issue}${RESET}..."
|
|
268
|
+
|
|
269
|
+
# Get suggested labels
|
|
270
|
+
local analysis
|
|
271
|
+
analysis=$(cmd_analyze "$issue" 2>/dev/null)
|
|
272
|
+
|
|
273
|
+
local labels_str
|
|
274
|
+
labels_str=$(echo "$analysis" | jq -r '.suggested_labels | join(" ")')
|
|
275
|
+
|
|
276
|
+
# Apply labels via gh CLI
|
|
277
|
+
local label_array
|
|
278
|
+
mapfile -t label_array <<< "$(echo "$labels_str" | tr ' ' '\n')"
|
|
279
|
+
|
|
280
|
+
for label in "${label_array[@]}"; do
|
|
281
|
+
[[ -z "$label" ]] && continue
|
|
282
|
+
gh label create "$label" --repo "$(gh repo view --json nameWithOwner -q)" 2>/dev/null || true
|
|
283
|
+
gh issue edit "$issue" --add-label "$label" 2>/dev/null || true
|
|
284
|
+
success "Applied label: ${CYAN}${label}${RESET}"
|
|
285
|
+
done
|
|
286
|
+
|
|
287
|
+
emit_event "triage_labeled" "issue=$issue" "label_count=${#label_array[@]}"
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
# ─── Subcommand: prioritize ───────────────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
cmd_prioritize() {
|
|
293
|
+
check_gh
|
|
294
|
+
|
|
295
|
+
info "Scoring open issues..."
|
|
296
|
+
|
|
297
|
+
local output_json="[]"
|
|
298
|
+
|
|
299
|
+
# Fetch all open issues
|
|
300
|
+
local issues_json
|
|
301
|
+
issues_json=$(gh issue list --state open --json number,title,body,labels,createdAt,reactions --limit 100 2>/dev/null || echo "[]")
|
|
302
|
+
|
|
303
|
+
local issue_count
|
|
304
|
+
issue_count=$(echo "$issues_json" | jq 'length')
|
|
305
|
+
info "Found ${CYAN}${issue_count}${RESET} open issues"
|
|
306
|
+
|
|
307
|
+
# Score each issue
|
|
308
|
+
while IFS= read -r issue_json; do
|
|
309
|
+
local number title body labels
|
|
310
|
+
number=$(echo "$issue_json" | jq -r '.number')
|
|
311
|
+
title=$(echo "$issue_json" | jq -r '.title')
|
|
312
|
+
body=$(echo "$issue_json" | jq -r '.body // ""')
|
|
313
|
+
labels=$(echo "$issue_json" | jq -r '.labels[].name' | tr '\n' ',' | sed 's/,$//')
|
|
314
|
+
|
|
315
|
+
local combined="${title} ${body}"
|
|
316
|
+
local type complexity risk
|
|
317
|
+
type=$(analyze_type "$combined")
|
|
318
|
+
complexity=$(analyze_complexity "$combined")
|
|
319
|
+
risk=$(analyze_risk "$combined")
|
|
320
|
+
|
|
321
|
+
# Score calculation: (Impact × 3) + (Urgency × 2) − (Effort × 0.5)
|
|
322
|
+
# Impact: risk level (0-3)
|
|
323
|
+
# Urgency: 1 if labeled urgent/blocking, else 0.5
|
|
324
|
+
# Effort: trivial=1, simple=2, moderate=3, complex=4, epic=5
|
|
325
|
+
local impact urgency effort_score
|
|
326
|
+
case "$risk" in
|
|
327
|
+
critical) impact=3 ;;
|
|
328
|
+
high) impact=2 ;;
|
|
329
|
+
medium) impact=1 ;;
|
|
330
|
+
*) impact=0 ;;
|
|
331
|
+
esac
|
|
332
|
+
|
|
333
|
+
if echo "$labels" | grep -qE "(urgent|blocking|critical)"; then
|
|
334
|
+
urgency=1
|
|
335
|
+
else
|
|
336
|
+
urgency=0.5
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
case "$complexity" in
|
|
340
|
+
trivial) effort_score=1 ;;
|
|
341
|
+
simple) effort_score=2 ;;
|
|
342
|
+
moderate) effort_score=3 ;;
|
|
343
|
+
complex) effort_score=4 ;;
|
|
344
|
+
epic) effort_score=5 ;;
|
|
345
|
+
*) effort_score=3 ;;
|
|
346
|
+
esac
|
|
347
|
+
|
|
348
|
+
local score
|
|
349
|
+
score=$(awk "BEGIN {printf \"%.1f\", ($impact * 3) + ($urgency * 2) - ($effort_score * 0.5)}")
|
|
350
|
+
|
|
351
|
+
local item
|
|
352
|
+
item=$(jq -n \
|
|
353
|
+
--arg number "$number" \
|
|
354
|
+
--arg title "$title" \
|
|
355
|
+
--arg type "$type" \
|
|
356
|
+
--arg complexity "$complexity" \
|
|
357
|
+
--arg risk "$risk" \
|
|
358
|
+
--arg score "$score" \
|
|
359
|
+
'{number: $number, title: $title, type: $type, complexity: $complexity, risk: $risk, score: ($score | tonumber)}')
|
|
360
|
+
|
|
361
|
+
output_json=$(echo "$output_json" | jq ". += [$item]")
|
|
362
|
+
done < <(echo "$issues_json" | jq -c '.[]')
|
|
363
|
+
|
|
364
|
+
# Sort by score descending
|
|
365
|
+
output_json=$(echo "$output_json" | jq 'sort_by(.score) | reverse')
|
|
366
|
+
|
|
367
|
+
# Pretty print
|
|
368
|
+
echo ""
|
|
369
|
+
echo -e "${BOLD}Prioritized Backlog${RESET}"
|
|
370
|
+
echo "─────────────────────────────────────────────────────────────────"
|
|
371
|
+
echo ""
|
|
372
|
+
echo "$output_json" | jq -r '.[] | "\(.number | tostring | @json) \(.score | tostring): \(.title) [type:\(.type) complexity:\(.complexity) risk:\(.risk)]"' | while IFS= read -r line; do
|
|
373
|
+
local number score rest
|
|
374
|
+
number=$(echo "$line" | jq -r 'split(" ")[0]' <<< "$line")
|
|
375
|
+
score=$(echo "$line" | cut -d: -f2 | cut -d' ' -f1)
|
|
376
|
+
rest=$(echo "$line" | cut -d' ' -f3-)
|
|
377
|
+
|
|
378
|
+
echo -e " ${CYAN}#${number}${RESET} ${BOLD}${score}${RESET} ${rest}"
|
|
379
|
+
done
|
|
380
|
+
|
|
381
|
+
echo ""
|
|
382
|
+
info "Output: $(echo "$output_json" | jq 'length') issues ranked"
|
|
383
|
+
|
|
384
|
+
emit_event "triage_prioritized" "issue_count=$(echo "$output_json" | jq 'length')"
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
# ─── Subcommand: team ─────────────────────────────────────────────────────
|
|
388
|
+
|
|
389
|
+
cmd_team() {
|
|
390
|
+
local issue="${1:-}"
|
|
391
|
+
[[ -z "$issue" ]] && { error "Usage: triage team <issue>"; exit 1; }
|
|
392
|
+
|
|
393
|
+
check_gh
|
|
394
|
+
|
|
395
|
+
info "Recommending team setup for issue ${CYAN}${issue}${RESET}..."
|
|
396
|
+
|
|
397
|
+
# Get analysis
|
|
398
|
+
local analysis
|
|
399
|
+
analysis=$(cmd_analyze "$issue" 2>/dev/null)
|
|
400
|
+
|
|
401
|
+
local complexity risk effort
|
|
402
|
+
complexity=$(echo "$analysis" | jq -r '.complexity')
|
|
403
|
+
risk=$(echo "$analysis" | jq -r '.risk')
|
|
404
|
+
effort=$(echo "$analysis" | jq -r '.effort')
|
|
405
|
+
|
|
406
|
+
# Recommend based on complexity/risk
|
|
407
|
+
local template model max_iterations agents
|
|
408
|
+
case "${complexity}-${risk}" in
|
|
409
|
+
trivial-low|simple-low)
|
|
410
|
+
template="fast"
|
|
411
|
+
model="haiku"
|
|
412
|
+
max_iterations=2
|
|
413
|
+
agents=1
|
|
414
|
+
;;
|
|
415
|
+
simple-*|moderate-low)
|
|
416
|
+
template="standard"
|
|
417
|
+
model="sonnet"
|
|
418
|
+
max_iterations=5
|
|
419
|
+
agents=2
|
|
420
|
+
;;
|
|
421
|
+
moderate-*|complex-low)
|
|
422
|
+
template="standard"
|
|
423
|
+
model="sonnet"
|
|
424
|
+
max_iterations=8
|
|
425
|
+
agents=3
|
|
426
|
+
;;
|
|
427
|
+
complex-*|epic-*)
|
|
428
|
+
template="full"
|
|
429
|
+
model="opus"
|
|
430
|
+
max_iterations=15
|
|
431
|
+
agents=4
|
|
432
|
+
;;
|
|
433
|
+
*)
|
|
434
|
+
template="standard"
|
|
435
|
+
model="sonnet"
|
|
436
|
+
max_iterations=5
|
|
437
|
+
agents=2
|
|
438
|
+
;;
|
|
439
|
+
esac
|
|
440
|
+
|
|
441
|
+
cat << EOF
|
|
442
|
+
{
|
|
443
|
+
"issue": "$issue",
|
|
444
|
+
"complexity": "$complexity",
|
|
445
|
+
"risk": "$risk",
|
|
446
|
+
"effort": "$effort",
|
|
447
|
+
"recommendation": {
|
|
448
|
+
"pipeline_template": "$template",
|
|
449
|
+
"model": "$model",
|
|
450
|
+
"max_iterations": $max_iterations,
|
|
451
|
+
"agents": $agents
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
EOF
|
|
455
|
+
|
|
456
|
+
emit_event "triage_team_recommended" "issue=$issue" "template=$template" "agents=$agents"
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
# ─── Subcommand: batch ────────────────────────────────────────────────────
|
|
460
|
+
|
|
461
|
+
cmd_batch() {
|
|
462
|
+
check_gh
|
|
463
|
+
|
|
464
|
+
info "Batch analyzing and labeling unlabeled open issues..."
|
|
465
|
+
|
|
466
|
+
# Fetch unlabeled open issues
|
|
467
|
+
local issues_json
|
|
468
|
+
issues_json=$(gh issue list --state open --search "no:label" --json number --limit 50 2>/dev/null || echo "[]")
|
|
469
|
+
|
|
470
|
+
local issue_count
|
|
471
|
+
issue_count=$(echo "$issues_json" | jq 'length')
|
|
472
|
+
info "Found ${CYAN}${issue_count}${RESET} unlabeled issues"
|
|
473
|
+
|
|
474
|
+
local success_count=0
|
|
475
|
+
echo "$issues_json" | jq -r '.[] | .number' | while IFS= read -r number; do
|
|
476
|
+
if cmd_label "$number" >/dev/null 2>&1; then
|
|
477
|
+
success_count=$((success_count + 1))
|
|
478
|
+
fi
|
|
479
|
+
done
|
|
480
|
+
|
|
481
|
+
success "Labeled ${CYAN}${issue_count}${RESET} issues"
|
|
482
|
+
emit_event "triage_batch_complete" "issue_count=$issue_count"
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
# ─── Subcommand: report ───────────────────────────────────────────────────
|
|
486
|
+
|
|
487
|
+
cmd_report() {
|
|
488
|
+
check_gh
|
|
489
|
+
|
|
490
|
+
info "Generating triage statistics..."
|
|
491
|
+
|
|
492
|
+
# Fetch labeled issues
|
|
493
|
+
local issues_json
|
|
494
|
+
issues_json=$(gh issue list --state open --json labels,title,number --limit 100 2>/dev/null || echo "[]")
|
|
495
|
+
|
|
496
|
+
local type_counts complexity_counts priority_counts
|
|
497
|
+
type_counts='{}'
|
|
498
|
+
complexity_counts='{}'
|
|
499
|
+
priority_counts='{}'
|
|
500
|
+
|
|
501
|
+
echo "$issues_json" | jq -c '.[]' | while IFS= read -r issue_json; do
|
|
502
|
+
local labels
|
|
503
|
+
labels=$(echo "$issue_json" | jq -r '.labels[].name' | tr '\n' ',')
|
|
504
|
+
|
|
505
|
+
# Extract type
|
|
506
|
+
local type
|
|
507
|
+
type=$(echo "$labels" | grep -oE "type:[a-z-]+" | cut -d: -f2 | head -1 || echo "unknown")
|
|
508
|
+
type_counts=$(echo "$type_counts" | jq --arg t "$type" '.[$t] = (.[$t] // 0) + 1')
|
|
509
|
+
|
|
510
|
+
# Extract complexity
|
|
511
|
+
local complexity
|
|
512
|
+
complexity=$(echo "$labels" | grep -oE "complexity:[a-z-]+" | cut -d: -f2 | head -1 || echo "unknown")
|
|
513
|
+
complexity_counts=$(echo "$complexity_counts" | jq --arg c "$complexity" '.[$c] = (.[$c] // 0) + 1')
|
|
514
|
+
|
|
515
|
+
# Extract priority
|
|
516
|
+
local priority
|
|
517
|
+
priority=$(echo "$labels" | grep -oE "priority:[a-z-]+" | cut -d: -f2 | head -1 || echo "unknown")
|
|
518
|
+
priority_counts=$(echo "$priority_counts" | jq --arg p "$priority" '.[$p] = (.[$p] // 0) + 1')
|
|
519
|
+
done
|
|
520
|
+
|
|
521
|
+
# Output report
|
|
522
|
+
echo ""
|
|
523
|
+
echo -e "${BOLD}Triage Report${RESET}"
|
|
524
|
+
echo "─────────────────────────────────────────────────────────────────"
|
|
525
|
+
echo ""
|
|
526
|
+
echo -e "${BOLD}By Type:${RESET}"
|
|
527
|
+
echo "$type_counts" | jq -r 'to_entries[] | " \(.key): \(.value)"'
|
|
528
|
+
echo ""
|
|
529
|
+
echo -e "${BOLD}By Complexity:${RESET}"
|
|
530
|
+
echo "$complexity_counts" | jq -r 'to_entries[] | " \(.key): \(.value)"'
|
|
531
|
+
echo ""
|
|
532
|
+
echo -e "${BOLD}By Priority:${RESET}"
|
|
533
|
+
echo "$priority_counts" | jq -r 'to_entries[] | " \(.key): \(.value)"'
|
|
534
|
+
|
|
535
|
+
emit_event "triage_report_generated"
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
# ─── Subcommand: help ────────────────────────────────────────────────────
|
|
539
|
+
|
|
540
|
+
cmd_help() {
|
|
541
|
+
echo -e "${BOLD}shipwright triage${RESET} — Intelligent Issue Labeling & Prioritization"
|
|
542
|
+
echo ""
|
|
543
|
+
echo -e "${BOLD}USAGE${RESET}"
|
|
544
|
+
echo -e " ${CYAN}shipwright triage${RESET} <subcommand> [options]"
|
|
545
|
+
echo ""
|
|
546
|
+
echo -e "${BOLD}SUBCOMMANDS${RESET}"
|
|
547
|
+
echo -e " ${CYAN}analyze <issue>${RESET} Analyze issue and suggest labels (outputs JSON)"
|
|
548
|
+
echo -e " ${CYAN}label <issue>${RESET} Apply suggested labels to issue"
|
|
549
|
+
echo -e " ${CYAN}prioritize${RESET} Score and rank all open issues by priority"
|
|
550
|
+
echo -e " ${CYAN}team <issue>${RESET} Recommend team size & pipeline template"
|
|
551
|
+
echo -e " ${CYAN}batch${RESET} Analyze + label all unlabeled open issues"
|
|
552
|
+
echo -e " ${CYAN}report${RESET} Show triage statistics (type, complexity, priority)"
|
|
553
|
+
echo -e " ${CYAN}help${RESET} Show this help message"
|
|
554
|
+
echo ""
|
|
555
|
+
echo -e "${BOLD}EXAMPLES${RESET}"
|
|
556
|
+
echo -e " ${DIM}shipwright triage analyze 42${RESET}"
|
|
557
|
+
echo -e " ${DIM}shipwright triage label 42${RESET}"
|
|
558
|
+
echo -e " ${DIM}shipwright triage prioritize${RESET}"
|
|
559
|
+
echo -e " ${DIM}shipwright triage team 42${RESET}"
|
|
560
|
+
echo -e " ${DIM}shipwright triage batch${RESET}"
|
|
561
|
+
echo -e " ${DIM}shipwright triage report${RESET}"
|
|
562
|
+
echo ""
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
# ─── Main ──────────────────────────────────────────────────────────────────
|
|
566
|
+
|
|
567
|
+
main() {
|
|
568
|
+
local cmd="${1:-help}"
|
|
569
|
+
shift 2>/dev/null || true
|
|
570
|
+
|
|
571
|
+
case "$cmd" in
|
|
572
|
+
analyze)
|
|
573
|
+
cmd_analyze "$@"
|
|
574
|
+
;;
|
|
575
|
+
label)
|
|
576
|
+
cmd_label "$@"
|
|
577
|
+
;;
|
|
578
|
+
prioritize)
|
|
579
|
+
cmd_prioritize "$@"
|
|
580
|
+
;;
|
|
581
|
+
team)
|
|
582
|
+
cmd_team "$@"
|
|
583
|
+
;;
|
|
584
|
+
batch)
|
|
585
|
+
cmd_batch "$@"
|
|
586
|
+
;;
|
|
587
|
+
report)
|
|
588
|
+
cmd_report "$@"
|
|
589
|
+
;;
|
|
590
|
+
help|--help|-h)
|
|
591
|
+
cmd_help
|
|
592
|
+
;;
|
|
593
|
+
*)
|
|
594
|
+
error "Unknown subcommand: ${cmd}"
|
|
595
|
+
cmd_help
|
|
596
|
+
exit 1
|
|
597
|
+
;;
|
|
598
|
+
esac
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
602
|
+
main "$@"
|
|
603
|
+
fi
|
package/scripts/sw-upgrade.sh
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
3
|
# ║ sw upgrade — Detect and apply updates from the repo ║
|
|
4
4
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
5
|
-
VERSION="
|
|
5
|
+
VERSION="2.0.0"
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|