shipwright-cli 1.10.0 → 2.1.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 (121) hide show
  1. package/README.md +221 -55
  2. package/completions/_shipwright +264 -32
  3. package/completions/shipwright.bash +118 -26
  4. package/completions/shipwright.fish +80 -2
  5. package/dashboard/server.ts +208 -0
  6. package/docs/strategy/01-market-research.md +619 -0
  7. package/docs/strategy/02-mission-and-brand.md +587 -0
  8. package/docs/strategy/03-gtm-and-roadmap.md +759 -0
  9. package/docs/strategy/QUICK-START.txt +289 -0
  10. package/docs/strategy/README.md +172 -0
  11. package/docs/tmux-research/TMUX-ARCHITECTURE.md +567 -0
  12. package/docs/tmux-research/TMUX-AUDIT.md +925 -0
  13. package/docs/tmux-research/TMUX-BEST-PRACTICES-2025-2026.md +829 -0
  14. package/docs/tmux-research/TMUX-QUICK-REFERENCE.md +543 -0
  15. package/docs/tmux-research/TMUX-RESEARCH-INDEX.md +438 -0
  16. package/package.json +4 -2
  17. package/scripts/lib/helpers.sh +7 -0
  18. package/scripts/sw +323 -2
  19. package/scripts/sw-activity.sh +500 -0
  20. package/scripts/sw-adaptive.sh +925 -0
  21. package/scripts/sw-adversarial.sh +1 -1
  22. package/scripts/sw-architecture-enforcer.sh +1 -1
  23. package/scripts/sw-auth.sh +613 -0
  24. package/scripts/sw-autonomous.sh +754 -0
  25. package/scripts/sw-changelog.sh +704 -0
  26. package/scripts/sw-checkpoint.sh +1 -1
  27. package/scripts/sw-ci.sh +602 -0
  28. package/scripts/sw-cleanup.sh +1 -1
  29. package/scripts/sw-code-review.sh +698 -0
  30. package/scripts/sw-connect.sh +1 -1
  31. package/scripts/sw-context.sh +605 -0
  32. package/scripts/sw-cost.sh +44 -3
  33. package/scripts/sw-daemon.sh +568 -138
  34. package/scripts/sw-dashboard.sh +1 -1
  35. package/scripts/sw-db.sh +1380 -0
  36. package/scripts/sw-decompose.sh +539 -0
  37. package/scripts/sw-deps.sh +551 -0
  38. package/scripts/sw-developer-simulation.sh +1 -1
  39. package/scripts/sw-discovery.sh +412 -0
  40. package/scripts/sw-docs-agent.sh +539 -0
  41. package/scripts/sw-docs.sh +1 -1
  42. package/scripts/sw-doctor.sh +107 -1
  43. package/scripts/sw-dora.sh +615 -0
  44. package/scripts/sw-durable.sh +710 -0
  45. package/scripts/sw-e2e-orchestrator.sh +535 -0
  46. package/scripts/sw-eventbus.sh +393 -0
  47. package/scripts/sw-feedback.sh +479 -0
  48. package/scripts/sw-fix.sh +1 -1
  49. package/scripts/sw-fleet-discover.sh +567 -0
  50. package/scripts/sw-fleet-viz.sh +404 -0
  51. package/scripts/sw-fleet.sh +8 -1
  52. package/scripts/sw-github-app.sh +596 -0
  53. package/scripts/sw-github-checks.sh +4 -4
  54. package/scripts/sw-github-deploy.sh +1 -1
  55. package/scripts/sw-github-graphql.sh +1 -1
  56. package/scripts/sw-guild.sh +569 -0
  57. package/scripts/sw-heartbeat.sh +1 -1
  58. package/scripts/sw-hygiene.sh +559 -0
  59. package/scripts/sw-incident.sh +656 -0
  60. package/scripts/sw-init.sh +237 -24
  61. package/scripts/sw-instrument.sh +699 -0
  62. package/scripts/sw-intelligence.sh +1 -1
  63. package/scripts/sw-jira.sh +1 -1
  64. package/scripts/sw-launchd.sh +363 -28
  65. package/scripts/sw-linear.sh +1 -1
  66. package/scripts/sw-logs.sh +1 -1
  67. package/scripts/sw-loop.sh +267 -21
  68. package/scripts/sw-memory.sh +18 -1
  69. package/scripts/sw-mission-control.sh +487 -0
  70. package/scripts/sw-model-router.sh +545 -0
  71. package/scripts/sw-otel.sh +596 -0
  72. package/scripts/sw-oversight.sh +764 -0
  73. package/scripts/sw-pipeline-composer.sh +1 -1
  74. package/scripts/sw-pipeline-vitals.sh +1 -1
  75. package/scripts/sw-pipeline.sh +947 -35
  76. package/scripts/sw-pm.sh +758 -0
  77. package/scripts/sw-pr-lifecycle.sh +522 -0
  78. package/scripts/sw-predictive.sh +8 -1
  79. package/scripts/sw-prep.sh +1 -1
  80. package/scripts/sw-ps.sh +1 -1
  81. package/scripts/sw-public-dashboard.sh +798 -0
  82. package/scripts/sw-quality.sh +595 -0
  83. package/scripts/sw-reaper.sh +1 -1
  84. package/scripts/sw-recruit.sh +2248 -0
  85. package/scripts/sw-regression.sh +642 -0
  86. package/scripts/sw-release-manager.sh +736 -0
  87. package/scripts/sw-release.sh +706 -0
  88. package/scripts/sw-remote.sh +1 -1
  89. package/scripts/sw-replay.sh +520 -0
  90. package/scripts/sw-retro.sh +691 -0
  91. package/scripts/sw-scale.sh +444 -0
  92. package/scripts/sw-security-audit.sh +505 -0
  93. package/scripts/sw-self-optimize.sh +1 -1
  94. package/scripts/sw-session.sh +1 -1
  95. package/scripts/sw-setup.sh +263 -127
  96. package/scripts/sw-standup.sh +712 -0
  97. package/scripts/sw-status.sh +44 -2
  98. package/scripts/sw-strategic.sh +806 -0
  99. package/scripts/sw-stream.sh +450 -0
  100. package/scripts/sw-swarm.sh +620 -0
  101. package/scripts/sw-team-stages.sh +511 -0
  102. package/scripts/sw-templates.sh +4 -4
  103. package/scripts/sw-testgen.sh +566 -0
  104. package/scripts/sw-tmux-pipeline.sh +554 -0
  105. package/scripts/sw-tmux-role-color.sh +58 -0
  106. package/scripts/sw-tmux-status.sh +128 -0
  107. package/scripts/sw-tmux.sh +1 -1
  108. package/scripts/sw-trace.sh +485 -0
  109. package/scripts/sw-tracker-github.sh +188 -0
  110. package/scripts/sw-tracker-jira.sh +172 -0
  111. package/scripts/sw-tracker-linear.sh +251 -0
  112. package/scripts/sw-tracker.sh +117 -2
  113. package/scripts/sw-triage.sh +627 -0
  114. package/scripts/sw-upgrade.sh +1 -1
  115. package/scripts/sw-ux.sh +677 -0
  116. package/scripts/sw-webhook.sh +627 -0
  117. package/scripts/sw-widgets.sh +530 -0
  118. package/scripts/sw-worktree.sh +1 -1
  119. package/templates/pipelines/autonomous.json +2 -2
  120. package/tmux/shipwright-overlay.conf +35 -17
  121. package/tmux/tmux.conf +23 -21
@@ -0,0 +1,758 @@
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.1.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
+ # Tries recruit's AI/heuristic team composition first, falls back to hardcoded rules.
227
+ recommend_team() {
228
+ local analysis="$1"
229
+
230
+ # ── Try recruit-powered team composition first ──
231
+ if [[ -x "${SCRIPT_DIR:-}/sw-recruit.sh" ]]; then
232
+ local issue_title
233
+ issue_title=$(echo "$analysis" | jq -r '.title // .recommendation // ""' 2>/dev/null || true)
234
+ if [[ -n "$issue_title" ]]; then
235
+ local recruit_result
236
+ recruit_result=$(bash "$SCRIPT_DIR/sw-recruit.sh" team --json "$issue_title" 2>/dev/null) || true
237
+ if [[ -n "$recruit_result" ]] && echo "$recruit_result" | jq -e '.team' &>/dev/null 2>&1; then
238
+ local recruit_roles recruit_model recruit_agents recruit_cost
239
+ recruit_roles=$(echo "$recruit_result" | jq -r '.team | join(",")')
240
+ recruit_model=$(echo "$recruit_result" | jq -r '.model // "sonnet"')
241
+ recruit_agents=$(echo "$recruit_result" | jq -r '.agents // 2')
242
+ recruit_cost=$(echo "$recruit_result" | jq -r '.estimated_cost // 0')
243
+
244
+ # Map recruit roles/model to PM output format
245
+ local max_iterations=5
246
+ local template="standard"
247
+ if [[ "$recruit_agents" -ge 4 ]]; then template="full"; max_iterations=8;
248
+ elif [[ "$recruit_agents" -le 1 ]]; then template="fast"; max_iterations=3;
249
+ fi
250
+
251
+ local team_rec
252
+ team_rec=$(jq -n \
253
+ --arg roles "$recruit_roles" \
254
+ --arg template "$template" \
255
+ --arg model "$recruit_model" \
256
+ --arg max_iter "$max_iterations" \
257
+ --arg agents "$recruit_agents" \
258
+ --arg cost "$recruit_cost" \
259
+ '{
260
+ roles: ($roles | split(",")),
261
+ template: $template,
262
+ model: $model,
263
+ max_iterations: ($max_iter | tonumber),
264
+ estimated_agents: ($agents | tonumber),
265
+ confidence_percent: 80,
266
+ risk_factors: "recruit-powered recommendation",
267
+ mitigation_strategies: "AI-optimized team composition",
268
+ source: "recruit"
269
+ }')
270
+ echo "$team_rec"
271
+ return 0
272
+ fi
273
+ fi
274
+ fi
275
+
276
+ # ── Fallback: hardcoded heuristic team composition ──
277
+ local complexity risk is_security is_perf file_scope
278
+ complexity=$(echo "$analysis" | jq -r '.complexity')
279
+ risk=$(echo "$analysis" | jq -r '.risk')
280
+ is_security=$(echo "$analysis" | jq -r '.characteristics.is_security')
281
+ is_perf=$(echo "$analysis" | jq -r '.characteristics.is_performance_critical')
282
+ file_scope=$(echo "$analysis" | jq -r '.file_scope')
283
+
284
+ local roles template model max_iterations estimated_agents
285
+ local confidence risk_factors mitigations
286
+
287
+ # Determine base team and template
288
+ if [[ "$complexity" -le 3 && "$file_scope" == "single_module" ]]; then
289
+ # Simple bugfix
290
+ roles="builder"
291
+ template="fast"
292
+ estimated_agents=1
293
+ model="haiku"
294
+ max_iterations=3
295
+ confidence=85
296
+ risk_factors="Low complexity, single module"
297
+ mitigations="Standard code review"
298
+ elif [[ "$complexity" -le 5 && "$file_scope" == "multiple_modules" ]]; then
299
+ # Small feature
300
+ roles="builder,tester"
301
+ template="standard"
302
+ estimated_agents=2
303
+ model="sonnet"
304
+ max_iterations=5
305
+ confidence=80
306
+ risk_factors="Moderate complexity across modules"
307
+ mitigations="Build + test iteration cycles"
308
+ elif [[ "$complexity" -le 7 && "$file_scope" == "multiple_modules" ]]; then
309
+ # Medium feature
310
+ roles="builder,builder,tester"
311
+ template="standard"
312
+ estimated_agents=3
313
+ model="sonnet"
314
+ max_iterations=6
315
+ confidence=75
316
+ risk_factors="Moderate-high complexity, coordination needed"
317
+ mitigations="Parallel builders with test validation"
318
+ else
319
+ # Complex feature or cross-system change
320
+ roles="builder,builder,tester,reviewer"
321
+ template="full"
322
+ estimated_agents=4
323
+ model="opus"
324
+ max_iterations=8
325
+ confidence=70
326
+ risk_factors="High complexity, cross-system impact"
327
+ mitigations="Full pipeline with review gates"
328
+ fi
329
+
330
+ # Add security specialist if needed
331
+ if [[ "$is_security" == "true" ]]; then
332
+ roles="${roles},security"
333
+ estimated_agents=$((estimated_agents + 1))
334
+ confidence=$((confidence - 10))
335
+ risk_factors="${risk_factors}; security-sensitive changes"
336
+ mitigations="${mitigations}; security review before merge"
337
+ fi
338
+
339
+ # Adjust for risk level
340
+ case "$risk" in
341
+ critical)
342
+ template="enterprise"
343
+ max_iterations=$((max_iterations + 4))
344
+ confidence=$((confidence - 15))
345
+ risk_factors="${risk_factors}; critical risk"
346
+ mitigations="${mitigations}; emergency rollback plan"
347
+ ;;
348
+ high)
349
+ template="full"
350
+ max_iterations=$((max_iterations + 2))
351
+ confidence=$((confidence - 5))
352
+ ;;
353
+ esac
354
+
355
+ # Add performance specialist if needed
356
+ if [[ "$is_perf" == "true" ]]; then
357
+ roles="${roles},optimizer"
358
+ estimated_agents=$((estimated_agents + 1))
359
+ confidence=$((confidence - 5))
360
+ risk_factors="${risk_factors}; performance-critical"
361
+ mitigations="${mitigations}; performance benchmarking"
362
+ fi
363
+
364
+ # Cap confidence
365
+ confidence=$((confidence > 95 ? 95 : confidence < 50 ? 50 : confidence))
366
+
367
+ local team_rec
368
+ team_rec=$(jq -n \
369
+ --arg roles "$roles" \
370
+ --arg template "$template" \
371
+ --arg model "$model" \
372
+ --arg max_iter "$max_iterations" \
373
+ --arg agents "$estimated_agents" \
374
+ --arg confidence "$confidence" \
375
+ --arg risk_factors "$risk_factors" \
376
+ --arg mitigations "$mitigations" \
377
+ '{
378
+ roles: ($roles | split(",")),
379
+ template: $template,
380
+ model: $model,
381
+ max_iterations: ($max_iter | tonumber),
382
+ estimated_agents: ($agents | tonumber),
383
+ confidence_percent: ($confidence | tonumber),
384
+ risk_factors: $risk_factors,
385
+ mitigation_strategies: $mitigations
386
+ }')
387
+
388
+ echo "$team_rec"
389
+ }
390
+
391
+ # ─── orchestrate_stages <analysis_json> ──────────────────────────────────────
392
+ # Plan which stages to run and in what parallel groups
393
+ orchestrate_stages() {
394
+ local analysis="$1"
395
+
396
+ local complexity file_scope
397
+ complexity=$(echo "$analysis" | jq -r '.complexity')
398
+ file_scope=$(echo "$analysis" | jq -r '.file_scope')
399
+
400
+ # Base stages
401
+ local stages_json
402
+
403
+ if [[ "$complexity" -le 3 && "$file_scope" == "single_module" ]]; then
404
+ # Fast track: minimal stages
405
+ stages_json=$(jq -n '[
406
+ {name: "intake", parallel_group: 1, agents: 1, timeout_minutes: 5, skip_if: false},
407
+ {name: "build", parallel_group: 2, agents: 1, timeout_minutes: 10, skip_if: false},
408
+ {name: "test", parallel_group: 3, agents: 1, timeout_minutes: 5, skip_if: false},
409
+ {name: "pr", parallel_group: 4, agents: 1, timeout_minutes: 5, skip_if: false}
410
+ ]')
411
+ elif [[ "$complexity" -le 5 ]]; then
412
+ # Standard track
413
+ stages_json=$(jq -n '[
414
+ {name: "intake", parallel_group: 1, agents: 1, timeout_minutes: 5, skip_if: false},
415
+ {name: "plan", parallel_group: 2, agents: 1, timeout_minutes: 10, skip_if: false},
416
+ {name: "build", parallel_group: 3, agents: 1, timeout_minutes: 15, skip_if: false},
417
+ {name: "test", parallel_group: 4, agents: 1, timeout_minutes: 10, skip_if: false},
418
+ {name: "review", parallel_group: 5, agents: 1, timeout_minutes: 10, skip_if: false},
419
+ {name: "pr", parallel_group: 6, agents: 1, timeout_minutes: 5, skip_if: false}
420
+ ]')
421
+ else
422
+ # Full track with parallelization
423
+ stages_json=$(jq -n '[
424
+ {name: "intake", parallel_group: 1, agents: 1, timeout_minutes: 5, skip_if: false},
425
+ {name: "plan", parallel_group: 2, agents: 1, timeout_minutes: 15, skip_if: false},
426
+ {name: "design", parallel_group: 3, agents: 1, timeout_minutes: 20, skip_if: false},
427
+ {name: "build", parallel_group: 4, agents: 2, timeout_minutes: 20, skip_if: false},
428
+ {name: "test", parallel_group: 5, agents: 1, timeout_minutes: 15, skip_if: false},
429
+ {name: "review", parallel_group: 6, agents: 1, timeout_minutes: 15, skip_if: false},
430
+ {name: "compound_quality", parallel_group: 6, agents: 1, timeout_minutes: 10, skip_if: false},
431
+ {name: "pr", parallel_group: 7, agents: 1, timeout_minutes: 5, skip_if: false}
432
+ ]')
433
+ fi
434
+
435
+ echo "$stages_json"
436
+ }
437
+
438
+ # ─── cmd_analyze <issue_num> ────────────────────────────────────────────────
439
+ cmd_analyze() {
440
+ local issue_num="${1:-}"
441
+ if [[ -z "$issue_num" ]]; then
442
+ error "Usage: shipwright pm analyze <issue-num>"
443
+ return 1
444
+ fi
445
+
446
+ info "Analyzing issue #${issue_num}..."
447
+ local analysis
448
+ analysis=$(analyze_issue "$issue_num")
449
+
450
+ # Output as formatted JSON
451
+ echo "$analysis" | jq '.'
452
+ emit_event "pm.analyze" "issue=${issue_num}"
453
+ }
454
+
455
+ # ─── cmd_team <issue_num> ───────────────────────────────────────────────────
456
+ cmd_team() {
457
+ local issue_num="${1:-}"
458
+ if [[ -z "$issue_num" ]]; then
459
+ error "Usage: shipwright pm team <issue-num>"
460
+ return 1
461
+ fi
462
+
463
+ info "Analyzing issue #${issue_num} for team composition..."
464
+ local analysis
465
+ analysis=$(analyze_issue "$issue_num")
466
+
467
+ local team_rec
468
+ team_rec=$(recommend_team "$analysis")
469
+
470
+ echo "$team_rec" | jq '.'
471
+ emit_event "pm.team" "issue=${issue_num}"
472
+ }
473
+
474
+ # ─── cmd_orchestrate <issue_num> ────────────────────────────────────────────
475
+ cmd_orchestrate() {
476
+ local issue_num="${1:-}"
477
+ if [[ -z "$issue_num" ]]; then
478
+ error "Usage: shipwright pm orchestrate <issue-num>"
479
+ return 1
480
+ fi
481
+
482
+ info "Planning stage orchestration for issue #${issue_num}..."
483
+ local analysis
484
+ analysis=$(analyze_issue "$issue_num")
485
+
486
+ local stages
487
+ stages=$(orchestrate_stages "$analysis")
488
+
489
+ echo "$stages" | jq '.'
490
+ emit_event "pm.orchestrate" "issue=${issue_num}"
491
+ }
492
+
493
+ # ─── cmd_recommend <issue_num> [--json] ──────────────────────────────────────
494
+ cmd_recommend() {
495
+ local json_mode="false"
496
+ local issue_num=""
497
+ if [[ "${1:-}" == "--json" ]]; then
498
+ json_mode="true"
499
+ issue_num="${2:-}"
500
+ else
501
+ issue_num="${1:-}"
502
+ fi
503
+ if [[ -z "$issue_num" ]]; then
504
+ error "Usage: shipwright pm recommend <issue-num> [--json]"
505
+ return 1
506
+ fi
507
+
508
+ [[ "$json_mode" != "true" ]] && info "Generating full PM recommendation for issue #${issue_num}..."
509
+ local analysis team_rec stages
510
+
511
+ analysis=$(analyze_issue "$issue_num")
512
+ team_rec=$(recommend_team "$analysis")
513
+ stages=$(orchestrate_stages "$analysis")
514
+
515
+ # Combine into comprehensive recommendation
516
+ local recommendation
517
+ recommendation=$(jq -n \
518
+ --argjson analysis "$analysis" \
519
+ --argjson team "$team_rec" \
520
+ --argjson stages "$stages" \
521
+ '{
522
+ issue: $analysis.issue,
523
+ title: $analysis.title,
524
+ analysis: $analysis,
525
+ team_composition: $team,
526
+ stage_orchestration: $stages,
527
+ recommendation_timestamp: "'$(now_iso)'"
528
+ }')
529
+
530
+ if [[ "$json_mode" == "true" ]]; then
531
+ echo "$recommendation"
532
+ ensure_pm_history
533
+ local tmp_hist
534
+ tmp_hist=$(mktemp)
535
+ jq --argjson rec "$recommendation" '.decisions += [$rec]' "$PM_HISTORY" > "$tmp_hist" && mv "$tmp_hist" "$PM_HISTORY"
536
+ emit_event "pm.recommend" "issue=${issue_num}"
537
+ return 0
538
+ fi
539
+
540
+ # Pretty-print
541
+ echo ""
542
+ echo -e "${BOLD}PM RECOMMENDATION FOR ISSUE #${issue_num}${RESET}"
543
+ echo -e "${DIM}$(echo "$analysis" | jq -r '.title')${RESET}"
544
+ echo ""
545
+ echo -e "${CYAN}${BOLD}ANALYSIS${RESET}"
546
+ 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)"'
547
+ echo ""
548
+ echo -e "${CYAN}${BOLD}TEAM COMPOSITION${RESET}"
549
+ 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)%"'
550
+ echo ""
551
+ echo -e "${CYAN}${BOLD}RISK ASSESSMENT${RESET}"
552
+ echo "$team_rec" | jq -r '" Factors: \(.risk_factors)\n Mitigations: \(.mitigation_strategies)"'
553
+ echo ""
554
+ echo -e "${CYAN}${BOLD}STAGE PLAN${RESET}"
555
+ echo "$stages" | jq -r '.[] | " \(.parallel_group). \(.name) (group \(.parallel_group), \(.agents) agent\(if .agents > 1 then "s" else "" end), \(.timeout_minutes)m)"'
556
+ echo ""
557
+
558
+ # Save to history
559
+ ensure_pm_history
560
+ local tmp_hist
561
+ tmp_hist=$(mktemp)
562
+ jq --argjson rec "$recommendation" '.decisions += [$rec]' "$PM_HISTORY" > "$tmp_hist" && mv "$tmp_hist" "$PM_HISTORY"
563
+
564
+ success "Recommendation saved to history"
565
+ emit_event "pm.recommend" "issue=${issue_num}"
566
+ }
567
+
568
+ # ─── cmd_learn <issue_num> <outcome> ────────────────────────────────────────
569
+ cmd_learn() {
570
+ local issue_num="${1:-}"
571
+ local outcome="${2:-}"
572
+
573
+ if [[ -z "$issue_num" || -z "$outcome" ]]; then
574
+ error "Usage: shipwright pm learn <issue-num> <outcome>"
575
+ echo " outcome: success or failure"
576
+ return 1
577
+ fi
578
+
579
+ if [[ "$outcome" != "success" && "$outcome" != "failure" ]]; then
580
+ error "Outcome must be 'success' or 'failure'"
581
+ return 1
582
+ fi
583
+
584
+ ensure_pm_history
585
+
586
+ # Find the recommendation in history
587
+ local recommendation
588
+ recommendation=$(jq -c --arg issue "$issue_num" '.decisions[] | select(.issue == $issue)' "$PM_HISTORY" 2>/dev/null | tail -1)
589
+
590
+ if [[ -z "$recommendation" ]]; then
591
+ warn "No previous recommendation found for issue #${issue_num}"
592
+ recommendation='null'
593
+ fi
594
+
595
+ # Record the outcome
596
+ local outcome_record
597
+ if [[ "$recommendation" == "null" ]]; then
598
+ outcome_record=$(jq -n \
599
+ --arg issue "$issue_num" \
600
+ --arg outcome "$outcome" \
601
+ --arg timestamp "$(now_iso)" \
602
+ '{
603
+ issue: $issue,
604
+ outcome: $outcome,
605
+ recorded_at: $timestamp,
606
+ recommendation: null
607
+ }')
608
+ else
609
+ outcome_record=$(jq -n \
610
+ --arg issue "$issue_num" \
611
+ --arg outcome "$outcome" \
612
+ --arg timestamp "$(now_iso)" \
613
+ --argjson recommendation "$recommendation" \
614
+ '{
615
+ issue: $issue,
616
+ outcome: $outcome,
617
+ recorded_at: $timestamp,
618
+ recommendation: $recommendation
619
+ }')
620
+ fi
621
+
622
+ # Save to history
623
+ local tmp_hist
624
+ tmp_hist=$(mktemp)
625
+ jq --argjson outcome "$outcome_record" '.outcomes += [$outcome]' "$PM_HISTORY" > "$tmp_hist" && mv "$tmp_hist" "$PM_HISTORY"
626
+
627
+ success "Recorded ${outcome} outcome for issue #${issue_num}"
628
+ emit_event "pm.learn" "issue=${issue_num}" "outcome=${outcome}"
629
+ }
630
+
631
+ # ─── cmd_history [--json|--pattern] ─────────────────────────────────────────
632
+ cmd_history() {
633
+ local format="${1:-table}"
634
+
635
+ ensure_pm_history
636
+
637
+ case "$format" in
638
+ --json)
639
+ jq '.' "$PM_HISTORY"
640
+ ;;
641
+ --pattern)
642
+ # Show success patterns
643
+ info "Success patterns from past recommendations:"
644
+ echo ""
645
+
646
+ local total_decisions success_count fail_count
647
+ total_decisions=$(jq '.outcomes | length' "$PM_HISTORY")
648
+ success_count=$(jq '[.outcomes[] | select(.outcome == "success")] | length' "$PM_HISTORY")
649
+ fail_count=$(jq '[.outcomes[] | select(.outcome == "failure")] | length' "$PM_HISTORY")
650
+
651
+ if [[ "$total_decisions" -gt 0 ]]; then
652
+ local success_rate
653
+ success_rate=$((success_count * 100 / total_decisions))
654
+ echo -e "${CYAN}Overall Success Rate: ${BOLD}${success_rate}%${RESET} (${success_count}/${total_decisions})"
655
+ echo ""
656
+
657
+ # Group by template
658
+ echo -e "${CYAN}${BOLD}Success Rate by Pipeline Template${RESET}"
659
+ jq -r '
660
+ [.outcomes[] | select(.outcome == "success")] as $successes |
661
+ [.decisions[]] as $decisions |
662
+ [
663
+ ("fast", "standard", "full", "hotfix", "enterprise") as $template |
664
+ {
665
+ template: $template,
666
+ total: ([.decisions[] | select(.team_composition.template == $template)] | length),
667
+ success: ([$successes[] | select(.recommendation.team_composition.template == $template)] | length)
668
+ } |
669
+ select(.total > 0) |
670
+ .success_rate = (if .total > 0 then (.success * 100 / .total) else 0 end)
671
+ ] |
672
+ sort_by(-.success_rate)
673
+ ' "$PM_HISTORY" | jq -r '.[] | " \(.template): \(.success)/\(.total) (\(.success_rate | round)%)"'
674
+ echo ""
675
+
676
+ # Group by team size
677
+ echo -e "${CYAN}${BOLD}Success Rate by Team Size${RESET}"
678
+ jq -r '
679
+ [.outcomes[] | select(.outcome == "success")] as $successes |
680
+ [
681
+ (1, 2, 3, 4, 5) as $size |
682
+ {
683
+ size: $size,
684
+ total: ([.decisions[] | select(.team_composition.estimated_agents == $size)] | length),
685
+ success: ([$successes[] | select(.recommendation.team_composition.estimated_agents == $size)] | length)
686
+ } |
687
+ select(.total > 0) |
688
+ .success_rate = (if .total > 0 then (.success * 100 / .total) else 0 end)
689
+ ] |
690
+ sort_by(-.success_rate)
691
+ ' "$PM_HISTORY" | jq -r '.[] | " \(.size) agents: \(.success)/\(.total) (\(.success_rate | round)%)"'
692
+ else
693
+ warn "No history recorded yet"
694
+ fi
695
+ ;;
696
+ *)
697
+ # Show as pretty table
698
+ if jq -e '.decisions | length > 0' "$PM_HISTORY" >/dev/null 2>&1; then
699
+ echo -e "${CYAN}${BOLD}Past PM Recommendations${RESET}"
700
+ echo ""
701
+ jq -r '
702
+ .decisions[] |
703
+ "Issue #\(.issue): \(.title) (complexity: \(.analysis.complexity)/10, team: \(.team_composition.estimated_agents) agents, template: \(.team_composition.template))"
704
+ ' "$PM_HISTORY"
705
+ echo ""
706
+ else
707
+ info "No recommendations in history yet"
708
+ fi
709
+
710
+ if jq -e '.outcomes | length > 0' "$PM_HISTORY" >/dev/null 2>&1; then
711
+ echo -e "${CYAN}${BOLD}Recorded Outcomes${RESET}"
712
+ echo ""
713
+ jq -r '.outcomes[] | "Issue #\(.issue): \(.outcome) (recorded: \(.recorded_at))"' "$PM_HISTORY"
714
+ fi
715
+ ;;
716
+ esac
717
+ }
718
+
719
+ # ─── Main command router ────────────────────────────────────────────────────
720
+ main() {
721
+ local cmd="${1:-help}"
722
+ shift 2>/dev/null || true
723
+
724
+ case "$cmd" in
725
+ analyze)
726
+ cmd_analyze "$@"
727
+ ;;
728
+ team)
729
+ cmd_team "$@"
730
+ ;;
731
+ orchestrate)
732
+ cmd_orchestrate "$@"
733
+ ;;
734
+ recommend)
735
+ cmd_recommend "$@"
736
+ ;;
737
+ learn)
738
+ cmd_learn "$@"
739
+ ;;
740
+ history)
741
+ cmd_history "$@"
742
+ ;;
743
+ help|--help|-h)
744
+ show_help
745
+ ;;
746
+ *)
747
+ error "Unknown command: ${cmd}"
748
+ echo ""
749
+ show_help
750
+ exit 1
751
+ ;;
752
+ esac
753
+ }
754
+
755
+ # ─── Source guard ───────────────────────────────────────────────────────────
756
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
757
+ main "$@"
758
+ fi