shipwright-cli 1.9.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 (117) hide show
  1. package/.claude/hooks/post-tool-use.sh +12 -5
  2. package/README.md +114 -36
  3. package/completions/_shipwright +212 -32
  4. package/completions/shipwright.bash +97 -25
  5. package/docs/strategy/01-market-research.md +619 -0
  6. package/docs/strategy/02-mission-and-brand.md +587 -0
  7. package/docs/strategy/03-gtm-and-roadmap.md +759 -0
  8. package/docs/strategy/QUICK-START.txt +289 -0
  9. package/docs/strategy/README.md +172 -0
  10. package/package.json +4 -2
  11. package/scripts/sw +217 -2
  12. package/scripts/sw-activity.sh +500 -0
  13. package/scripts/sw-adaptive.sh +925 -0
  14. package/scripts/sw-adversarial.sh +1 -1
  15. package/scripts/sw-architecture-enforcer.sh +1 -1
  16. package/scripts/sw-auth.sh +613 -0
  17. package/scripts/sw-autonomous.sh +664 -0
  18. package/scripts/sw-changelog.sh +704 -0
  19. package/scripts/sw-checkpoint.sh +79 -1
  20. package/scripts/sw-ci.sh +602 -0
  21. package/scripts/sw-cleanup.sh +192 -7
  22. package/scripts/sw-code-review.sh +637 -0
  23. package/scripts/sw-connect.sh +1 -1
  24. package/scripts/sw-context.sh +605 -0
  25. package/scripts/sw-cost.sh +1 -1
  26. package/scripts/sw-daemon.sh +812 -138
  27. package/scripts/sw-dashboard.sh +1 -1
  28. package/scripts/sw-db.sh +540 -0
  29. package/scripts/sw-decompose.sh +539 -0
  30. package/scripts/sw-deps.sh +551 -0
  31. package/scripts/sw-developer-simulation.sh +1 -1
  32. package/scripts/sw-discovery.sh +412 -0
  33. package/scripts/sw-docs-agent.sh +539 -0
  34. package/scripts/sw-docs.sh +1 -1
  35. package/scripts/sw-doctor.sh +59 -1
  36. package/scripts/sw-dora.sh +615 -0
  37. package/scripts/sw-durable.sh +710 -0
  38. package/scripts/sw-e2e-orchestrator.sh +535 -0
  39. package/scripts/sw-eventbus.sh +393 -0
  40. package/scripts/sw-feedback.sh +471 -0
  41. package/scripts/sw-fix.sh +1 -1
  42. package/scripts/sw-fleet-discover.sh +567 -0
  43. package/scripts/sw-fleet-viz.sh +404 -0
  44. package/scripts/sw-fleet.sh +8 -1
  45. package/scripts/sw-github-app.sh +596 -0
  46. package/scripts/sw-github-checks.sh +1 -1
  47. package/scripts/sw-github-deploy.sh +1 -1
  48. package/scripts/sw-github-graphql.sh +1 -1
  49. package/scripts/sw-guild.sh +569 -0
  50. package/scripts/sw-heartbeat.sh +1 -1
  51. package/scripts/sw-hygiene.sh +559 -0
  52. package/scripts/sw-incident.sh +617 -0
  53. package/scripts/sw-init.sh +88 -1
  54. package/scripts/sw-instrument.sh +699 -0
  55. package/scripts/sw-intelligence.sh +1 -1
  56. package/scripts/sw-jira.sh +1 -1
  57. package/scripts/sw-launchd.sh +366 -31
  58. package/scripts/sw-linear.sh +1 -1
  59. package/scripts/sw-logs.sh +1 -1
  60. package/scripts/sw-loop.sh +507 -51
  61. package/scripts/sw-memory.sh +198 -3
  62. package/scripts/sw-mission-control.sh +487 -0
  63. package/scripts/sw-model-router.sh +545 -0
  64. package/scripts/sw-otel.sh +596 -0
  65. package/scripts/sw-oversight.sh +689 -0
  66. package/scripts/sw-pipeline-composer.sh +8 -8
  67. package/scripts/sw-pipeline-vitals.sh +1096 -0
  68. package/scripts/sw-pipeline.sh +2451 -180
  69. package/scripts/sw-pm.sh +693 -0
  70. package/scripts/sw-pr-lifecycle.sh +522 -0
  71. package/scripts/sw-predictive.sh +1 -1
  72. package/scripts/sw-prep.sh +1 -1
  73. package/scripts/sw-ps.sh +4 -3
  74. package/scripts/sw-public-dashboard.sh +798 -0
  75. package/scripts/sw-quality.sh +595 -0
  76. package/scripts/sw-reaper.sh +5 -3
  77. package/scripts/sw-recruit.sh +573 -0
  78. package/scripts/sw-regression.sh +642 -0
  79. package/scripts/sw-release-manager.sh +736 -0
  80. package/scripts/sw-release.sh +706 -0
  81. package/scripts/sw-remote.sh +1 -1
  82. package/scripts/sw-replay.sh +520 -0
  83. package/scripts/sw-retro.sh +691 -0
  84. package/scripts/sw-scale.sh +444 -0
  85. package/scripts/sw-security-audit.sh +505 -0
  86. package/scripts/sw-self-optimize.sh +109 -8
  87. package/scripts/sw-session.sh +31 -9
  88. package/scripts/sw-setup.sh +1 -1
  89. package/scripts/sw-standup.sh +712 -0
  90. package/scripts/sw-status.sh +192 -1
  91. package/scripts/sw-strategic.sh +658 -0
  92. package/scripts/sw-stream.sh +450 -0
  93. package/scripts/sw-swarm.sh +583 -0
  94. package/scripts/sw-team-stages.sh +511 -0
  95. package/scripts/sw-templates.sh +1 -1
  96. package/scripts/sw-testgen.sh +515 -0
  97. package/scripts/sw-tmux-pipeline.sh +554 -0
  98. package/scripts/sw-tmux.sh +1 -1
  99. package/scripts/sw-trace.sh +485 -0
  100. package/scripts/sw-tracker-github.sh +188 -0
  101. package/scripts/sw-tracker-jira.sh +172 -0
  102. package/scripts/sw-tracker-linear.sh +251 -0
  103. package/scripts/sw-tracker.sh +117 -2
  104. package/scripts/sw-triage.sh +603 -0
  105. package/scripts/sw-upgrade.sh +1 -1
  106. package/scripts/sw-ux.sh +677 -0
  107. package/scripts/sw-webhook.sh +627 -0
  108. package/scripts/sw-widgets.sh +530 -0
  109. package/scripts/sw-worktree.sh +1 -1
  110. package/templates/pipelines/autonomous.json +8 -1
  111. package/templates/pipelines/cost-aware.json +21 -0
  112. package/templates/pipelines/deployed.json +40 -6
  113. package/templates/pipelines/enterprise.json +16 -2
  114. package/templates/pipelines/fast.json +19 -0
  115. package/templates/pipelines/full.json +16 -2
  116. package/templates/pipelines/hotfix.json +19 -0
  117. package/templates/pipelines/standard.json +19 -0
@@ -0,0 +1,693 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright pm — Autonomous PM Agent for Team Orchestration ║
4
+ # ║ Intelligent team sizing · Composition · Stage orchestration ║
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
+
12
+ # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
13
+ CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
14
+ PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
15
+ BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
16
+ GREEN='\033[38;2;74;222;128m' # success
17
+ YELLOW='\033[38;2;250;204;21m' # warning
18
+ RED='\033[38;2;248;113;113m' # error
19
+ DIM='\033[2m'
20
+ BOLD='\033[1m'
21
+ RESET='\033[0m'
22
+
23
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
24
+ # shellcheck source=lib/compat.sh
25
+ [[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
26
+
27
+ # ─── Output Helpers ─────────────────────────────────────────────────────────
28
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
29
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
30
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
31
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
32
+
33
+ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
34
+ now_epoch() { date +%s; }
35
+
36
+ # ─── PM History Storage ──────────────────────────────────────────────────────
37
+ PM_HISTORY="${HOME}/.shipwright/pm-history.json"
38
+
39
+ emit_event() {
40
+ local event_type="$1"
41
+ shift
42
+ local json_fields=""
43
+ for kv in "$@"; do
44
+ local key="${kv%%=*}"
45
+ local val="${kv#*=}"
46
+ if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
47
+ json_fields="${json_fields},\"${key}\":${val}"
48
+ else
49
+ val="${val//\"/\\\"}"
50
+ json_fields="${json_fields},\"${key}\":\"${val}\""
51
+ fi
52
+ done
53
+ mkdir -p "${HOME}/.shipwright"
54
+ local events_file="${HOME}/.shipwright/events.jsonl"
55
+ echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$events_file"
56
+ }
57
+
58
+ # ─── Ensure PM history file exists ───────────────────────────────────────────
59
+ ensure_pm_history() {
60
+ mkdir -p "${HOME}/.shipwright"
61
+ if [[ ! -f "$PM_HISTORY" ]]; then
62
+ echo '{"decisions":[],"outcomes":[]}' > "$PM_HISTORY"
63
+ fi
64
+ }
65
+
66
+ # ─── Help ───────────────────────────────────────────────────────────────────
67
+ show_help() {
68
+ echo -e "${CYAN}${BOLD}shipwright pm${RESET} ${DIM}v${VERSION}${RESET} — Autonomous PM agent for team orchestration"
69
+ echo ""
70
+ echo -e "${BOLD}USAGE${RESET}"
71
+ echo -e " ${CYAN}shipwright pm${RESET} <subcommand> [options]"
72
+ echo ""
73
+ echo -e "${BOLD}SUBCOMMANDS${RESET}"
74
+ echo -e " ${CYAN}analyze${RESET} <issue-num> Deep analysis of issue complexity and scope"
75
+ echo -e " ${CYAN}team${RESET} <issue-num> Recommend team composition and pipeline"
76
+ echo -e " ${CYAN}orchestrate${RESET} <issue-num> Plan stage execution and parallelization"
77
+ echo -e " ${CYAN}recommend${RESET} <issue-num> Full PM recommendation (all above combined)"
78
+ echo -e " ${CYAN}learn${RESET} <issue-num> <outcome> Record outcome (success|failure) for learning"
79
+ echo -e " ${CYAN}history${RESET} [--json|--pattern] Show past decisions and outcomes"
80
+ echo -e " ${CYAN}help${RESET} Show this help message"
81
+ echo ""
82
+ echo -e "${BOLD}EXAMPLES${RESET}"
83
+ echo -e " ${DIM}shipwright pm analyze 42${RESET} # Analyze issue 42"
84
+ echo -e " ${DIM}shipwright pm team 42${RESET} # Get team recommendation"
85
+ echo -e " ${DIM}shipwright pm recommend 42${RESET} # Full recommendation"
86
+ echo -e " ${DIM}shipwright pm learn 42 success${RESET} # Record successful outcome"
87
+ echo -e " ${DIM}shipwright pm history${RESET} # Show past decisions"
88
+ echo -e " ${DIM}shipwright pm history --pattern${RESET} # Show success patterns"
89
+ echo ""
90
+ echo -e "${DIM}Docs: https://sethdford.github.io/shipwright | GitHub: https://github.com/sethdford/shipwright${RESET}"
91
+ }
92
+
93
+ # ─── analyze_issue <issue_num> ───────────────────────────────────────────────
94
+ # Fetches issue and analyzes file scope, complexity, and risk
95
+ analyze_issue() {
96
+ local issue_num="$1"
97
+ local analysis
98
+
99
+ # Check if gh is available and not disabled
100
+ if [[ -n "${NO_GITHUB:-}" ]]; then
101
+ warn "GitHub API disabled (NO_GITHUB set)"
102
+ # Return mock analysis
103
+ analysis=$(jq -n \
104
+ --arg issue "$issue_num" \
105
+ --arg file_scope "mock" \
106
+ --arg complexity "5" \
107
+ --arg risk "medium" \
108
+ --arg effort_hours "8" \
109
+ '{
110
+ issue: $issue,
111
+ file_scope: $file_scope,
112
+ complexity: ($complexity | tonumber),
113
+ risk: $risk,
114
+ estimated_effort_hours: ($effort_hours | tonumber),
115
+ recommendation: "mock analysis - GitHub API disabled"
116
+ }')
117
+ echo "$analysis"
118
+ return 0
119
+ fi
120
+
121
+ # Fetch issue metadata
122
+ local issue_data
123
+ if ! issue_data=$(gh issue view "$issue_num" --json title,body,labels,createdAt 2>/dev/null); then
124
+ error "Failed to fetch issue #${issue_num}"
125
+ return 1
126
+ fi
127
+
128
+ local title body labels
129
+ title=$(echo "$issue_data" | jq -r '.title // ""')
130
+ body=$(echo "$issue_data" | jq -r '.body // ""')
131
+ labels=$(echo "$issue_data" | jq -r '.labels[].name' | tr '\n' ',' | sed 's/,$//')
132
+
133
+ # Analyze title and body for keywords
134
+ local is_bugfix is_feature is_refactor is_security is_perf
135
+ is_bugfix=$(echo "$title $body" | grep -iq "bug\|fix\|issue" && echo "true" || echo "false")
136
+ is_feature=$(echo "$title $body" | grep -iq "feature\|add\|new\|implement" && echo "true" || echo "false")
137
+ is_refactor=$(echo "$title $body" | grep -iq "refactor\|refactoring\|cleanup" && echo "true" || echo "false")
138
+ is_security=$(echo "$title $body" | grep -iq "security\|vulnerability\|cve\|auth" && echo "true" || echo "false")
139
+ is_perf=$(echo "$title $body" | grep -iq "performance\|perf\|speed\|optimize" && echo "true" || echo "false")
140
+
141
+ # Count estimated files affected by analyzing body content
142
+ local file_scope complexity risk estimated_hours
143
+ local files_mentioned
144
+ files_mentioned=$(echo "$body" | grep -o '\b[a-zA-Z0-9_.-]*\.[a-z]*' | sort -u | wc -l || echo "0")
145
+ files_mentioned=$((files_mentioned + 1)) # At least 1 file
146
+
147
+ # Determine file scope
148
+ if [[ "$files_mentioned" -le 2 ]]; then
149
+ file_scope="single_module"
150
+ elif [[ "$files_mentioned" -le 5 ]]; then
151
+ file_scope="multiple_modules"
152
+ else
153
+ file_scope="cross_system"
154
+ fi
155
+
156
+ # Calculate complexity (1-10 scale)
157
+ complexity=5
158
+ [[ "$is_bugfix" == "true" ]] && complexity=$((complexity - 2))
159
+ [[ "$is_refactor" == "true" ]] && complexity=$((complexity + 2))
160
+ [[ "$is_feature" == "true" ]] && complexity=$((complexity + 1))
161
+ [[ "$is_security" == "true" ]] && complexity=$((complexity + 3))
162
+ [[ "$is_perf" == "true" ]] && complexity=$((complexity + 2))
163
+ [[ "${#body}" -gt 500 ]] && complexity=$((complexity + 1))
164
+ complexity=$((complexity > 10 ? 10 : complexity < 1 ? 1 : complexity))
165
+
166
+ # Determine risk
167
+ if [[ "$is_security" == "true" ]]; then
168
+ risk="critical"
169
+ elif [[ "$is_refactor" == "true" ]]; then
170
+ risk="high"
171
+ elif [[ "$is_perf" == "true" ]]; then
172
+ risk="medium"
173
+ elif [[ "$is_bugfix" == "true" && "$file_scope" == "single_module" ]]; then
174
+ risk="low"
175
+ else
176
+ risk="medium"
177
+ fi
178
+
179
+ # Estimate effort
180
+ case "$complexity" in
181
+ 1|2|3) estimated_hours=4 ;;
182
+ 4|5|6) estimated_hours=8 ;;
183
+ 7|8) estimated_hours=16 ;;
184
+ 9|10) estimated_hours=32 ;;
185
+ *) estimated_hours=8 ;;
186
+ esac
187
+
188
+ analysis=$(jq -n \
189
+ --arg issue "$issue_num" \
190
+ --arg file_scope "$file_scope" \
191
+ --arg complexity "$complexity" \
192
+ --arg risk "$risk" \
193
+ --arg effort_hours "$estimated_hours" \
194
+ --arg title "$title" \
195
+ --arg is_bugfix "$is_bugfix" \
196
+ --arg is_feature "$is_feature" \
197
+ --arg is_refactor "$is_refactor" \
198
+ --arg is_security "$is_security" \
199
+ --arg is_perf "$is_perf" \
200
+ --arg files_count "$files_mentioned" \
201
+ --arg labels "$labels" \
202
+ '{
203
+ issue: $issue,
204
+ title: $title,
205
+ file_scope: $file_scope,
206
+ complexity: ($complexity | tonumber),
207
+ risk: $risk,
208
+ estimated_effort_hours: ($effort_hours | tonumber),
209
+ estimated_files_affected: ($files_count | tonumber),
210
+ labels: $labels,
211
+ characteristics: {
212
+ is_bugfix: ($is_bugfix == "true"),
213
+ is_feature: ($is_feature == "true"),
214
+ is_refactor: ($is_refactor == "true"),
215
+ is_security: ($is_security == "true"),
216
+ is_performance_critical: ($is_perf == "true")
217
+ },
218
+ recommendation: ("Based on complexity " + $complexity + ", estimated " + $effort_hours + "h effort")
219
+ }')
220
+
221
+ echo "$analysis"
222
+ }
223
+
224
+ # ─── recommend_team <analysis_json> ──────────────────────────────────────────
225
+ # Based on analysis, recommend team composition
226
+ recommend_team() {
227
+ local analysis="$1"
228
+
229
+ local complexity risk is_security is_perf file_scope
230
+ complexity=$(echo "$analysis" | jq -r '.complexity')
231
+ risk=$(echo "$analysis" | jq -r '.risk')
232
+ is_security=$(echo "$analysis" | jq -r '.characteristics.is_security')
233
+ is_perf=$(echo "$analysis" | jq -r '.characteristics.is_performance_critical')
234
+ file_scope=$(echo "$analysis" | jq -r '.file_scope')
235
+
236
+ local roles template model max_iterations estimated_agents
237
+ local confidence risk_factors mitigations
238
+
239
+ # Determine base team and template
240
+ if [[ "$complexity" -le 3 && "$file_scope" == "single_module" ]]; then
241
+ # Simple bugfix
242
+ roles="builder"
243
+ template="fast"
244
+ estimated_agents=1
245
+ model="haiku"
246
+ max_iterations=3
247
+ confidence=85
248
+ risk_factors="Low complexity, single module"
249
+ mitigations="Standard code review"
250
+ elif [[ "$complexity" -le 5 && "$file_scope" == "multiple_modules" ]]; then
251
+ # Small feature
252
+ roles="builder,tester"
253
+ template="standard"
254
+ estimated_agents=2
255
+ model="sonnet"
256
+ max_iterations=5
257
+ confidence=80
258
+ risk_factors="Moderate complexity across modules"
259
+ mitigations="Build + test iteration cycles"
260
+ elif [[ "$complexity" -le 7 && "$file_scope" == "multiple_modules" ]]; then
261
+ # Medium feature
262
+ roles="builder,builder,tester"
263
+ template="standard"
264
+ estimated_agents=3
265
+ model="sonnet"
266
+ max_iterations=6
267
+ confidence=75
268
+ risk_factors="Moderate-high complexity, coordination needed"
269
+ mitigations="Parallel builders with test validation"
270
+ else
271
+ # Complex feature or cross-system change
272
+ roles="builder,builder,tester,reviewer"
273
+ template="full"
274
+ estimated_agents=4
275
+ model="opus"
276
+ max_iterations=8
277
+ confidence=70
278
+ risk_factors="High complexity, cross-system impact"
279
+ mitigations="Full pipeline with review gates"
280
+ fi
281
+
282
+ # Add security specialist if needed
283
+ if [[ "$is_security" == "true" ]]; then
284
+ roles="${roles},security"
285
+ estimated_agents=$((estimated_agents + 1))
286
+ confidence=$((confidence - 10))
287
+ risk_factors="${risk_factors}; security-sensitive changes"
288
+ mitigations="${mitigations}; security review before merge"
289
+ fi
290
+
291
+ # Adjust for risk level
292
+ case "$risk" in
293
+ critical)
294
+ template="enterprise"
295
+ max_iterations=$((max_iterations + 4))
296
+ confidence=$((confidence - 15))
297
+ risk_factors="${risk_factors}; critical risk"
298
+ mitigations="${mitigations}; emergency rollback plan"
299
+ ;;
300
+ high)
301
+ template="full"
302
+ max_iterations=$((max_iterations + 2))
303
+ confidence=$((confidence - 5))
304
+ ;;
305
+ esac
306
+
307
+ # Add performance specialist if needed
308
+ if [[ "$is_perf" == "true" ]]; then
309
+ roles="${roles},optimizer"
310
+ estimated_agents=$((estimated_agents + 1))
311
+ confidence=$((confidence - 5))
312
+ risk_factors="${risk_factors}; performance-critical"
313
+ mitigations="${mitigations}; performance benchmarking"
314
+ fi
315
+
316
+ # Cap confidence
317
+ confidence=$((confidence > 95 ? 95 : confidence < 50 ? 50 : confidence))
318
+
319
+ local team_rec
320
+ team_rec=$(jq -n \
321
+ --arg roles "$roles" \
322
+ --arg template "$template" \
323
+ --arg model "$model" \
324
+ --arg max_iter "$max_iterations" \
325
+ --arg agents "$estimated_agents" \
326
+ --arg confidence "$confidence" \
327
+ --arg risk_factors "$risk_factors" \
328
+ --arg mitigations "$mitigations" \
329
+ '{
330
+ roles: ($roles | split(",")),
331
+ template: $template,
332
+ model: $model,
333
+ max_iterations: ($max_iter | tonumber),
334
+ estimated_agents: ($agents | tonumber),
335
+ confidence_percent: ($confidence | tonumber),
336
+ risk_factors: $risk_factors,
337
+ mitigation_strategies: $mitigations
338
+ }')
339
+
340
+ echo "$team_rec"
341
+ }
342
+
343
+ # ─── orchestrate_stages <analysis_json> ──────────────────────────────────────
344
+ # Plan which stages to run and in what parallel groups
345
+ orchestrate_stages() {
346
+ local analysis="$1"
347
+
348
+ local complexity file_scope
349
+ complexity=$(echo "$analysis" | jq -r '.complexity')
350
+ file_scope=$(echo "$analysis" | jq -r '.file_scope')
351
+
352
+ # Base stages
353
+ local stages_json
354
+
355
+ if [[ "$complexity" -le 3 && "$file_scope" == "single_module" ]]; then
356
+ # Fast track: minimal stages
357
+ stages_json=$(jq -n '[
358
+ {name: "intake", parallel_group: 1, agents: 1, timeout_minutes: 5, skip_if: false},
359
+ {name: "build", parallel_group: 2, agents: 1, timeout_minutes: 10, skip_if: false},
360
+ {name: "test", parallel_group: 3, agents: 1, timeout_minutes: 5, skip_if: false},
361
+ {name: "pr", parallel_group: 4, agents: 1, timeout_minutes: 5, skip_if: false}
362
+ ]')
363
+ elif [[ "$complexity" -le 5 ]]; then
364
+ # Standard track
365
+ stages_json=$(jq -n '[
366
+ {name: "intake", parallel_group: 1, agents: 1, timeout_minutes: 5, skip_if: false},
367
+ {name: "plan", parallel_group: 2, agents: 1, timeout_minutes: 10, skip_if: false},
368
+ {name: "build", parallel_group: 3, agents: 1, timeout_minutes: 15, skip_if: false},
369
+ {name: "test", parallel_group: 4, agents: 1, timeout_minutes: 10, skip_if: false},
370
+ {name: "review", parallel_group: 5, agents: 1, timeout_minutes: 10, skip_if: false},
371
+ {name: "pr", parallel_group: 6, agents: 1, timeout_minutes: 5, skip_if: false}
372
+ ]')
373
+ else
374
+ # Full track with parallelization
375
+ stages_json=$(jq -n '[
376
+ {name: "intake", parallel_group: 1, agents: 1, timeout_minutes: 5, skip_if: false},
377
+ {name: "plan", parallel_group: 2, agents: 1, timeout_minutes: 15, skip_if: false},
378
+ {name: "design", parallel_group: 3, agents: 1, timeout_minutes: 20, skip_if: false},
379
+ {name: "build", parallel_group: 4, agents: 2, timeout_minutes: 20, skip_if: false},
380
+ {name: "test", parallel_group: 5, agents: 1, timeout_minutes: 15, skip_if: false},
381
+ {name: "review", parallel_group: 6, agents: 1, timeout_minutes: 15, skip_if: false},
382
+ {name: "compound_quality", parallel_group: 6, agents: 1, timeout_minutes: 10, skip_if: false},
383
+ {name: "pr", parallel_group: 7, agents: 1, timeout_minutes: 5, skip_if: false}
384
+ ]')
385
+ fi
386
+
387
+ echo "$stages_json"
388
+ }
389
+
390
+ # ─── cmd_analyze <issue_num> ────────────────────────────────────────────────
391
+ cmd_analyze() {
392
+ local issue_num="${1:-}"
393
+ if [[ -z "$issue_num" ]]; then
394
+ error "Usage: shipwright pm analyze <issue-num>"
395
+ return 1
396
+ fi
397
+
398
+ info "Analyzing issue #${issue_num}..."
399
+ local analysis
400
+ analysis=$(analyze_issue "$issue_num")
401
+
402
+ # Output as formatted JSON
403
+ echo "$analysis" | jq '.'
404
+ emit_event "pm.analyze" "issue=${issue_num}"
405
+ }
406
+
407
+ # ─── cmd_team <issue_num> ───────────────────────────────────────────────────
408
+ cmd_team() {
409
+ local issue_num="${1:-}"
410
+ if [[ -z "$issue_num" ]]; then
411
+ error "Usage: shipwright pm team <issue-num>"
412
+ return 1
413
+ fi
414
+
415
+ info "Analyzing issue #${issue_num} for team composition..."
416
+ local analysis
417
+ analysis=$(analyze_issue "$issue_num")
418
+
419
+ local team_rec
420
+ team_rec=$(recommend_team "$analysis")
421
+
422
+ echo "$team_rec" | jq '.'
423
+ emit_event "pm.team" "issue=${issue_num}"
424
+ }
425
+
426
+ # ─── cmd_orchestrate <issue_num> ────────────────────────────────────────────
427
+ cmd_orchestrate() {
428
+ local issue_num="${1:-}"
429
+ if [[ -z "$issue_num" ]]; then
430
+ error "Usage: shipwright pm orchestrate <issue-num>"
431
+ return 1
432
+ fi
433
+
434
+ info "Planning stage orchestration for issue #${issue_num}..."
435
+ local analysis
436
+ analysis=$(analyze_issue "$issue_num")
437
+
438
+ local stages
439
+ stages=$(orchestrate_stages "$analysis")
440
+
441
+ echo "$stages" | jq '.'
442
+ emit_event "pm.orchestrate" "issue=${issue_num}"
443
+ }
444
+
445
+ # ─── cmd_recommend <issue_num> ──────────────────────────────────────────────
446
+ cmd_recommend() {
447
+ local issue_num="${1:-}"
448
+ if [[ -z "$issue_num" ]]; then
449
+ error "Usage: shipwright pm recommend <issue-num>"
450
+ return 1
451
+ fi
452
+
453
+ info "Generating full PM recommendation for issue #${issue_num}..."
454
+ local analysis team_rec stages
455
+
456
+ analysis=$(analyze_issue "$issue_num")
457
+ team_rec=$(recommend_team "$analysis")
458
+ stages=$(orchestrate_stages "$analysis")
459
+
460
+ # Combine into comprehensive recommendation
461
+ local recommendation
462
+ recommendation=$(jq -n \
463
+ --argjson analysis "$analysis" \
464
+ --argjson team "$team_rec" \
465
+ --argjson stages "$stages" \
466
+ '{
467
+ issue: $analysis.issue,
468
+ title: $analysis.title,
469
+ analysis: $analysis,
470
+ team_composition: $team,
471
+ stage_orchestration: $stages,
472
+ recommendation_timestamp: "'$(now_iso)'"
473
+ }')
474
+
475
+ # Pretty-print
476
+ echo ""
477
+ echo -e "${BOLD}PM RECOMMENDATION FOR ISSUE #${issue_num}${RESET}"
478
+ echo -e "${DIM}$(echo "$analysis" | jq -r '.title')${RESET}"
479
+ echo ""
480
+ echo -e "${CYAN}${BOLD}ANALYSIS${RESET}"
481
+ echo "$analysis" | jq -r '" File Scope: \(.file_scope)\n Complexity: \(.complexity)/10\n Risk Level: \(.risk)\n Estimated Effort: \(.estimated_effort_hours)h\n Files Affected: ~\(.estimated_files_affected)"'
482
+ echo ""
483
+ echo -e "${CYAN}${BOLD}TEAM COMPOSITION${RESET}"
484
+ echo "$team_rec" | jq -r '" Roles: \(.roles | join(", "))\n Team Size: \(.estimated_agents) agents\n Pipeline Template: \(.template)\n Model: \(.model)\n Max Iterations: \(.max_iterations)\n Confidence: \(.confidence_percent)%"'
485
+ echo ""
486
+ echo -e "${CYAN}${BOLD}RISK ASSESSMENT${RESET}"
487
+ echo "$team_rec" | jq -r '" Factors: \(.risk_factors)\n Mitigations: \(.mitigation_strategies)"'
488
+ echo ""
489
+ echo -e "${CYAN}${BOLD}STAGE PLAN${RESET}"
490
+ echo "$stages" | jq -r '.[] | " \(.parallel_group). \(.name) (group \(.parallel_group), \(.agents) agent\(if .agents > 1 then "s" else "" end), \(.timeout_minutes)m)"'
491
+ echo ""
492
+
493
+ # Save to history
494
+ ensure_pm_history
495
+ local tmp_hist
496
+ tmp_hist=$(mktemp)
497
+ jq --argjson rec "$recommendation" '.decisions += [$rec]' "$PM_HISTORY" > "$tmp_hist" && mv "$tmp_hist" "$PM_HISTORY"
498
+
499
+ success "Recommendation saved to history"
500
+ emit_event "pm.recommend" "issue=${issue_num}"
501
+ }
502
+
503
+ # ─── cmd_learn <issue_num> <outcome> ────────────────────────────────────────
504
+ cmd_learn() {
505
+ local issue_num="${1:-}"
506
+ local outcome="${2:-}"
507
+
508
+ if [[ -z "$issue_num" || -z "$outcome" ]]; then
509
+ error "Usage: shipwright pm learn <issue-num> <outcome>"
510
+ echo " outcome: success or failure"
511
+ return 1
512
+ fi
513
+
514
+ if [[ "$outcome" != "success" && "$outcome" != "failure" ]]; then
515
+ error "Outcome must be 'success' or 'failure'"
516
+ return 1
517
+ fi
518
+
519
+ ensure_pm_history
520
+
521
+ # Find the recommendation in history
522
+ local recommendation
523
+ recommendation=$(jq -c --arg issue "$issue_num" '.decisions[] | select(.issue == $issue)' "$PM_HISTORY" 2>/dev/null | tail -1)
524
+
525
+ if [[ -z "$recommendation" ]]; then
526
+ warn "No previous recommendation found for issue #${issue_num}"
527
+ recommendation='null'
528
+ fi
529
+
530
+ # Record the outcome
531
+ local outcome_record
532
+ if [[ "$recommendation" == "null" ]]; then
533
+ outcome_record=$(jq -n \
534
+ --arg issue "$issue_num" \
535
+ --arg outcome "$outcome" \
536
+ --arg timestamp "$(now_iso)" \
537
+ '{
538
+ issue: $issue,
539
+ outcome: $outcome,
540
+ recorded_at: $timestamp,
541
+ recommendation: null
542
+ }')
543
+ else
544
+ outcome_record=$(jq -n \
545
+ --arg issue "$issue_num" \
546
+ --arg outcome "$outcome" \
547
+ --arg timestamp "$(now_iso)" \
548
+ --argjson recommendation "$recommendation" \
549
+ '{
550
+ issue: $issue,
551
+ outcome: $outcome,
552
+ recorded_at: $timestamp,
553
+ recommendation: $recommendation
554
+ }')
555
+ fi
556
+
557
+ # Save to history
558
+ local tmp_hist
559
+ tmp_hist=$(mktemp)
560
+ jq --argjson outcome "$outcome_record" '.outcomes += [$outcome]' "$PM_HISTORY" > "$tmp_hist" && mv "$tmp_hist" "$PM_HISTORY"
561
+
562
+ success "Recorded ${outcome} outcome for issue #${issue_num}"
563
+ emit_event "pm.learn" "issue=${issue_num}" "outcome=${outcome}"
564
+ }
565
+
566
+ # ─── cmd_history [--json|--pattern] ─────────────────────────────────────────
567
+ cmd_history() {
568
+ local format="${1:-table}"
569
+
570
+ ensure_pm_history
571
+
572
+ case "$format" in
573
+ --json)
574
+ jq '.' "$PM_HISTORY"
575
+ ;;
576
+ --pattern)
577
+ # Show success patterns
578
+ info "Success patterns from past recommendations:"
579
+ echo ""
580
+
581
+ local total_decisions success_count fail_count
582
+ total_decisions=$(jq '.outcomes | length' "$PM_HISTORY")
583
+ success_count=$(jq '[.outcomes[] | select(.outcome == "success")] | length' "$PM_HISTORY")
584
+ fail_count=$(jq '[.outcomes[] | select(.outcome == "failure")] | length' "$PM_HISTORY")
585
+
586
+ if [[ "$total_decisions" -gt 0 ]]; then
587
+ local success_rate
588
+ success_rate=$((success_count * 100 / total_decisions))
589
+ echo -e "${CYAN}Overall Success Rate: ${BOLD}${success_rate}%${RESET} (${success_count}/${total_decisions})"
590
+ echo ""
591
+
592
+ # Group by template
593
+ echo -e "${CYAN}${BOLD}Success Rate by Pipeline Template${RESET}"
594
+ jq -r '
595
+ [.outcomes[] | select(.outcome == "success")] as $successes |
596
+ [.decisions[]] as $decisions |
597
+ [
598
+ ("fast", "standard", "full", "hotfix", "enterprise") as $template |
599
+ {
600
+ template: $template,
601
+ total: ([.decisions[] | select(.team_composition.template == $template)] | length),
602
+ success: ([$successes[] | select(.recommendation.team_composition.template == $template)] | length)
603
+ } |
604
+ select(.total > 0) |
605
+ .success_rate = (if .total > 0 then (.success * 100 / .total) else 0 end)
606
+ ] |
607
+ sort_by(-.success_rate)
608
+ ' "$PM_HISTORY" | jq -r '.[] | " \(.template): \(.success)/\(.total) (\(.success_rate | round)%)"'
609
+ echo ""
610
+
611
+ # Group by team size
612
+ echo -e "${CYAN}${BOLD}Success Rate by Team Size${RESET}"
613
+ jq -r '
614
+ [.outcomes[] | select(.outcome == "success")] as $successes |
615
+ [
616
+ (1, 2, 3, 4, 5) as $size |
617
+ {
618
+ size: $size,
619
+ total: ([.decisions[] | select(.team_composition.estimated_agents == $size)] | length),
620
+ success: ([$successes[] | select(.recommendation.team_composition.estimated_agents == $size)] | length)
621
+ } |
622
+ select(.total > 0) |
623
+ .success_rate = (if .total > 0 then (.success * 100 / .total) else 0 end)
624
+ ] |
625
+ sort_by(-.success_rate)
626
+ ' "$PM_HISTORY" | jq -r '.[] | " \(.size) agents: \(.success)/\(.total) (\(.success_rate | round)%)"'
627
+ else
628
+ warn "No history recorded yet"
629
+ fi
630
+ ;;
631
+ *)
632
+ # Show as pretty table
633
+ if jq -e '.decisions | length > 0' "$PM_HISTORY" >/dev/null 2>&1; then
634
+ echo -e "${CYAN}${BOLD}Past PM Recommendations${RESET}"
635
+ echo ""
636
+ jq -r '
637
+ .decisions[] |
638
+ "Issue #\(.issue): \(.title) (complexity: \(.analysis.complexity)/10, team: \(.team_composition.estimated_agents) agents, template: \(.team_composition.template))"
639
+ ' "$PM_HISTORY"
640
+ echo ""
641
+ else
642
+ info "No recommendations in history yet"
643
+ fi
644
+
645
+ if jq -e '.outcomes | length > 0' "$PM_HISTORY" >/dev/null 2>&1; then
646
+ echo -e "${CYAN}${BOLD}Recorded Outcomes${RESET}"
647
+ echo ""
648
+ jq -r '.outcomes[] | "Issue #\(.issue): \(.outcome) (recorded: \(.recorded_at))"' "$PM_HISTORY"
649
+ fi
650
+ ;;
651
+ esac
652
+ }
653
+
654
+ # ─── Main command router ────────────────────────────────────────────────────
655
+ main() {
656
+ local cmd="${1:-help}"
657
+ shift 2>/dev/null || true
658
+
659
+ case "$cmd" in
660
+ analyze)
661
+ cmd_analyze "$@"
662
+ ;;
663
+ team)
664
+ cmd_team "$@"
665
+ ;;
666
+ orchestrate)
667
+ cmd_orchestrate "$@"
668
+ ;;
669
+ recommend)
670
+ cmd_recommend "$@"
671
+ ;;
672
+ learn)
673
+ cmd_learn "$@"
674
+ ;;
675
+ history)
676
+ cmd_history "$@"
677
+ ;;
678
+ help|--help|-h)
679
+ show_help
680
+ ;;
681
+ *)
682
+ error "Unknown command: ${cmd}"
683
+ echo ""
684
+ show_help
685
+ exit 1
686
+ ;;
687
+ esac
688
+ }
689
+
690
+ # ─── Source guard ───────────────────────────────────────────────────────────
691
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
692
+ main "$@"
693
+ fi