shipwright-cli 3.2.0 → 3.3.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 (279) hide show
  1. package/.claude/agents/code-reviewer.md +2 -0
  2. package/.claude/agents/devops-engineer.md +2 -0
  3. package/.claude/agents/doc-fleet-agent.md +2 -0
  4. package/.claude/agents/pipeline-agent.md +2 -0
  5. package/.claude/agents/shell-script-specialist.md +2 -0
  6. package/.claude/agents/test-specialist.md +2 -0
  7. package/.claude/hooks/agent-crash-capture.sh +32 -0
  8. package/.claude/hooks/post-tool-use.sh +3 -2
  9. package/.claude/hooks/pre-tool-use.sh +35 -3
  10. package/README.md +4 -4
  11. package/claude-code/hooks/config-change.sh +18 -0
  12. package/claude-code/hooks/instructions-reloaded.sh +7 -0
  13. package/claude-code/hooks/worktree-create.sh +25 -0
  14. package/claude-code/hooks/worktree-remove.sh +20 -0
  15. package/config/code-constitution.json +130 -0
  16. package/dashboard/middleware/auth.ts +134 -0
  17. package/dashboard/middleware/constants.ts +21 -0
  18. package/dashboard/public/index.html +2 -6
  19. package/dashboard/public/styles.css +100 -97
  20. package/dashboard/routes/auth.ts +38 -0
  21. package/dashboard/server.ts +66 -25
  22. package/dashboard/services/config.ts +26 -0
  23. package/dashboard/services/db.ts +118 -0
  24. package/dashboard/src/canvas/pixel-agent.ts +298 -0
  25. package/dashboard/src/canvas/pixel-sprites.ts +440 -0
  26. package/dashboard/src/canvas/shipyard-effects.ts +367 -0
  27. package/dashboard/src/canvas/shipyard-scene.ts +616 -0
  28. package/dashboard/src/canvas/submarine-layout.ts +267 -0
  29. package/dashboard/src/components/header.ts +8 -7
  30. package/dashboard/src/core/router.ts +1 -0
  31. package/dashboard/src/design/submarine-theme.ts +253 -0
  32. package/dashboard/src/main.ts +2 -0
  33. package/dashboard/src/types/api.ts +2 -1
  34. package/dashboard/src/views/activity.ts +2 -1
  35. package/dashboard/src/views/shipyard.ts +39 -0
  36. package/dashboard/types/index.ts +166 -0
  37. package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
  38. package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
  39. package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
  40. package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
  41. package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
  42. package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
  43. package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
  44. package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
  45. package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
  46. package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
  47. package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
  48. package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
  49. package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
  50. package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
  51. package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
  52. package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
  53. package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
  54. package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
  55. package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
  56. package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
  57. package/docs/research/RESEARCH_INDEX.md +439 -0
  58. package/docs/research/RESEARCH_SOURCES.md +440 -0
  59. package/docs/research/RESEARCH_SUMMARY.txt +275 -0
  60. package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
  61. package/package.json +2 -2
  62. package/scripts/lib/adaptive-model.sh +427 -0
  63. package/scripts/lib/adaptive-timeout.sh +316 -0
  64. package/scripts/lib/audit-trail.sh +309 -0
  65. package/scripts/lib/auto-recovery.sh +471 -0
  66. package/scripts/lib/bandit-selector.sh +431 -0
  67. package/scripts/lib/bootstrap.sh +104 -2
  68. package/scripts/lib/causal-graph.sh +455 -0
  69. package/scripts/lib/compat.sh +126 -0
  70. package/scripts/lib/compound-audit.sh +337 -0
  71. package/scripts/lib/constitutional.sh +454 -0
  72. package/scripts/lib/context-budget.sh +359 -0
  73. package/scripts/lib/convergence.sh +594 -0
  74. package/scripts/lib/cost-optimizer.sh +634 -0
  75. package/scripts/lib/daemon-adaptive.sh +10 -0
  76. package/scripts/lib/daemon-dispatch.sh +106 -17
  77. package/scripts/lib/daemon-failure.sh +34 -4
  78. package/scripts/lib/daemon-patrol.sh +23 -2
  79. package/scripts/lib/daemon-poll-github.sh +361 -0
  80. package/scripts/lib/daemon-poll-health.sh +299 -0
  81. package/scripts/lib/daemon-poll.sh +27 -611
  82. package/scripts/lib/daemon-state.sh +112 -66
  83. package/scripts/lib/daemon-triage.sh +10 -0
  84. package/scripts/lib/dod-scorecard.sh +442 -0
  85. package/scripts/lib/error-actionability.sh +300 -0
  86. package/scripts/lib/formal-spec.sh +461 -0
  87. package/scripts/lib/helpers.sh +177 -4
  88. package/scripts/lib/intent-analysis.sh +409 -0
  89. package/scripts/lib/loop-convergence.sh +350 -0
  90. package/scripts/lib/loop-iteration.sh +682 -0
  91. package/scripts/lib/loop-progress.sh +48 -0
  92. package/scripts/lib/loop-restart.sh +185 -0
  93. package/scripts/lib/memory-effectiveness.sh +506 -0
  94. package/scripts/lib/mutation-executor.sh +352 -0
  95. package/scripts/lib/outcome-feedback.sh +521 -0
  96. package/scripts/lib/pipeline-cli.sh +336 -0
  97. package/scripts/lib/pipeline-commands.sh +1216 -0
  98. package/scripts/lib/pipeline-detection.sh +100 -2
  99. package/scripts/lib/pipeline-execution.sh +897 -0
  100. package/scripts/lib/pipeline-github.sh +28 -3
  101. package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
  102. package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
  103. package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
  104. package/scripts/lib/pipeline-intelligence.sh +100 -1136
  105. package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
  106. package/scripts/lib/pipeline-quality-checks.sh +17 -715
  107. package/scripts/lib/pipeline-quality-gates.sh +563 -0
  108. package/scripts/lib/pipeline-stages-build.sh +730 -0
  109. package/scripts/lib/pipeline-stages-delivery.sh +965 -0
  110. package/scripts/lib/pipeline-stages-intake.sh +1133 -0
  111. package/scripts/lib/pipeline-stages-monitor.sh +407 -0
  112. package/scripts/lib/pipeline-stages-review.sh +1022 -0
  113. package/scripts/lib/pipeline-stages.sh +59 -2929
  114. package/scripts/lib/pipeline-state.sh +36 -5
  115. package/scripts/lib/pipeline-util.sh +487 -0
  116. package/scripts/lib/policy-learner.sh +438 -0
  117. package/scripts/lib/process-reward.sh +493 -0
  118. package/scripts/lib/project-detect.sh +649 -0
  119. package/scripts/lib/quality-profile.sh +334 -0
  120. package/scripts/lib/recruit-commands.sh +885 -0
  121. package/scripts/lib/recruit-learning.sh +739 -0
  122. package/scripts/lib/recruit-roles.sh +648 -0
  123. package/scripts/lib/reward-aggregator.sh +458 -0
  124. package/scripts/lib/rl-optimizer.sh +362 -0
  125. package/scripts/lib/root-cause.sh +427 -0
  126. package/scripts/lib/scope-enforcement.sh +445 -0
  127. package/scripts/lib/session-restart.sh +493 -0
  128. package/scripts/lib/skill-memory.sh +300 -0
  129. package/scripts/lib/skill-registry.sh +775 -0
  130. package/scripts/lib/spec-driven.sh +476 -0
  131. package/scripts/lib/test-helpers.sh +18 -7
  132. package/scripts/lib/test-holdout.sh +429 -0
  133. package/scripts/lib/test-optimizer.sh +511 -0
  134. package/scripts/shipwright-file-suggest.sh +45 -0
  135. package/scripts/skills/adversarial-quality.md +61 -0
  136. package/scripts/skills/api-design.md +44 -0
  137. package/scripts/skills/architecture-design.md +50 -0
  138. package/scripts/skills/brainstorming.md +43 -0
  139. package/scripts/skills/data-pipeline.md +44 -0
  140. package/scripts/skills/deploy-safety.md +64 -0
  141. package/scripts/skills/documentation.md +38 -0
  142. package/scripts/skills/frontend-design.md +45 -0
  143. package/scripts/skills/generated/.gitkeep +0 -0
  144. package/scripts/skills/generated/_refinements/.gitkeep +0 -0
  145. package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
  146. package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
  147. package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
  148. package/scripts/skills/generated/cli-version-management.md +29 -0
  149. package/scripts/skills/generated/collection-system-validation.md +99 -0
  150. package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
  151. package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
  152. package/scripts/skills/generated/test-parallelization-detection.md +65 -0
  153. package/scripts/skills/observability.md +79 -0
  154. package/scripts/skills/performance.md +48 -0
  155. package/scripts/skills/pr-quality.md +49 -0
  156. package/scripts/skills/product-thinking.md +43 -0
  157. package/scripts/skills/security-audit.md +49 -0
  158. package/scripts/skills/systematic-debugging.md +40 -0
  159. package/scripts/skills/testing-strategy.md +47 -0
  160. package/scripts/skills/two-stage-review.md +52 -0
  161. package/scripts/skills/validation-thoroughness.md +55 -0
  162. package/scripts/sw +9 -3
  163. package/scripts/sw-activity.sh +9 -2
  164. package/scripts/sw-adaptive.sh +2 -1
  165. package/scripts/sw-adversarial.sh +2 -1
  166. package/scripts/sw-architecture-enforcer.sh +3 -1
  167. package/scripts/sw-auth.sh +12 -2
  168. package/scripts/sw-autonomous.sh +5 -1
  169. package/scripts/sw-changelog.sh +4 -1
  170. package/scripts/sw-checkpoint.sh +2 -1
  171. package/scripts/sw-ci.sh +5 -1
  172. package/scripts/sw-cleanup.sh +4 -26
  173. package/scripts/sw-code-review.sh +10 -4
  174. package/scripts/sw-connect.sh +2 -1
  175. package/scripts/sw-context.sh +2 -1
  176. package/scripts/sw-cost.sh +48 -3
  177. package/scripts/sw-daemon.sh +66 -9
  178. package/scripts/sw-dashboard.sh +3 -1
  179. package/scripts/sw-db.sh +59 -16
  180. package/scripts/sw-decide.sh +8 -2
  181. package/scripts/sw-decompose.sh +360 -17
  182. package/scripts/sw-deps.sh +4 -1
  183. package/scripts/sw-developer-simulation.sh +4 -1
  184. package/scripts/sw-discovery.sh +325 -2
  185. package/scripts/sw-doc-fleet.sh +4 -1
  186. package/scripts/sw-docs-agent.sh +3 -1
  187. package/scripts/sw-docs.sh +2 -1
  188. package/scripts/sw-doctor.sh +453 -2
  189. package/scripts/sw-dora.sh +4 -1
  190. package/scripts/sw-durable.sh +4 -3
  191. package/scripts/sw-e2e-orchestrator.sh +17 -16
  192. package/scripts/sw-eventbus.sh +7 -1
  193. package/scripts/sw-evidence.sh +364 -12
  194. package/scripts/sw-feedback.sh +550 -9
  195. package/scripts/sw-fix.sh +20 -1
  196. package/scripts/sw-fleet-discover.sh +6 -2
  197. package/scripts/sw-fleet-viz.sh +4 -1
  198. package/scripts/sw-fleet.sh +5 -1
  199. package/scripts/sw-github-app.sh +16 -3
  200. package/scripts/sw-github-checks.sh +3 -2
  201. package/scripts/sw-github-deploy.sh +3 -2
  202. package/scripts/sw-github-graphql.sh +18 -7
  203. package/scripts/sw-guild.sh +5 -1
  204. package/scripts/sw-heartbeat.sh +5 -30
  205. package/scripts/sw-hello.sh +67 -0
  206. package/scripts/sw-hygiene.sh +6 -1
  207. package/scripts/sw-incident.sh +265 -1
  208. package/scripts/sw-init.sh +18 -2
  209. package/scripts/sw-instrument.sh +10 -2
  210. package/scripts/sw-intelligence.sh +42 -6
  211. package/scripts/sw-jira.sh +5 -1
  212. package/scripts/sw-launchd.sh +2 -1
  213. package/scripts/sw-linear.sh +4 -1
  214. package/scripts/sw-logs.sh +4 -1
  215. package/scripts/sw-loop.sh +432 -1128
  216. package/scripts/sw-memory.sh +356 -2
  217. package/scripts/sw-mission-control.sh +6 -1
  218. package/scripts/sw-model-router.sh +481 -26
  219. package/scripts/sw-otel.sh +13 -4
  220. package/scripts/sw-oversight.sh +14 -5
  221. package/scripts/sw-patrol-meta.sh +334 -0
  222. package/scripts/sw-pipeline-composer.sh +5 -1
  223. package/scripts/sw-pipeline-vitals.sh +2 -1
  224. package/scripts/sw-pipeline.sh +53 -2664
  225. package/scripts/sw-pm.sh +12 -5
  226. package/scripts/sw-pr-lifecycle.sh +2 -1
  227. package/scripts/sw-predictive.sh +7 -1
  228. package/scripts/sw-prep.sh +185 -2
  229. package/scripts/sw-ps.sh +5 -25
  230. package/scripts/sw-public-dashboard.sh +15 -3
  231. package/scripts/sw-quality.sh +2 -1
  232. package/scripts/sw-reaper.sh +8 -25
  233. package/scripts/sw-recruit.sh +156 -2303
  234. package/scripts/sw-regression.sh +19 -12
  235. package/scripts/sw-release-manager.sh +3 -1
  236. package/scripts/sw-release.sh +4 -1
  237. package/scripts/sw-remote.sh +3 -1
  238. package/scripts/sw-replay.sh +7 -1
  239. package/scripts/sw-retro.sh +158 -1
  240. package/scripts/sw-review-rerun.sh +3 -1
  241. package/scripts/sw-scale.sh +10 -3
  242. package/scripts/sw-security-audit.sh +6 -1
  243. package/scripts/sw-self-optimize.sh +6 -3
  244. package/scripts/sw-session.sh +9 -3
  245. package/scripts/sw-setup.sh +3 -1
  246. package/scripts/sw-stall-detector.sh +406 -0
  247. package/scripts/sw-standup.sh +15 -7
  248. package/scripts/sw-status.sh +3 -1
  249. package/scripts/sw-strategic.sh +4 -1
  250. package/scripts/sw-stream.sh +7 -1
  251. package/scripts/sw-swarm.sh +18 -6
  252. package/scripts/sw-team-stages.sh +13 -6
  253. package/scripts/sw-templates.sh +5 -29
  254. package/scripts/sw-testgen.sh +7 -1
  255. package/scripts/sw-tmux-pipeline.sh +4 -1
  256. package/scripts/sw-tmux-role-color.sh +2 -0
  257. package/scripts/sw-tmux-status.sh +1 -1
  258. package/scripts/sw-tmux.sh +3 -1
  259. package/scripts/sw-trace.sh +3 -1
  260. package/scripts/sw-tracker-github.sh +3 -0
  261. package/scripts/sw-tracker-jira.sh +3 -0
  262. package/scripts/sw-tracker-linear.sh +3 -0
  263. package/scripts/sw-tracker.sh +3 -1
  264. package/scripts/sw-triage.sh +2 -1
  265. package/scripts/sw-upgrade.sh +3 -1
  266. package/scripts/sw-ux.sh +5 -2
  267. package/scripts/sw-webhook.sh +3 -1
  268. package/scripts/sw-widgets.sh +3 -1
  269. package/scripts/sw-worktree.sh +15 -3
  270. package/scripts/test-skill-injection.sh +1233 -0
  271. package/templates/pipelines/autonomous.json +27 -3
  272. package/templates/pipelines/cost-aware.json +34 -8
  273. package/templates/pipelines/deployed.json +12 -0
  274. package/templates/pipelines/enterprise.json +12 -0
  275. package/templates/pipelines/fast.json +6 -0
  276. package/templates/pipelines/full.json +27 -3
  277. package/templates/pipelines/hotfix.json +6 -0
  278. package/templates/pipelines/standard.json +12 -0
  279. package/templates/pipelines/tdd.json +12 -0
@@ -0,0 +1,885 @@
1
+ #!/usr/bin/env bash
2
+ # shellcheck disable=SC2034,SC2064
3
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
4
+ # ║ lib/recruit-commands.sh — High-Level Commands (Team, Mind, Decompose) ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+
7
+ [[ -n "${_RECRUIT_COMMANDS_LOADED:-}" ]] && return 0
8
+ _RECRUIT_COMMANDS_LOADED=1
9
+
10
+ SCRIPT_DIR="${SCRIPT_DIR:-.}"
11
+ RECRUIT_ROOT="${RECRUIT_ROOT:-${HOME}/.shipwright/recruitment}"
12
+ PROFILES_DB="${PROFILES_DB:-${RECRUIT_ROOT}/profiles.json}"
13
+ ROLES_DB="${ROLES_DB:-${RECRUIT_ROOT}/roles.json}"
14
+ AGENT_MINDS_DB="${AGENT_MINDS_DB:-${RECRUIT_ROOT}/agent-minds.json}"
15
+ ONBOARDING_DB="${ONBOARDING_DB:-${RECRUIT_ROOT}/onboarding.json}"
16
+
17
+ # Fallback color/output helpers
18
+ [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
19
+ [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
20
+ [[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
21
+ [[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
22
+
23
+ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
24
+ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
25
+ now_epoch() { date +%s; }
26
+ fi
27
+
28
+ # Fallback for color codes
29
+ CYAN="${CYAN:-\033[38;2;0;212;255m}"
30
+ RESET="${RESET:-\033[0m}"
31
+ BOLD="${BOLD:-\033[1m}"
32
+ DIM="${DIM:-\033[2m}"
33
+ PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
34
+
35
+ # Smart routing: given a task, find the best available agent
36
+ cmd_route() {
37
+ local task_description="${1:-}"
38
+
39
+ if [[ -z "$task_description" ]]; then
40
+ error "Usage: shipwright recruit route \"<task description>\""
41
+ exit 1
42
+ fi
43
+
44
+ ensure_recruit_dir
45
+ initialize_builtin_roles
46
+
47
+ info "Smart routing for: ${CYAN}${task_description}${RESET}"
48
+ echo ""
49
+
50
+ # Step 1: Determine best role
51
+ local role_match
52
+ role_match=$(_recruit_keyword_match "$task_description")
53
+ local primary_role
54
+ primary_role=$(echo "$role_match" | awk '{print $1}')
55
+
56
+ # Step 2: Find best agent for that role
57
+ if [[ -f "$PROFILES_DB" && "$(jq 'length' "$PROFILES_DB" 2>/dev/null || echo 0)" -gt 0 ]]; then
58
+ local best_agent
59
+ best_agent=$(jq -r --arg role "$primary_role" '
60
+ to_entries |
61
+ map(select(.value.role == $role and (.value.tasks_completed // 0) >= 3)) |
62
+ sort_by(-(.value.success_rate // 0)) |
63
+ .[0] // null |
64
+ if . then "\(.key) (\(.value.success_rate)% success over \(.value.tasks_completed) tasks)"
65
+ else null end
66
+ ' "$PROFILES_DB" 2>/dev/null || echo "")
67
+
68
+ if [[ -n "$best_agent" && "$best_agent" != "null" ]]; then
69
+ success "Best agent: ${CYAN}${best_agent}${RESET}"
70
+ else
71
+ info "No experienced agent for ${primary_role} role — assign any available agent"
72
+ fi
73
+ fi
74
+
75
+ # Step 3: Get recommended model
76
+ local recommended_model
77
+ recommended_model=$(jq -r --arg role "$primary_role" '.[$role].recommended_model // "sonnet"' "$ROLES_DB" 2>/dev/null || echo "sonnet")
78
+
79
+ echo " Role: ${primary_role}"
80
+ echo " Model: ${recommended_model}"
81
+ }
82
+
83
+ # ═══════════════════════════════════════════════════════════════════════════════
84
+ # CONTEXT-AWARE TEAM COMPOSITION (Tier 2)
85
+ # ═══════════════════════════════════════════════════════════════════════════════
86
+
87
+ cmd_team() {
88
+ local json_mode=false
89
+ if [[ "${1:-}" == "--json" ]]; then
90
+ json_mode=true
91
+ shift
92
+ fi
93
+ local issue_or_project="${1:-}"
94
+
95
+ if [[ -z "$issue_or_project" ]]; then
96
+ error "Usage: shipwright recruit team [--json] <issue|project>"
97
+ exit 1
98
+ fi
99
+
100
+ ensure_recruit_dir
101
+ initialize_builtin_roles
102
+
103
+ if ! $json_mode; then
104
+ info "Recommending team composition for: ${CYAN}${issue_or_project}${RESET}"
105
+ echo ""
106
+ fi
107
+
108
+ local recommended_team=()
109
+ local team_method="heuristic"
110
+
111
+ # Try LLM-powered team composition first
112
+ if _recruit_has_claude; then
113
+ local available_roles
114
+ available_roles=$(jq -r 'to_entries | map({key: .key, title: .value.title, cost: .value.estimated_cost_per_task_usd}) | tojson' "$ROLES_DB" 2>/dev/null || echo "[]")
115
+
116
+ # Gather codebase context if in a git repo
117
+ local codebase_context=""
118
+ if command -v git >/dev/null 2>&1 && git rev-parse --git-dir >/dev/null 2>&1; then
119
+ local file_count lang_summary
120
+ file_count=$(git ls-files 2>/dev/null | wc -l | tr -d ' ')
121
+ lang_summary=$(git ls-files 2>/dev/null | grep -oE '\.[^.]+$' | sort | uniq -c | sort -rn | head -5 | tr '\n' ';' || echo "unknown")
122
+ codebase_context="Files: ${file_count}, Languages: ${lang_summary}"
123
+ fi
124
+
125
+ local prompt
126
+ prompt="You are a team composition optimizer. Given a task and available roles, recommend the optimal team.
127
+
128
+ Task/Issue: ${issue_or_project}
129
+ Codebase context: ${codebase_context:-unknown}
130
+ Available roles: ${available_roles}
131
+
132
+ Consider:
133
+ - Task complexity (simple tasks need fewer roles)
134
+ - Risk areas (security-sensitive = add security-auditor)
135
+ - Cost efficiency (minimize cost while covering all needs)
136
+
137
+ Return ONLY a JSON object:
138
+ {\"team\": [\"<role_key>\", ...], \"reasoning\": \"<brief explanation>\", \"estimated_cost\": <total_usd>, \"risk_level\": \"low|medium|high\"}
139
+
140
+ Return JSON only."
141
+
142
+ local result
143
+ result=$(_recruit_call_claude "$prompt")
144
+
145
+ if [[ -n "$result" ]] && echo "$result" | jq -e '.team' >/dev/null 2>&1; then
146
+ while IFS= read -r role; do
147
+ [[ -z "$role" || "$role" == "null" ]] && continue
148
+ recommended_team+=("$role")
149
+ done < <(echo "$result" | jq -r '.team[]' 2>/dev/null)
150
+
151
+ team_method="ai"
152
+ local reasoning
153
+ reasoning=$(echo "$result" | jq -r '.reasoning // ""')
154
+ local risk_level
155
+ risk_level=$(echo "$result" | jq -r '.risk_level // "medium"')
156
+
157
+ if [[ -n "$reasoning" ]]; then
158
+ echo -e " ${DIM}AI reasoning: ${reasoning}${RESET}"
159
+ echo -e " ${DIM}Risk level: ${risk_level}${RESET}"
160
+ echo ""
161
+ fi
162
+ fi
163
+ fi
164
+
165
+ # Fallback: heuristic team composition
166
+ if [[ ${#recommended_team[@]} -eq 0 ]]; then
167
+ recommended_team=("builder" "reviewer" "tester")
168
+
169
+ if echo "$issue_or_project" | grep -qiE "security|vulnerability|compliance"; then
170
+ recommended_team+=("security-auditor")
171
+ fi
172
+ if echo "$issue_or_project" | grep -qiE "architecture|design|refactor"; then
173
+ recommended_team+=("architect")
174
+ fi
175
+ if echo "$issue_or_project" | grep -qiE "deploy|infra|ci.cd|pipeline"; then
176
+ recommended_team+=("devops")
177
+ fi
178
+ if echo "$issue_or_project" | grep -qiE "performance|speed|latency|optimization"; then
179
+ recommended_team+=("optimizer")
180
+ fi
181
+ fi
182
+
183
+ # Compute total cost and model list
184
+ local total_cost
185
+ total_cost=$(printf "%.2f" "$(
186
+ for role in "${recommended_team[@]}"; do
187
+ jq ".\"${role}\".estimated_cost_per_task_usd // 1.5" "$ROLES_DB" 2>/dev/null || echo "1.5"
188
+ done | awk '{sum+=$1} END {print sum}'
189
+ )")
190
+
191
+ # Determine primary model (highest-tier model on the team)
192
+ local team_model="sonnet"
193
+ for role in "${recommended_team[@]}"; do
194
+ local rm
195
+ rm=$(jq -r ".\"${role}\".recommended_model // \"sonnet\"" "$ROLES_DB" 2>/dev/null || echo "sonnet")
196
+ if [[ "$rm" == "opus" ]]; then team_model="opus"; break; fi
197
+ done
198
+
199
+ emit_event "recruit_team" "size=${#recommended_team[@]}" "method=${team_method}" "cost=${total_cost}"
200
+
201
+ # JSON mode: structured output for programmatic consumption
202
+ if $json_mode; then
203
+ local roles_json
204
+ roles_json=$(printf '%s\n' "${recommended_team[@]}" | jq -R . | jq -s .)
205
+
206
+ # Derive template and max_iterations from team size/composition (triage needs these)
207
+ local team_template="full"
208
+ local team_max_iterations=10
209
+ local team_size=${#recommended_team[@]}
210
+ if [[ $team_size -le 2 ]]; then
211
+ team_template="quick-fix"
212
+ team_max_iterations=5
213
+ elif [[ $team_size -ge 5 ]]; then
214
+ team_template="careful"
215
+ team_max_iterations=20
216
+ fi
217
+ # Security tasks get more iterations
218
+ if printf '%s\n' "${recommended_team[@]}" | grep -q "security-auditor"; then
219
+ team_template="careful"
220
+ [[ $team_max_iterations -lt 15 ]] && team_max_iterations=15
221
+ fi
222
+
223
+ jq -c -n \
224
+ --argjson team "$roles_json" \
225
+ --arg method "$team_method" \
226
+ --argjson cost "$total_cost" \
227
+ --arg model "$team_model" \
228
+ --argjson agents "$team_size" \
229
+ --arg template "$team_template" \
230
+ --argjson max_iterations "$team_max_iterations" \
231
+ '{
232
+ team: $team,
233
+ method: $method,
234
+ estimated_cost: $cost,
235
+ model: $model,
236
+ agents: $agents,
237
+ template: $template,
238
+ max_iterations: $max_iterations
239
+ }'
240
+ return 0
241
+ fi
242
+
243
+ success "Recommended Team (${#recommended_team[@]} members, via ${team_method}):"
244
+ echo ""
245
+
246
+ for role in "${recommended_team[@]}"; do
247
+ local role_info
248
+ role_info=$(jq ".\"${role}\"" "$ROLES_DB" 2>/dev/null || echo "null")
249
+ if [[ "$role_info" != "null" ]]; then
250
+ printf " • ${CYAN}%-20s${RESET} (${PURPLE}%s${RESET}) — %s\n" \
251
+ "$role" \
252
+ "$(echo "$role_info" | jq -r '.recommended_model')" \
253
+ "$(echo "$role_info" | jq -r '.title')"
254
+ else
255
+ printf " • ${CYAN}%-20s${RESET} (${PURPLE}%s${RESET}) — %s\n" \
256
+ "$role" "sonnet" "Custom role"
257
+ fi
258
+ done
259
+
260
+ echo ""
261
+ echo "Estimated Team Cost: \$${total_cost}/task"
262
+ }
263
+
264
+ # ═══════════════════════════════════════════════════════════════════════════════
265
+ # AUTONOMOUS ROLE INVENTION (Tier 3)
266
+ # ═══════════════════════════════════════════════════════════════════════════════
267
+
268
+ cmd_invent() {
269
+ ensure_recruit_dir
270
+ initialize_builtin_roles
271
+
272
+ info "Scanning for unmatched task patterns to invent new roles..."
273
+ echo ""
274
+
275
+ if [[ ! -f "$MATCH_HISTORY" ]]; then
276
+ warn "No match history — run more tasks first"
277
+ return 0
278
+ fi
279
+
280
+ # Find tasks that defaulted to builder (low confidence or no keyword match)
281
+ local unmatched_tasks
282
+ unmatched_tasks=$(jq -s -r '
283
+ [.[] | select(
284
+ (.role == "builder" and (.confidence // 0.5) < 0.6) or
285
+ (.method == "keyword" and (.confidence // 0.5) < 0.4)
286
+ ) | .task] | unique | .[:20][]
287
+ ' "$MATCH_HISTORY" 2>/dev/null || true)
288
+
289
+ if [[ -z "$unmatched_tasks" ]]; then
290
+ success "No unmatched patterns detected — all tasks well-covered"
291
+ return 0
292
+ fi
293
+
294
+ local task_count
295
+ task_count=$(echo "$unmatched_tasks" | wc -l | tr -d ' ')
296
+ info "Found ${task_count} poorly-matched tasks"
297
+
298
+ if ! _recruit_has_claude; then
299
+ warn "Claude not available for role invention. Unmatched tasks:"
300
+ echo "$unmatched_tasks" | sed 's/^/ - /'
301
+ return 0
302
+ fi
303
+
304
+ local existing_roles
305
+ existing_roles=$(jq -r 'to_entries | map("\(.key): \(.value.description)") | join("\n")' "$ROLES_DB" 2>/dev/null || echo "none")
306
+
307
+ local prompt
308
+ prompt="Analyze these tasks that weren't well-matched to existing agent roles. Identify recurring patterns and suggest new roles.
309
+
310
+ Poorly-matched tasks:
311
+ ${unmatched_tasks}
312
+
313
+ Existing roles:
314
+ ${existing_roles}
315
+
316
+ If you identify a clear pattern (2+ tasks that share a theme), propose a new role:
317
+ {\"roles\": [{\"key\": \"<kebab-case>\", \"title\": \"<Title>\", \"description\": \"<desc>\", \"required_skills\": [\"<skill>\"], \"trigger_keywords\": [\"<keyword>\"], \"recommended_model\": \"sonnet\", \"estimated_cost_per_task_usd\": 1.5}]}
318
+
319
+ If no new role is needed, return: {\"roles\": [], \"reasoning\": \"existing roles are sufficient\"}
320
+
321
+ Return JSON only."
322
+
323
+ local result
324
+ result=$(_recruit_call_claude "$prompt")
325
+
326
+ if [[ -n "$result" ]] && echo "$result" | jq -e '.roles | length > 0' >/dev/null 2>&1; then
327
+ local new_count
328
+ new_count=$(echo "$result" | jq '.roles | length')
329
+
330
+ echo ""
331
+ success "Invented ${new_count} new role(s):"
332
+ echo ""
333
+
334
+ local i=0
335
+ while [[ "$i" -lt "$new_count" ]]; do
336
+ local role_key role_title role_desc
337
+ role_key=$(echo "$result" | jq -r ".roles[$i].key")
338
+ role_title=$(echo "$result" | jq -r ".roles[$i].title")
339
+ role_desc=$(echo "$result" | jq -r ".roles[$i].description")
340
+
341
+ echo -e " ${CYAN}${BOLD}${role_key}${RESET}: ${role_title}"
342
+ echo -e " ${DIM}${role_desc}${RESET}"
343
+ echo ""
344
+
345
+ # Auto-create the role
346
+ local role_json
347
+ role_json=$(echo "$result" | jq ".roles[$i] | del(.key) + {origin: \"invented\", created_at: \"$(now_iso)\"}")
348
+
349
+ local tmp_file
350
+ tmp_file=$(mktemp)
351
+ trap "rm -f '$tmp_file'" RETURN
352
+ jq --arg key "$role_key" --argjson role "$role_json" '.[$key] = $role' "$ROLES_DB" > "$tmp_file" && _recruit_locked_write "$ROLES_DB" "$tmp_file" || rm -f "$tmp_file"
353
+
354
+ # Update heuristics with trigger keywords
355
+ local keywords
356
+ keywords=$(echo "$result" | jq -r ".roles[$i].trigger_keywords // [] | .[]" 2>/dev/null || true)
357
+ if [[ -n "$keywords" ]]; then
358
+ local heur_tmp
359
+ heur_tmp=$(mktemp)
360
+ trap "rm -f '$heur_tmp'" RETURN
361
+ while IFS= read -r kw; do
362
+ [[ -z "$kw" ]] && continue
363
+ jq --arg kw "$kw" --arg role "$role_key" \
364
+ '.keyword_weights[$kw] = {role: $role, weight: 10, source: "invented"}' \
365
+ "$HEURISTICS_DB" > "$heur_tmp" && mv "$heur_tmp" "$HEURISTICS_DB" || true
366
+ done <<< "$keywords"
367
+ fi
368
+
369
+ # Log invention
370
+ echo "$role_json" | jq -c --arg key "$role_key" '. + {key: $key}' >> "$INVENTED_ROLES_LOG" 2>/dev/null || true
371
+
372
+ emit_event "recruit_role_invented" "role=${role_key}" "title=${role_title}"
373
+ i=$((i + 1))
374
+ done
375
+ else
376
+ local reasoning
377
+ reasoning=$(echo "$result" | jq -r '.reasoning // "no analysis available"' 2>/dev/null || echo "no analysis available")
378
+ info "No new roles needed: ${reasoning}"
379
+ fi
380
+ }
381
+
382
+ # ═══════════════════════════════════════════════════════════════════════════════
383
+ # THEORY OF MIND: PER-AGENT WORKING STYLE PROFILES (Tier 3)
384
+ # ═══════════════════════════════════════════════════════════════════════════════
385
+
386
+ cmd_mind() {
387
+ local agent_id="${1:-}"
388
+
389
+ if [[ -z "$agent_id" ]]; then
390
+ # Show all agent minds
391
+ ensure_recruit_dir
392
+
393
+ info "Agent Theory of Mind Profiles:"
394
+ echo ""
395
+
396
+ if [[ ! -f "$AGENT_MINDS_DB" || "$(jq 'length' "$AGENT_MINDS_DB" 2>/dev/null || echo 0)" -eq 0 ]]; then
397
+ warn "No agent mind profiles yet. Use 'shipwright recruit mind <agent-id>' after recording outcomes."
398
+ return 0
399
+ fi
400
+
401
+ jq -r 'to_entries[] |
402
+ "\(.key):" +
403
+ "\n Style: \(.value.working_style // "unknown")" +
404
+ "\n Strengths: \(.value.strengths // [] | join(", "))" +
405
+ "\n Weaknesses: \(.value.weaknesses // [] | join(", "))" +
406
+ "\n Best with: \(.value.ideal_task_type // "general")" +
407
+ "\n Onboarding: \(.value.onboarding_preference // "standard")\n"
408
+ ' "$AGENT_MINDS_DB" 2>/dev/null || warn "Could not read mind profiles"
409
+ return 0
410
+ fi
411
+
412
+ ensure_recruit_dir
413
+
414
+ info "Building theory of mind for: ${CYAN}${agent_id}${RESET}"
415
+ echo ""
416
+
417
+ # Gather agent's task history
418
+ local profile
419
+ profile=$(jq ".\"${agent_id}\" // {}" "$PROFILES_DB" 2>/dev/null || echo "{}")
420
+
421
+ if [[ "$profile" == "{}" ]]; then
422
+ warn "No profile data for ${agent_id}"
423
+ return 1
424
+ fi
425
+
426
+ local task_history
427
+ task_history=$(echo "$profile" | jq -c '.task_history // []')
428
+ local success_rate
429
+ success_rate=$(echo "$profile" | jq -r '.success_rate // 0')
430
+ local avg_time
431
+ avg_time=$(echo "$profile" | jq -r '.avg_time_minutes // 0')
432
+ local tasks_completed
433
+ tasks_completed=$(echo "$profile" | jq -r '.tasks_completed // 0')
434
+
435
+ # Heuristic mind model
436
+ local working_style="balanced"
437
+ local strengths=()
438
+ local weaknesses=()
439
+ local ideal_task_type="general"
440
+ local onboarding_pref="standard"
441
+
442
+ # Analyze speed
443
+ if awk -v t="$avg_time" 'BEGIN{exit !(t < 10)}' 2>/dev/null; then
444
+ working_style="fast-iterative"
445
+ strengths+=("speed")
446
+ onboarding_pref="minimal-context"
447
+ elif awk -v t="$avg_time" 'BEGIN{exit !(t > 30)}' 2>/dev/null; then
448
+ working_style="thorough-methodical"
449
+ strengths+=("thoroughness")
450
+ onboarding_pref="detailed-specs"
451
+ fi
452
+
453
+ # Analyze success rate
454
+ if awk -v s="$success_rate" 'BEGIN{exit !(s >= 90)}' 2>/dev/null; then
455
+ strengths+=("reliability")
456
+ elif awk -v s="$success_rate" 'BEGIN{exit !(s < 60)}' 2>/dev/null; then
457
+ weaknesses+=("consistency")
458
+ fi
459
+
460
+ # LLM-powered mind profile
461
+ if _recruit_has_claude && [[ "$tasks_completed" -ge 5 ]]; then
462
+ local prompt
463
+ prompt="Build a psychological profile for an AI agent based on its performance history.
464
+
465
+ Agent: ${agent_id}
466
+ Tasks completed: ${tasks_completed}
467
+ Success rate: ${success_rate}%
468
+ Avg time per task: ${avg_time} minutes
469
+ Recent task history: ${task_history}
470
+
471
+ Create a working style profile:
472
+ {\"working_style\": \"<fast-iterative|thorough-methodical|balanced|creative-exploratory>\",
473
+ \"strengths\": [\"<strength1>\", \"<strength2>\"],
474
+ \"weaknesses\": [\"<weakness1>\"],
475
+ \"ideal_task_type\": \"<description of best-fit tasks>\",
476
+ \"onboarding_preference\": \"<minimal-context|detailed-specs|example-driven|standard>\",
477
+ \"collaboration_style\": \"<independent|pair-oriented|team-player>\"}
478
+
479
+ Return JSON only."
480
+
481
+ local result
482
+ result=$(_recruit_call_claude "$prompt")
483
+
484
+ if [[ -n "$result" ]] && echo "$result" | jq -e '.working_style' >/dev/null 2>&1; then
485
+ # Save the LLM-generated mind profile
486
+ local tmp_file
487
+ tmp_file=$(mktemp)
488
+ trap "rm -f '$tmp_file'" RETURN
489
+ jq --arg id "$agent_id" --argjson mind "$result" '.[$id] = ($mind + {updated: (now | todate)})' "$AGENT_MINDS_DB" > "$tmp_file" && _recruit_locked_write "$AGENT_MINDS_DB" "$tmp_file" || rm -f "$tmp_file"
490
+
491
+ success "Mind profile generated:"
492
+ echo "$result" | jq -r '
493
+ " Working style: \(.working_style)" +
494
+ "\n Strengths: \(.strengths | join(", "))" +
495
+ "\n Weaknesses: \(.weaknesses | join(", "))" +
496
+ "\n Ideal tasks: \(.ideal_task_type)" +
497
+ "\n Onboarding: \(.onboarding_preference)" +
498
+ "\n Collaboration: \(.collaboration_style // "standard")"
499
+ '
500
+ emit_event "recruit_mind" "agent_id=${agent_id}"
501
+ return 0
502
+ fi
503
+ fi
504
+
505
+ # Fallback: save heuristic profile
506
+ local strengths_json weaknesses_json
507
+ if [[ ${#strengths[@]} -gt 0 ]]; then
508
+ strengths_json=$(printf '%s\n' "${strengths[@]}" | jq -R . | jq -s .)
509
+ else
510
+ strengths_json='[]'
511
+ fi
512
+ if [[ ${#weaknesses[@]} -gt 0 ]]; then
513
+ weaknesses_json=$(printf '%s\n' "${weaknesses[@]}" | jq -R . | jq -s .)
514
+ else
515
+ weaknesses_json='[]'
516
+ fi
517
+
518
+ local mind_json
519
+ mind_json=$(jq -n \
520
+ --arg style "$working_style" \
521
+ --argjson strengths "$strengths_json" \
522
+ --argjson weaknesses "$weaknesses_json" \
523
+ --arg ideal "$ideal_task_type" \
524
+ --arg onboard "$onboarding_pref" \
525
+ --arg ts "$(now_iso)" \
526
+ '{working_style: $style, strengths: $strengths, weaknesses: $weaknesses, ideal_task_type: $ideal, onboarding_preference: $onboard, updated: $ts}')
527
+
528
+ local tmp_file
529
+ tmp_file=$(mktemp)
530
+ trap "rm -f '$tmp_file'" RETURN
531
+ jq --arg id "$agent_id" --argjson mind "$mind_json" '.[$id] = $mind' "$AGENT_MINDS_DB" > "$tmp_file" && _recruit_locked_write "$AGENT_MINDS_DB" "$tmp_file" || rm -f "$tmp_file"
532
+
533
+ local strengths_display="none detected"
534
+ [[ ${#strengths[@]} -gt 0 ]] && strengths_display="${strengths[*]}"
535
+
536
+ success "Mind profile (heuristic):"
537
+ echo " Working style: ${working_style}"
538
+ echo " Strengths: ${strengths_display}"
539
+ echo " Onboarding: ${onboarding_pref}"
540
+ emit_event "recruit_mind" "agent_id=${agent_id}" "method=heuristic"
541
+ }
542
+
543
+ # ═══════════════════════════════════════════════════════════════════════════════
544
+ # GOAL DECOMPOSITION (Tier 3)
545
+ # ═══════════════════════════════════════════════════════════════════════════════
546
+
547
+ cmd_decompose() {
548
+ local goal="${1:-}"
549
+
550
+ if [[ -z "$goal" ]]; then
551
+ error "Usage: shipwright recruit decompose \"<vague goal or intent>\""
552
+ exit 1
553
+ fi
554
+
555
+ ensure_recruit_dir
556
+ initialize_builtin_roles
557
+
558
+ info "Decomposing goal: ${CYAN}${goal}${RESET}"
559
+ echo ""
560
+
561
+ local available_roles
562
+ available_roles=$(jq -r 'to_entries | map("\(.key): \(.value.title) — \(.value.description)") | join("\n")' "$ROLES_DB" 2>/dev/null || echo "none")
563
+
564
+ if _recruit_has_claude; then
565
+ local prompt
566
+ prompt="Decompose this high-level goal into specific sub-tasks, and assign the best agent role for each.
567
+
568
+ Goal: ${goal}
569
+
570
+ Available agent roles:
571
+ ${available_roles}
572
+
573
+ Return a JSON object:
574
+ {\"goal\": \"<restated goal>\",
575
+ \"sub_tasks\": [
576
+ {\"task\": \"<specific task>\", \"role\": \"<role_key>\", \"priority\": \"high|medium|low\", \"depends_on\": [], \"estimated_time_min\": 30},
577
+ ...
578
+ ],
579
+ \"capability_gaps\": [\"<any capabilities not covered by existing roles>\"],
580
+ \"total_estimated_time_min\": 120,
581
+ \"risk_assessment\": \"<brief risk summary>\"}
582
+
583
+ Return JSON only."
584
+
585
+ local result
586
+ result=$(_recruit_call_claude "$prompt")
587
+
588
+ if [[ -n "$result" ]] && echo "$result" | jq -e '.sub_tasks' >/dev/null 2>&1; then
589
+ local restated_goal
590
+ restated_goal=$(echo "$result" | jq -r '.goal // ""')
591
+ [[ -n "$restated_goal" ]] && echo -e " ${DIM}Interpreted as: ${restated_goal}${RESET}"
592
+ echo ""
593
+
594
+ local task_count
595
+ task_count=$(echo "$result" | jq '.sub_tasks | length')
596
+ success "Decomposed into ${task_count} sub-tasks:"
597
+ echo ""
598
+
599
+ echo "$result" | jq -r '.sub_tasks | to_entries[] |
600
+ " \(.key + 1). [\(.value.priority // "medium")] \(.value.task)" +
601
+ "\n Role: \(.value.role) | Est: \(.value.estimated_time_min // "?")min" +
602
+ (if (.value.depends_on | length) > 0 then "\n Depends on: \(.value.depends_on | join(", "))" else "" end)
603
+ '
604
+
605
+ # Show capability gaps
606
+ local gaps
607
+ gaps=$(echo "$result" | jq -r '.capability_gaps // [] | .[]' 2>/dev/null || true)
608
+ if [[ -n "$gaps" ]]; then
609
+ echo ""
610
+ warn "Capability gaps detected:"
611
+ echo "$gaps" | sed 's/^/ - /'
612
+ echo " Consider: shipwright recruit create-role --auto \"<gap description>\""
613
+ fi
614
+
615
+ # Show totals
616
+ local total_time
617
+ total_time=$(echo "$result" | jq -r '.total_estimated_time_min // 0')
618
+ local risk
619
+ risk=$(echo "$result" | jq -r '.risk_assessment // "unknown"')
620
+ echo ""
621
+ echo " Total estimated time: ${total_time} minutes"
622
+ echo " Risk: ${risk}"
623
+
624
+ emit_event "recruit_decompose" "goal_length=${#goal}" "tasks=${task_count}" "gaps=$(echo "$gaps" | wc -l | tr -d ' ')"
625
+ return 0
626
+ fi
627
+ fi
628
+
629
+ # Fallback: simple decomposition
630
+ warn "AI decomposition unavailable — showing default breakdown"
631
+ echo ""
632
+ echo " 1. [high] Plan and design the approach"
633
+ echo " Role: architect"
634
+ echo " 2. [high] Implement the solution"
635
+ echo " Role: builder"
636
+ echo " 3. [medium] Write tests"
637
+ echo " Role: tester"
638
+ echo " 4. [medium] Code review"
639
+ echo " Role: reviewer"
640
+ echo " 5. [low] Update documentation"
641
+ echo " Role: docs-writer"
642
+ }
643
+
644
+ # ═══════════════════════════════════════════════════════════════════════════════
645
+ # AGENT PROMOTION (Tier 2)
646
+ # ═══════════════════════════════════════════════════════════════════════════════
647
+
648
+ cmd_promote() {
649
+ local agent_id="${1:-}"
650
+
651
+ if [[ -z "$agent_id" ]]; then
652
+ error "Usage: shipwright recruit promote <agent-id>"
653
+ exit 1
654
+ fi
655
+
656
+ ensure_recruit_dir
657
+
658
+ info "Evaluating promotion eligibility for: ${CYAN}${agent_id}${RESET}"
659
+ echo ""
660
+
661
+ local profile
662
+ profile=$(jq ".\"${agent_id}\"" "$PROFILES_DB" 2>/dev/null || echo "{}")
663
+
664
+ if [[ "$profile" == "{}" || "$profile" == "null" ]]; then
665
+ warn "No profile found for ${agent_id}"
666
+ return 1
667
+ fi
668
+
669
+ local success_rate quality_score
670
+ success_rate=$(echo "$profile" | jq -r '.success_rate // 0')
671
+ quality_score=$(echo "$profile" | jq -r '.quality_score // 0')
672
+
673
+ local current_model
674
+ current_model=$(echo "$profile" | jq -r '.model // "haiku"')
675
+
676
+ # Use population-aware thresholds
677
+ local pop_stats
678
+ pop_stats=$(_recruit_compute_population_stats)
679
+ local mean_success
680
+ mean_success=$(echo "$pop_stats" | jq -r '.mean_success')
681
+ local agent_count
682
+ agent_count=$(echo "$pop_stats" | jq -r '.count')
683
+
684
+ local promote_sr_threshold="${RECRUIT_PROMOTE_SUCCESS:-85}"
685
+ local promote_q_threshold=9
686
+ local demote_sr_threshold=60
687
+ local demote_q_threshold=5
688
+
689
+ if [[ "$agent_count" -ge 3 ]]; then
690
+ local stddev
691
+ stddev=$(echo "$pop_stats" | jq -r '.stddev_success')
692
+ promote_sr_threshold=$(awk -v m="$mean_success" -v s="$stddev" 'BEGIN{v=m+s; if(v>98) v=98; printf "%.0f", v}')
693
+ demote_sr_threshold=$(awk -v m="$mean_success" -v s="$stddev" 'BEGIN{v=m-1.5*s; if(v<30) v=30; printf "%.0f", v}')
694
+ fi
695
+
696
+ local recommended_model="$current_model"
697
+ local promotion_reason=""
698
+
699
+ if awk -v sr="$success_rate" -v st="$promote_sr_threshold" -v qs="$quality_score" -v qt="$promote_q_threshold" \
700
+ 'BEGIN{exit !(sr >= st && qs >= qt)}' 2>/dev/null; then
701
+ case "$current_model" in
702
+ haiku) recommended_model="sonnet"; promotion_reason="Excellent performance on Haiku" ;;
703
+ sonnet) recommended_model="opus"; promotion_reason="Outstanding results on Sonnet" ;;
704
+ opus) promotion_reason="Already on best model"; recommended_model="opus" ;;
705
+ esac
706
+ elif awk -v sr="$success_rate" -v st="$demote_sr_threshold" -v qs="$quality_score" -v qt="$demote_q_threshold" \
707
+ 'BEGIN{exit !(sr < st || qs < qt)}' 2>/dev/null; then
708
+ case "$current_model" in
709
+ opus) recommended_model="sonnet"; promotion_reason="Struggling on Opus, try Sonnet" ;;
710
+ sonnet) recommended_model="haiku"; promotion_reason="Poor performance, reduce cost" ;;
711
+ haiku) promotion_reason="Consider retraining"; recommended_model="haiku" ;;
712
+ esac
713
+ fi
714
+
715
+ if [[ "$recommended_model" != "$current_model" ]]; then
716
+ success "Recommend upgrading from ${CYAN}${current_model}${RESET} to ${PURPLE}${recommended_model}${RESET}"
717
+ echo " Reason: $promotion_reason"
718
+ echo -e " ${DIM}Thresholds: promote ≥${promote_sr_threshold}%, demote <${demote_sr_threshold}% (${agent_count} agents in population)${RESET}"
719
+ emit_event "recruit_promotion" "agent_id=${agent_id}" "from=${current_model}" "to=${recommended_model}" "reason=${promotion_reason}"
720
+ else
721
+ info "No model change recommended for ${agent_id}"
722
+ echo " Current: ${current_model} | Success: ${success_rate}% | Quality: ${quality_score}/10"
723
+ fi
724
+ }
725
+
726
+ # ═══════════════════════════════════════════════════════════════════════════════
727
+ # ONBOARDING CONTEXT GENERATION
728
+ # ═══════════════════════════════════════════════════════════════════════════════
729
+
730
+ cmd_onboard() {
731
+ local agent_role="${1:-builder}"
732
+ local agent_id="${2:-}"
733
+
734
+ ensure_recruit_dir
735
+ initialize_builtin_roles
736
+
737
+ info "Generating onboarding context for: ${CYAN}${agent_role}${RESET}"
738
+ echo ""
739
+
740
+ local role_info
741
+ role_info=$(jq --arg role "$agent_role" '.[$role]' "$ROLES_DB" 2>/dev/null)
742
+
743
+ if [[ -z "$role_info" || "$role_info" == "null" ]]; then
744
+ error "Unknown role: ${agent_role}"
745
+ exit 1
746
+ fi
747
+
748
+ # Build adaptive onboarding based on theory-of-mind if available
749
+ local onboarding_style="standard"
750
+ if [[ -n "$agent_id" && -f "$AGENT_MINDS_DB" ]]; then
751
+ local mind_profile
752
+ mind_profile=$(jq ".\"${agent_id}\"" "$AGENT_MINDS_DB" 2>/dev/null || echo "null")
753
+ if [[ "$mind_profile" != "null" ]]; then
754
+ onboarding_style=$(echo "$mind_profile" | jq -r '.onboarding_preference // "standard"')
755
+ info "Adapting onboarding to agent preference: ${PURPLE}${onboarding_style}${RESET}"
756
+ fi
757
+ fi
758
+
759
+ # Build onboarding style description outside the heredoc
760
+ local style_desc="Standard onboarding. Review the role profile and codebase structure."
761
+ case "$onboarding_style" in
762
+ minimal-context) style_desc="This agent works best with minimal upfront context. Provide the core task and let them explore." ;;
763
+ detailed-specs) style_desc="This agent prefers detailed specifications. Provide full requirements, edge cases, and examples." ;;
764
+ example-driven) style_desc="This agent learns best from examples. Provide sample inputs/outputs and reference implementations." ;;
765
+ esac
766
+
767
+ local role_title_val role_desc_val role_model_val role_origin_val role_cost_val
768
+ role_title_val=$(echo "$role_info" | jq -r '.title')
769
+ role_desc_val=$(echo "$role_info" | jq -r '.description')
770
+ role_model_val=$(echo "$role_info" | jq -r '.recommended_model')
771
+ role_origin_val=$(echo "$role_info" | jq -r '.origin // "builtin"')
772
+ role_cost_val=$(echo "$role_info" | jq -r '.estimated_cost_per_task_usd')
773
+ local role_skills_val role_context_val role_metrics_val
774
+ role_skills_val=$(echo "$role_info" | jq -r '.required_skills[]' | sed 's/^/- /')
775
+ role_context_val=$(echo "$role_info" | jq -r '.context_needs[]' | sed 's/^/- /')
776
+ role_metrics_val=$(echo "$role_info" | jq -r '.success_metrics[]' | sed 's/^/- /')
777
+
778
+ local onboarding_doc
779
+ onboarding_doc="# Onboarding Context: ${agent_role}
780
+
781
+ ## Role Profile
782
+ **Title:** ${role_title_val}
783
+ **Description:** ${role_desc_val}
784
+ **Recommended Model:** ${role_model_val}
785
+ **Origin:** ${role_origin_val}
786
+
787
+ ## Required Skills
788
+ ${role_skills_val}
789
+
790
+ ## Context Needs
791
+ ${role_context_val}
792
+
793
+ ## Success Metrics
794
+ ${role_metrics_val}
795
+
796
+ ## Cost Profile
797
+ Estimated cost per task: \$${role_cost_val}
798
+
799
+ ## Onboarding Style: ${onboarding_style}
800
+ ${style_desc}
801
+
802
+ ## Getting Started
803
+ 1. Review the role profile above
804
+ 2. Study the codebase architecture
805
+ 3. Familiarize yourself with coding standards
806
+ 4. Review past pipeline runs for patterns
807
+ 5. Ask questions about unclear requirements
808
+
809
+ ## Resources
810
+ - Codebase: /path/to/repo
811
+ - Documentation: See .claude/ directory
812
+ - Team patterns: Reviewed in memory system
813
+ - Past learnings: Available in ~/.shipwright/memory/"
814
+
815
+ local onboarding_key
816
+ onboarding_key=$(date +%s)
817
+ jq --arg key "$onboarding_key" --arg doc "$onboarding_doc" '.[$key] = $doc' "$ONBOARDING_DB" > "${ONBOARDING_DB}.tmp"
818
+ mv "${ONBOARDING_DB}.tmp" "$ONBOARDING_DB"
819
+
820
+ success "Onboarding context generated for ${agent_role}"
821
+ echo ""
822
+ echo "$onboarding_doc"
823
+ emit_event "recruit_onboarding" "role=${agent_role}" "style=${onboarding_style}" "timestamp=$(now_epoch)"
824
+ }
825
+
826
+ # ═══════════════════════════════════════════════════════════════════════════════
827
+ # STATISTICS REPORTING
828
+ # ═══════════════════════════════════════════════════════════════════════════════
829
+
830
+ cmd_stats() {
831
+ ensure_recruit_dir
832
+
833
+ info "Recruitment Statistics & Talent Trends:"
834
+ echo ""
835
+
836
+ local role_count profile_count talent_count
837
+ role_count=$(jq 'length' "$ROLES_DB" 2>/dev/null || echo 0)
838
+ profile_count=$(jq 'length' "$PROFILES_DB" 2>/dev/null || echo 0)
839
+ talent_count=$(jq 'length' "$TALENT_DB" 2>/dev/null || echo 0)
840
+
841
+ local builtin_count custom_count invented_count
842
+ builtin_count=$(jq '[.[] | select(.origin == "builtin" or .origin == null)] | length' "$ROLES_DB" 2>/dev/null || echo 0)
843
+ custom_count=$(jq '[.[] | select(.origin == "manual" or .origin == "ai-generated")] | length' "$ROLES_DB" 2>/dev/null || echo 0)
844
+ invented_count=$(jq '[.[] | select(.origin == "invented")] | length' "$ROLES_DB" 2>/dev/null || echo 0)
845
+
846
+ echo " Roles Defined: $role_count (builtin: ${builtin_count}, custom: ${custom_count}, invented: ${invented_count})"
847
+ echo " Agents Profiled: $profile_count"
848
+ echo " Talent Records: $talent_count"
849
+
850
+ if [[ -f "$MATCH_HISTORY" ]]; then
851
+ local match_count
852
+ match_count=$(wc -l < "$MATCH_HISTORY" 2>/dev/null | tr -d ' ')
853
+ echo " Match History: ${match_count} records"
854
+ fi
855
+
856
+ if [[ -f "$HEURISTICS_DB" ]]; then
857
+ local keyword_count last_tuned
858
+ keyword_count=$(jq '.keyword_weights | length' "$HEURISTICS_DB" 2>/dev/null || echo 0)
859
+ last_tuned=$(jq -r '.last_tuned // "never"' "$HEURISTICS_DB" 2>/dev/null || echo "never")
860
+ echo " Learned Keywords: ${keyword_count}"
861
+ echo " Last Self-Tuned: ${last_tuned}"
862
+ fi
863
+
864
+ if [[ -f "$META_LEARNING_DB" ]]; then
865
+ local corrections accuracy_points
866
+ corrections=$(jq '.corrections | length' "$META_LEARNING_DB" 2>/dev/null || echo 0)
867
+ accuracy_points=$(jq '.accuracy_trend | length' "$META_LEARNING_DB" 2>/dev/null || echo 0)
868
+ echo " Meta-Learning Corrections: ${corrections}"
869
+ echo " Accuracy Data Points: ${accuracy_points}"
870
+ fi
871
+
872
+ echo ""
873
+
874
+ if [[ "$profile_count" -gt 0 ]]; then
875
+ local pop_stats
876
+ pop_stats=$(_recruit_compute_population_stats)
877
+ echo " Population Stats:"
878
+ echo " Mean Success Rate: $(echo "$pop_stats" | jq -r '.mean_success')%"
879
+ echo " Std Dev: $(echo "$pop_stats" | jq -r '.stddev_success')%"
880
+ echo " P90/P10 Spread: $(echo "$pop_stats" | jq -r '.p90_success')% / $(echo "$pop_stats" | jq -r '.p10_success')%"
881
+ echo ""
882
+ fi
883
+
884
+ success "Use 'shipwright recruit profiles' for detailed breakdown"
885
+ }