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.
Files changed (108) hide show
  1. package/README.md +114 -36
  2. package/completions/_shipwright +212 -32
  3. package/completions/shipwright.bash +97 -25
  4. package/docs/strategy/01-market-research.md +619 -0
  5. package/docs/strategy/02-mission-and-brand.md +587 -0
  6. package/docs/strategy/03-gtm-and-roadmap.md +759 -0
  7. package/docs/strategy/QUICK-START.txt +289 -0
  8. package/docs/strategy/README.md +172 -0
  9. package/package.json +4 -2
  10. package/scripts/sw +208 -1
  11. package/scripts/sw-activity.sh +500 -0
  12. package/scripts/sw-adaptive.sh +925 -0
  13. package/scripts/sw-adversarial.sh +1 -1
  14. package/scripts/sw-architecture-enforcer.sh +1 -1
  15. package/scripts/sw-auth.sh +613 -0
  16. package/scripts/sw-autonomous.sh +664 -0
  17. package/scripts/sw-changelog.sh +704 -0
  18. package/scripts/sw-checkpoint.sh +1 -1
  19. package/scripts/sw-ci.sh +602 -0
  20. package/scripts/sw-cleanup.sh +1 -1
  21. package/scripts/sw-code-review.sh +637 -0
  22. package/scripts/sw-connect.sh +1 -1
  23. package/scripts/sw-context.sh +605 -0
  24. package/scripts/sw-cost.sh +1 -1
  25. package/scripts/sw-daemon.sh +432 -130
  26. package/scripts/sw-dashboard.sh +1 -1
  27. package/scripts/sw-db.sh +540 -0
  28. package/scripts/sw-decompose.sh +539 -0
  29. package/scripts/sw-deps.sh +551 -0
  30. package/scripts/sw-developer-simulation.sh +1 -1
  31. package/scripts/sw-discovery.sh +412 -0
  32. package/scripts/sw-docs-agent.sh +539 -0
  33. package/scripts/sw-docs.sh +1 -1
  34. package/scripts/sw-doctor.sh +59 -1
  35. package/scripts/sw-dora.sh +615 -0
  36. package/scripts/sw-durable.sh +710 -0
  37. package/scripts/sw-e2e-orchestrator.sh +535 -0
  38. package/scripts/sw-eventbus.sh +393 -0
  39. package/scripts/sw-feedback.sh +471 -0
  40. package/scripts/sw-fix.sh +1 -1
  41. package/scripts/sw-fleet-discover.sh +567 -0
  42. package/scripts/sw-fleet-viz.sh +404 -0
  43. package/scripts/sw-fleet.sh +8 -1
  44. package/scripts/sw-github-app.sh +596 -0
  45. package/scripts/sw-github-checks.sh +1 -1
  46. package/scripts/sw-github-deploy.sh +1 -1
  47. package/scripts/sw-github-graphql.sh +1 -1
  48. package/scripts/sw-guild.sh +569 -0
  49. package/scripts/sw-heartbeat.sh +1 -1
  50. package/scripts/sw-hygiene.sh +559 -0
  51. package/scripts/sw-incident.sh +617 -0
  52. package/scripts/sw-init.sh +88 -1
  53. package/scripts/sw-instrument.sh +699 -0
  54. package/scripts/sw-intelligence.sh +1 -1
  55. package/scripts/sw-jira.sh +1 -1
  56. package/scripts/sw-launchd.sh +363 -28
  57. package/scripts/sw-linear.sh +1 -1
  58. package/scripts/sw-logs.sh +1 -1
  59. package/scripts/sw-loop.sh +64 -3
  60. package/scripts/sw-memory.sh +1 -1
  61. package/scripts/sw-mission-control.sh +487 -0
  62. package/scripts/sw-model-router.sh +545 -0
  63. package/scripts/sw-otel.sh +596 -0
  64. package/scripts/sw-oversight.sh +689 -0
  65. package/scripts/sw-pipeline-composer.sh +1 -1
  66. package/scripts/sw-pipeline-vitals.sh +1 -1
  67. package/scripts/sw-pipeline.sh +687 -24
  68. package/scripts/sw-pm.sh +693 -0
  69. package/scripts/sw-pr-lifecycle.sh +522 -0
  70. package/scripts/sw-predictive.sh +1 -1
  71. package/scripts/sw-prep.sh +1 -1
  72. package/scripts/sw-ps.sh +1 -1
  73. package/scripts/sw-public-dashboard.sh +798 -0
  74. package/scripts/sw-quality.sh +595 -0
  75. package/scripts/sw-reaper.sh +1 -1
  76. package/scripts/sw-recruit.sh +573 -0
  77. package/scripts/sw-regression.sh +642 -0
  78. package/scripts/sw-release-manager.sh +736 -0
  79. package/scripts/sw-release.sh +706 -0
  80. package/scripts/sw-remote.sh +1 -1
  81. package/scripts/sw-replay.sh +520 -0
  82. package/scripts/sw-retro.sh +691 -0
  83. package/scripts/sw-scale.sh +444 -0
  84. package/scripts/sw-security-audit.sh +505 -0
  85. package/scripts/sw-self-optimize.sh +1 -1
  86. package/scripts/sw-session.sh +1 -1
  87. package/scripts/sw-setup.sh +1 -1
  88. package/scripts/sw-standup.sh +712 -0
  89. package/scripts/sw-status.sh +1 -1
  90. package/scripts/sw-strategic.sh +658 -0
  91. package/scripts/sw-stream.sh +450 -0
  92. package/scripts/sw-swarm.sh +583 -0
  93. package/scripts/sw-team-stages.sh +511 -0
  94. package/scripts/sw-templates.sh +1 -1
  95. package/scripts/sw-testgen.sh +515 -0
  96. package/scripts/sw-tmux-pipeline.sh +554 -0
  97. package/scripts/sw-tmux.sh +1 -1
  98. package/scripts/sw-trace.sh +485 -0
  99. package/scripts/sw-tracker-github.sh +188 -0
  100. package/scripts/sw-tracker-jira.sh +172 -0
  101. package/scripts/sw-tracker-linear.sh +251 -0
  102. package/scripts/sw-tracker.sh +117 -2
  103. package/scripts/sw-triage.sh +603 -0
  104. package/scripts/sw-upgrade.sh +1 -1
  105. package/scripts/sw-ux.sh +677 -0
  106. package/scripts/sw-webhook.sh +627 -0
  107. package/scripts/sw-widgets.sh +530 -0
  108. 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
@@ -2,7 +2,7 @@
2
2
  # ╔═══════════════════════════════════════════════════════════════════════════╗
3
3
  # ║ sw upgrade — Detect and apply updates from the repo ║
4
4
  # ╚═══════════════════════════════════════════════════════════════════════════╝
5
- VERSION="1.10.0"
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