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.
- package/README.md +221 -55
- package/completions/_shipwright +264 -32
- package/completions/shipwright.bash +118 -26
- package/completions/shipwright.fish +80 -2
- package/dashboard/server.ts +208 -0
- package/docs/strategy/01-market-research.md +619 -0
- package/docs/strategy/02-mission-and-brand.md +587 -0
- package/docs/strategy/03-gtm-and-roadmap.md +759 -0
- package/docs/strategy/QUICK-START.txt +289 -0
- package/docs/strategy/README.md +172 -0
- package/docs/tmux-research/TMUX-ARCHITECTURE.md +567 -0
- package/docs/tmux-research/TMUX-AUDIT.md +925 -0
- package/docs/tmux-research/TMUX-BEST-PRACTICES-2025-2026.md +829 -0
- package/docs/tmux-research/TMUX-QUICK-REFERENCE.md +543 -0
- package/docs/tmux-research/TMUX-RESEARCH-INDEX.md +438 -0
- package/package.json +4 -2
- package/scripts/lib/helpers.sh +7 -0
- package/scripts/sw +323 -2
- package/scripts/sw-activity.sh +500 -0
- package/scripts/sw-adaptive.sh +925 -0
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +613 -0
- package/scripts/sw-autonomous.sh +754 -0
- package/scripts/sw-changelog.sh +704 -0
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +602 -0
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +698 -0
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +605 -0
- package/scripts/sw-cost.sh +44 -3
- package/scripts/sw-daemon.sh +568 -138
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +1380 -0
- package/scripts/sw-decompose.sh +539 -0
- package/scripts/sw-deps.sh +551 -0
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +412 -0
- package/scripts/sw-docs-agent.sh +539 -0
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +107 -1
- package/scripts/sw-dora.sh +615 -0
- package/scripts/sw-durable.sh +710 -0
- package/scripts/sw-e2e-orchestrator.sh +535 -0
- package/scripts/sw-eventbus.sh +393 -0
- package/scripts/sw-feedback.sh +479 -0
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +567 -0
- package/scripts/sw-fleet-viz.sh +404 -0
- package/scripts/sw-fleet.sh +8 -1
- package/scripts/sw-github-app.sh +596 -0
- package/scripts/sw-github-checks.sh +4 -4
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +569 -0
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +559 -0
- package/scripts/sw-incident.sh +656 -0
- package/scripts/sw-init.sh +237 -24
- package/scripts/sw-instrument.sh +699 -0
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +363 -28
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +267 -21
- package/scripts/sw-memory.sh +18 -1
- package/scripts/sw-mission-control.sh +487 -0
- package/scripts/sw-model-router.sh +545 -0
- package/scripts/sw-otel.sh +596 -0
- package/scripts/sw-oversight.sh +764 -0
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +947 -35
- package/scripts/sw-pm.sh +758 -0
- package/scripts/sw-pr-lifecycle.sh +522 -0
- package/scripts/sw-predictive.sh +8 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +798 -0
- package/scripts/sw-quality.sh +595 -0
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +2248 -0
- package/scripts/sw-regression.sh +642 -0
- package/scripts/sw-release-manager.sh +736 -0
- package/scripts/sw-release.sh +706 -0
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +520 -0
- package/scripts/sw-retro.sh +691 -0
- package/scripts/sw-scale.sh +444 -0
- package/scripts/sw-security-audit.sh +505 -0
- package/scripts/sw-self-optimize.sh +1 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +263 -127
- package/scripts/sw-standup.sh +712 -0
- package/scripts/sw-status.sh +44 -2
- package/scripts/sw-strategic.sh +806 -0
- package/scripts/sw-stream.sh +450 -0
- package/scripts/sw-swarm.sh +620 -0
- package/scripts/sw-team-stages.sh +511 -0
- package/scripts/sw-templates.sh +4 -4
- package/scripts/sw-testgen.sh +566 -0
- package/scripts/sw-tmux-pipeline.sh +554 -0
- package/scripts/sw-tmux-role-color.sh +58 -0
- package/scripts/sw-tmux-status.sh +128 -0
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +485 -0
- package/scripts/sw-tracker-github.sh +188 -0
- package/scripts/sw-tracker-jira.sh +172 -0
- package/scripts/sw-tracker-linear.sh +251 -0
- package/scripts/sw-tracker.sh +117 -2
- package/scripts/sw-triage.sh +627 -0
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +677 -0
- package/scripts/sw-webhook.sh +627 -0
- package/scripts/sw-widgets.sh +530 -0
- package/scripts/sw-worktree.sh +1 -1
- package/templates/pipelines/autonomous.json +2 -2
- package/tmux/shipwright-overlay.conf +35 -17
- package/tmux/tmux.conf +23 -21
package/scripts/sw-pm.sh
ADDED
|
@@ -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
|