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,539 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright decompose — Intelligent Issue Decomposition ║
4
+ # ║ Analyze complexity · Auto-create subtasks · Track progress ║
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
+ val="${val//\"/\\\"}"
51
+ json_fields="${json_fields},\"${key}\":\"${val}\""
52
+ fi
53
+ done
54
+ mkdir -p "${HOME}/.shipwright"
55
+ echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
56
+ }
57
+
58
+ # ─── Configuration ─────────────────────────────────────────────────────────
59
+ COMPLEXITY_THRESHOLD=70 # Decompose if complexity > this
60
+ HOURS_THRESHOLD=8 # Decompose if estimated hours > this
61
+ MAX_SUBTASKS=5
62
+ MIN_SUBTASKS=3
63
+ DECOMPOSE_LABEL="subtask"
64
+ DECOMPOSED_MARKER_LABEL="decomposed"
65
+
66
+ # ─── Helper: Check if issue has label ──────────────────────────────────────
67
+ _has_label() {
68
+ local issue_num="$1"
69
+ local label="$2"
70
+
71
+ if [[ "$NO_GITHUB" == "true" ]]; then
72
+ return 1
73
+ fi
74
+
75
+ local labels
76
+ labels=$(gh issue view "$issue_num" --json labels --jq '.labels[].name' 2>/dev/null || echo "")
77
+ [[ "$labels" =~ $label ]]
78
+ }
79
+
80
+ # ─── Helper: Call Claude for complexity analysis ──────────────────────────
81
+ _decompose_call_claude() {
82
+ local prompt="$1"
83
+
84
+ # Verify claude CLI is available
85
+ if ! command -v claude >/dev/null 2>&1; then
86
+ error "claude CLI not found"
87
+ echo '{"error":"claude_cli_not_found"}'
88
+ return 1
89
+ fi
90
+
91
+ # Call Claude (--print mode returns raw text response, max-turns 1)
92
+ local response
93
+ if ! response=$(claude --print --max-turns 1 "$prompt" 2>/dev/null); then
94
+ error "Claude call failed"
95
+ echo '{"error":"claude_call_failed"}'
96
+ return 1
97
+ fi
98
+
99
+ # Extract JSON from the response
100
+ local result
101
+ result=$(echo "$response" | jq -c . 2>/dev/null || echo "")
102
+
103
+ if [[ -z "$result" || "$result" == "null" ]]; then
104
+ error "Failed to parse Claude response as JSON"
105
+ echo '{"error":"parse_failed"}'
106
+ return 1
107
+ fi
108
+
109
+ echo "$result"
110
+ }
111
+
112
+ # ─── Analyze Issue Complexity ──────────────────────────────────────────────
113
+ decompose_analyze() {
114
+ local issue_num="$1"
115
+
116
+ if [[ "$NO_GITHUB" == "true" ]]; then
117
+ # Mock data for testing (JSON only, no messages)
118
+ echo '{
119
+ "issue_number": '$issue_num',
120
+ "complexity_score": 85,
121
+ "estimated_hours": 12,
122
+ "should_decompose": true,
123
+ "reasoning": "Issue involves major architectural changes",
124
+ "subtasks": [
125
+ {
126
+ "title": "Subtask 1: Design phase",
127
+ "description": "Plan and document the new architecture"
128
+ },
129
+ {
130
+ "title": "Subtask 2: Implementation phase",
131
+ "description": "Implement core changes"
132
+ },
133
+ {
134
+ "title": "Subtask 3: Integration & testing",
135
+ "description": "Integrate changes and add tests"
136
+ }
137
+ ]
138
+ }'
139
+ return 0
140
+ fi
141
+
142
+ # Fetch issue details
143
+ local issue_json
144
+ issue_json=$(gh issue view "$issue_num" --json number,title,body,labels 2>/dev/null || echo "")
145
+
146
+ if [[ -z "$issue_json" ]]; then
147
+ error "Could not fetch issue #${issue_num}"
148
+ return 1
149
+ fi
150
+
151
+ local issue_title
152
+ issue_title=$(echo "$issue_json" | jq -r '.title' 2>/dev/null || echo "")
153
+
154
+ local issue_body
155
+ issue_body=$(echo "$issue_json" | jq -r '.body // ""' 2>/dev/null | head -500 || echo "")
156
+
157
+ local issue_labels
158
+ issue_labels=$(echo "$issue_json" | jq -r '.labels[].name' 2>/dev/null | tr '\n' ',' || echo "")
159
+
160
+ # Build prompt for Claude
161
+ local prompt
162
+ read -r -d '' prompt <<'PROMPT' || true
163
+ You are an issue complexity analyzer. Analyze the GitHub issue below and determine:
164
+ 1. Complexity score (1-100): How intricate/multi-faceted is the work?
165
+ 2. Estimated hours (1-100): How long would this realistically take?
166
+ 3. Should decompose: Is complexity > 70 OR hours > 8?
167
+ 4. If should decompose: Generate 3-5 focused, independent subtasks
168
+
169
+ Each subtask should be:
170
+ - Self-contained (can be worked on independently)
171
+ - Completable in one pipeline run (~20 iterations max)
172
+ - Have clear acceptance criteria
173
+ - Include test strategy
174
+
175
+ Return ONLY valid JSON (no markdown, no explanation):
176
+ {
177
+ "issue_number": <number>,
178
+ "complexity_score": <1-100>,
179
+ "estimated_hours": <1-100>,
180
+ "should_decompose": <true|false>,
181
+ "reasoning": "<brief explanation>",
182
+ "subtasks": [
183
+ {
184
+ "title": "Subtask N: <clear title>",
185
+ "description": "<1-2 sentences describing the work>",
186
+ "acceptance_criteria": ["criterion 1", "criterion 2"],
187
+ "test_approach": "<how to validate this subtask>"
188
+ }
189
+ ]
190
+ }
191
+
192
+ ISSUE #<issue_number>:
193
+ Title: <issue_title>
194
+ Body:
195
+ <issue_body>
196
+ Labels: <issue_labels>
197
+ PROMPT
198
+
199
+ # Replace placeholders
200
+ prompt="${prompt//<issue_number>/$issue_num}"
201
+ prompt="${prompt//<issue_title>/$issue_title}"
202
+ prompt="${prompt//<issue_body>/$issue_body}"
203
+ prompt="${prompt//<issue_labels>/$issue_labels}"
204
+
205
+ # Call Claude
206
+ local result
207
+ result=$(_decompose_call_claude "$prompt")
208
+
209
+ if [[ "$result" == *"error"* ]]; then
210
+ error "Claude analysis failed"
211
+ return 1
212
+ fi
213
+
214
+ echo "$result"
215
+ emit_event "decompose.analyzed" "issue=$issue_num" "result=$result"
216
+ }
217
+
218
+ # ─── Create Subtask Issues ──────────────────────────────────────────────────
219
+ decompose_create_subtasks() {
220
+ local issue_num="$1"
221
+ local analysis_json="$2"
222
+
223
+ if [[ "$NO_GITHUB" == "true" ]]; then
224
+ # Return mock subtask numbers (JSON-clean output only)
225
+ echo "123 124 125"
226
+ return 0
227
+ fi
228
+
229
+ # Fetch parent issue details for label inheritance
230
+ local parent_labels parent_title
231
+ parent_labels=$(gh issue view "$issue_num" --json labels --jq '.labels[].name' 2>/dev/null | tr '\n' ',' | sed 's/,$//' || echo "")
232
+ parent_title=$(gh issue view "$issue_num" --json title --jq '.title' 2>/dev/null || echo "")
233
+
234
+ # Extract subtasks from analysis
235
+ local subtask_count
236
+ subtask_count=$(echo "$analysis_json" | jq '.subtasks | length' 2>/dev/null || echo "0")
237
+
238
+ if [[ "$subtask_count" -eq 0 ]]; then
239
+ return 1
240
+ fi
241
+
242
+ local created_issue_nums=""
243
+ local idx=1
244
+
245
+ while [[ "$idx" -le "$subtask_count" ]]; do
246
+ local subtask
247
+ subtask=$(echo "$analysis_json" | jq ".subtasks[$((idx - 1))]" 2>/dev/null || echo "{}")
248
+
249
+ local subtask_title
250
+ subtask_title=$(echo "$subtask" | jq -r '.title // ""' 2>/dev/null || echo "")
251
+
252
+ if [[ -z "$subtask_title" ]]; then
253
+ error " Subtask #$idx: missing title"
254
+ idx=$((idx + 1))
255
+ continue
256
+ fi
257
+
258
+ # Build subtask description with acceptance criteria and test approach
259
+ local subtask_description
260
+ local acceptance_criteria
261
+ local test_approach
262
+
263
+ acceptance_criteria=$(echo "$subtask" | jq -r '.acceptance_criteria[]? // empty' 2>/dev/null | sed 's/^/- /' || echo "")
264
+ test_approach=$(echo "$subtask" | jq -r '.test_approach // ""' 2>/dev/null || echo "")
265
+
266
+ read -r -d '' subtask_description <<SUBEOF || true
267
+ ## Description
268
+ $(echo "$subtask" | jq -r '.description // ""' 2>/dev/null)
269
+
270
+ ## Part of
271
+ Issue #${issue_num}: ${parent_title}
272
+
273
+ ## Acceptance Criteria
274
+ ${acceptance_criteria:-None specified}
275
+
276
+ ## Test Approach
277
+ ${test_approach:-Run standard test suite}
278
+ SUBEOF
279
+
280
+ # Create the subtask issue
281
+ local create_labels="${parent_labels}"
282
+ if [[ -n "$create_labels" ]]; then
283
+ create_labels="${create_labels},${DECOMPOSE_LABEL}"
284
+ else
285
+ create_labels="$DECOMPOSE_LABEL"
286
+ fi
287
+
288
+ local subtask_issue_num
289
+ if subtask_issue_num=$(gh issue create \
290
+ --title "$subtask_title" \
291
+ --body "$subtask_description" \
292
+ --label "$create_labels" 2>/dev/null); then
293
+
294
+ created_issue_nums="${created_issue_nums}${subtask_issue_num} "
295
+ emit_event "decompose.subtask_created" "parent=$issue_num" "subtask=$subtask_issue_num"
296
+ fi
297
+
298
+ idx=$((idx + 1))
299
+ done
300
+
301
+ echo "${created_issue_nums% }"
302
+ }
303
+
304
+ # ─── Add Comment to Parent Issue ────────────────────────────────────────────
305
+ decompose_add_parent_comment() {
306
+ local issue_num="$1"
307
+ local subtask_nums="$2"
308
+
309
+ if [[ "$NO_GITHUB" == "true" ]]; then
310
+ return 0
311
+ fi
312
+
313
+ # Build comment with subtask links
314
+ local comment_body="## 🔄 Decomposed into subtasks
315
+
316
+ This issue was too ambitious for a single pipeline run. It has been decomposed into smaller, focused subtasks:
317
+
318
+ "
319
+
320
+ for subtask_num in $subtask_nums; do
321
+ comment_body="${comment_body}- #${subtask_num}
322
+ "
323
+ done
324
+
325
+ comment_body="${comment_body}
326
+ Each subtask can be completed independently and merged gradually. Close this issue once all subtasks are complete."
327
+
328
+ if gh issue comment "$issue_num" --body "$comment_body" 2>/dev/null; then
329
+ success "Added decomposition comment to issue #$issue_num"
330
+ return 0
331
+ else
332
+ warn "Failed to add comment to issue #$issue_num"
333
+ return 1
334
+ fi
335
+ }
336
+
337
+ # ─── Add Decomposed Label ───────────────────────────────────────────────────
338
+ decompose_mark_decomposed() {
339
+ local issue_num="$1"
340
+
341
+ if [[ "$NO_GITHUB" == "true" ]]; then
342
+ return 0
343
+ fi
344
+
345
+ if gh issue edit "$issue_num" --add-label "$DECOMPOSED_MARKER_LABEL" 2>/dev/null; then
346
+ success "Marked issue #$issue_num as decomposed"
347
+ return 0
348
+ else
349
+ warn "Failed to add decomposed label to issue #$issue_num"
350
+ return 1
351
+ fi
352
+ }
353
+
354
+ # ─── Main: Analyze Only ─────────────────────────────────────────────────────
355
+ cmd_analyze() {
356
+ local issue_num="${1:-}"
357
+
358
+ if [[ -z "$issue_num" ]]; then
359
+ error "Usage: sw-decompose.sh analyze <issue-number>"
360
+ return 1
361
+ fi
362
+
363
+ echo ""
364
+ info "Issue Complexity Analysis"
365
+ info "Analyzing issue #${issue_num}..."
366
+ echo ""
367
+
368
+ local analysis
369
+ analysis=$(decompose_analyze "$issue_num") || return 1
370
+
371
+ # Pretty-print the JSON result
372
+ echo "$analysis" | jq '.' 2>/dev/null || echo "$analysis"
373
+
374
+ echo ""
375
+ local should_decompose
376
+ should_decompose=$(echo "$analysis" | jq '.should_decompose' 2>/dev/null || echo "false")
377
+
378
+ if [[ "$should_decompose" == "true" ]]; then
379
+ local complexity
380
+ complexity=$(echo "$analysis" | jq '.complexity_score' 2>/dev/null || echo "0")
381
+ local hours
382
+ hours=$(echo "$analysis" | jq '.estimated_hours' 2>/dev/null || echo "0")
383
+ warn "Issue is too ambitious (complexity=${complexity}, hours=${hours})"
384
+ echo "Run 'sw decompose $issue_num' to auto-create subtasks"
385
+ else
386
+ success "Issue is simple enough for a single pipeline run"
387
+ fi
388
+ }
389
+
390
+ # ─── Main: Decompose & Create Subtasks ──────────────────────────────────────
391
+ cmd_decompose() {
392
+ local issue_num="${1:-}"
393
+
394
+ if [[ -z "$issue_num" ]]; then
395
+ error "Usage: sw-decompose.sh decompose <issue-number>"
396
+ return 1
397
+ fi
398
+
399
+ echo ""
400
+ info "Decomposing Issue #${issue_num}"
401
+ echo ""
402
+
403
+ # Check if already decomposed
404
+ if _has_label "$issue_num" "$DECOMPOSED_MARKER_LABEL"; then
405
+ warn "Issue #$issue_num is already marked as decomposed"
406
+ return 0
407
+ fi
408
+
409
+ # Analyze
410
+ local analysis
411
+ analysis=$(decompose_analyze "$issue_num") || return 1
412
+
413
+ local should_decompose
414
+ should_decompose=$(echo "$analysis" | jq '.should_decompose' 2>/dev/null || echo "false")
415
+
416
+ if [[ "$should_decompose" != "true" ]]; then
417
+ success "Issue #$issue_num is simple enough — no decomposition needed"
418
+ emit_event "decompose.skipped" "issue=$issue_num" "reason=simple"
419
+ return 0
420
+ fi
421
+
422
+ # Create subtasks
423
+ info "Creating subtask issues..."
424
+ local subtask_nums
425
+ subtask_nums=$(decompose_create_subtasks "$issue_num" "$analysis") || return 1
426
+
427
+ if [[ -z "$subtask_nums" ]]; then
428
+ error "No subtasks were created"
429
+ return 1
430
+ fi
431
+
432
+ # Add parent comment
433
+ decompose_add_parent_comment "$issue_num" "$subtask_nums"
434
+
435
+ # Mark as decomposed
436
+ decompose_mark_decomposed "$issue_num"
437
+
438
+ echo ""
439
+ local subtask_count
440
+ subtask_count=$(echo "$subtask_nums" | wc -w)
441
+ success "Issue #$issue_num decomposed into $subtask_count subtasks:"
442
+ for subtask_num in $subtask_nums; do
443
+ echo " - #$subtask_num"
444
+ done
445
+
446
+ emit_event "decompose.completed" "issue=$issue_num" "subtask_count=$(echo $subtask_nums | wc -w)"
447
+ }
448
+
449
+ # ─── Main: Auto (for daemon) ────────────────────────────────────────────────
450
+ cmd_auto() {
451
+ local issue_num="${1:-}"
452
+
453
+ if [[ -z "$issue_num" ]]; then
454
+ error "Usage: sw-decompose.sh auto <issue-number>"
455
+ return 1
456
+ fi
457
+
458
+ # Check if already decomposed
459
+ if _has_label "$issue_num" "$DECOMPOSED_MARKER_LABEL"; then
460
+ return 0
461
+ fi
462
+
463
+ # Analyze
464
+ local analysis
465
+ analysis=$(decompose_analyze "$issue_num") || return 1
466
+
467
+ local should_decompose
468
+ should_decompose=$(echo "$analysis" | jq '.should_decompose' 2>/dev/null || echo "false")
469
+
470
+ if [[ "$should_decompose" != "true" ]]; then
471
+ return 0
472
+ fi
473
+
474
+ # Create subtasks
475
+ local subtask_nums
476
+ subtask_nums=$(decompose_create_subtasks "$issue_num" "$analysis") || return 1
477
+
478
+ if [[ -z "$subtask_nums" ]]; then
479
+ return 1
480
+ fi
481
+
482
+ # Add parent comment
483
+ decompose_add_parent_comment "$issue_num" "$subtask_nums"
484
+
485
+ # Mark as decomposed
486
+ decompose_mark_decomposed "$issue_num"
487
+
488
+ emit_event "decompose.auto_completed" "issue=$issue_num" "subtask_count=$(echo $subtask_nums | wc -w)"
489
+
490
+ return 0
491
+ }
492
+
493
+ # ─── CLI Router ──────────────────────────────────────────────────────────────
494
+ main() {
495
+ local cmd="${1:-help}"
496
+
497
+ case "$cmd" in
498
+ analyze)
499
+ cmd_analyze "${2:-}"
500
+ ;;
501
+ decompose)
502
+ cmd_decompose "${2:-}"
503
+ ;;
504
+ auto)
505
+ cmd_auto "${2:-}"
506
+ ;;
507
+ help|--help|-h)
508
+ echo ""
509
+ echo -e "${CYAN}${BOLD}shipwright decompose${RESET} — Issue Complexity Analysis & Decomposition"
510
+ echo ""
511
+ echo -e "${BOLD}USAGE${RESET}"
512
+ echo -e " ${CYAN}sw decompose${RESET} <command> <issue-number>"
513
+ echo ""
514
+ echo -e "${BOLD}COMMANDS${RESET}"
515
+ echo -e " ${CYAN}analyze${RESET} <num> Analyze complexity without creating issues"
516
+ echo -e " ${CYAN}decompose${RESET} <num> Analyze + create subtask issues if needed"
517
+ echo -e " ${CYAN}auto${RESET} <num> Daemon mode: silent decomposition (returns 0)"
518
+ echo ""
519
+ echo -e "${BOLD}EXAMPLES${RESET}"
520
+ echo -e " ${DIM}sw decompose analyze 42${RESET} # See complexity score and reasoning"
521
+ echo -e " ${DIM}sw decompose decompose 42${RESET} # Create subtasks for issue #42"
522
+ echo -e " ${DIM}sw decompose auto 42${RESET} # Used by daemon (no output)"
523
+ echo ""
524
+ ;;
525
+ --version|-v)
526
+ echo "sw-decompose $VERSION"
527
+ ;;
528
+ *)
529
+ error "Unknown command: $cmd"
530
+ echo "Run 'sw decompose help' for usage"
531
+ exit 1
532
+ ;;
533
+ esac
534
+ }
535
+
536
+ # ─── Guard: only run main if not sourced ──────────────────────────────────
537
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
538
+ main "$@"
539
+ fi