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
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bash
2
+ # shellcheck disable=SC2034,SC2064 # config vars used by sourced scripts; traps expand at definition time
2
3
  # ╔═══════════════════════════════════════════════════════════════════════════╗
3
4
  # ║ sw-recruit.sh — AGI-Level Agent Recruitment & Talent Management ║
4
5
  # ║ ║
@@ -40,2342 +41,130 @@ fi
40
41
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
41
42
  emit_event() {
42
43
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
43
- local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
44
+ local payload
45
+ payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
44
46
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
45
47
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
46
48
  }
47
49
  fi
48
- # ─── File Locking for Concurrent Safety ────────────────────────────────────
49
- # Usage: _recruit_locked_write <target_file> <tmp_file>
50
- # Acquires flock, then moves tmp_file to target atomically.
51
- # Caller is responsible for creating tmp_file and cleaning up on error.
52
- _recruit_locked_write() {
53
- local target="$1"
54
- local tmp_file="$2"
55
- local lock_file="${target}.lock"
56
-
57
- (
58
- if command -v flock >/dev/null 2>&1; then
59
- flock -w 5 200 2>/dev/null || true
60
- fi
61
- mv "$tmp_file" "$target"
62
- ) 200>"$lock_file"
63
- }
64
-
65
- # ─── Recruitment Storage Paths ─────────────────────────────────────────────
66
- RECRUIT_ROOT="${HOME}/.shipwright/recruitment"
67
- ROLES_DB="${RECRUIT_ROOT}/roles.json"
68
- PROFILES_DB="${RECRUIT_ROOT}/profiles.json"
69
- TALENT_DB="${RECRUIT_ROOT}/talent.json"
70
- ONBOARDING_DB="${RECRUIT_ROOT}/onboarding.json"
71
- MATCH_HISTORY="${RECRUIT_ROOT}/match-history.jsonl"
72
- ROLE_USAGE_DB="${RECRUIT_ROOT}/role-usage.json"
73
- HEURISTICS_DB="${RECRUIT_ROOT}/heuristics.json"
74
- AGENT_MINDS_DB="${RECRUIT_ROOT}/agent-minds.json"
75
- INVENTED_ROLES_LOG="${RECRUIT_ROOT}/invented-roles.jsonl"
76
- META_LEARNING_DB="${RECRUIT_ROOT}/meta-learning.json"
77
-
78
- # ─── Policy Integration ──────────────────────────────────────────────────
79
- POLICY_FILE="${SCRIPT_DIR}/../config/policy.json"
80
- _recruit_policy() {
81
- local key="$1"
82
- local default="$2"
83
- if [[ -f "$POLICY_FILE" ]] && command -v jq >/dev/null 2>&1; then
84
- local val
85
- val=$(jq -r ".recruit.${key} // empty" "$POLICY_FILE" 2>/dev/null) || true
86
- [[ -n "$val" ]] && echo "$val" || echo "$default"
87
- else
88
- echo "$default"
89
- fi
90
- }
91
-
92
- RECRUIT_CONFIDENCE_THRESHOLD=$(_recruit_policy "match_confidence_threshold" "0.3")
93
- RECRUIT_MAX_MATCH_HISTORY=$(_recruit_policy "max_match_history_size" "5000")
94
- RECRUIT_META_ACCURACY_FLOOR=$(_recruit_policy "meta_learning_accuracy_floor" "50")
95
- RECRUIT_LLM_TIMEOUT=$(_recruit_policy "llm_timeout_seconds" "30")
96
- RECRUIT_DEFAULT_MODEL=$(_recruit_policy "default_model" "sonnet")
97
- RECRUIT_SELF_TUNE_MIN_MATCHES=$(_recruit_policy "self_tune_min_matches" "5")
98
- RECRUIT_PROMOTE_TASKS=$(_recruit_policy "promote_threshold_tasks" "10")
99
- RECRUIT_PROMOTE_SUCCESS=$(_recruit_policy "promote_threshold_success_rate" "85")
100
- RECRUIT_AUTO_EVOLVE_AFTER=$(_recruit_policy "auto_evolve_after_outcomes" "20")
101
-
102
- ensure_recruit_dir() {
103
- mkdir -p "$RECRUIT_ROOT"
104
- [[ -f "$ROLES_DB" ]] || echo '{}' > "$ROLES_DB"
105
- [[ -f "$PROFILES_DB" ]] || echo '{}' > "$PROFILES_DB"
106
- [[ -f "$TALENT_DB" ]] || echo '[]' > "$TALENT_DB"
107
- [[ -f "$ONBOARDING_DB" ]] || echo '{}' > "$ONBOARDING_DB"
108
- [[ -f "$ROLE_USAGE_DB" ]] || echo '{}' > "$ROLE_USAGE_DB"
109
- [[ -f "$HEURISTICS_DB" ]] || echo '{"keyword_weights":{},"match_accuracy":[],"last_tuned":"never"}' > "$HEURISTICS_DB"
110
- [[ -f "$AGENT_MINDS_DB" ]] || echo '{}' > "$AGENT_MINDS_DB"
111
- [[ -f "$META_LEARNING_DB" ]] || echo '{"corrections":[],"accuracy_trend":[],"last_reflection":"never"}' > "$META_LEARNING_DB"
112
- }
113
-
114
- # ─── Intelligence Engine (optional) ────────────────────────────────────────
115
- INTELLIGENCE_AVAILABLE=false
116
- if [[ -f "$SCRIPT_DIR/sw-intelligence.sh" ]]; then
117
- # shellcheck source=sw-intelligence.sh
118
- source "$SCRIPT_DIR/sw-intelligence.sh"
119
- INTELLIGENCE_AVAILABLE=true
120
- fi
121
-
122
- # Check if Claude CLI is available for LLM-powered features
123
- # Set SW_RECRUIT_NO_LLM=1 to disable LLM calls (e.g., in tests)
124
- _recruit_has_claude() {
125
- [[ "${SW_RECRUIT_NO_LLM:-}" == "1" ]] && return 1
126
- command -v claude >/dev/null 2>&1
127
- }
128
-
129
- # Call Claude with a prompt, return text. Falls back gracefully.
130
- _recruit_call_claude() {
131
- local prompt="$1"
132
- local model="${2:-sonnet}"
133
-
134
- # Honor the no-LLM flag everywhere (not just _recruit_has_claude)
135
- [[ "${SW_RECRUIT_NO_LLM:-}" == "1" ]] && { echo ""; return; }
136
-
137
- if [[ "$INTELLIGENCE_AVAILABLE" == "true" ]] && command -v _intelligence_call_claude >/dev/null 2>&1; then
138
- _intelligence_call_claude "$prompt" 2>/dev/null || echo ""
139
- return
140
- fi
141
-
142
- if _recruit_has_claude; then
143
- claude -p "$prompt" --model "$model" 2>/dev/null || echo ""
144
- return
145
- fi
146
-
147
- echo ""
148
- }
149
-
150
- # ═══════════════════════════════════════════════════════════════════════════════
151
- # BUILT-IN ROLE DEFINITIONS
152
- # ═══════════════════════════════════════════════════════════════════════════════
153
-
154
- initialize_builtin_roles() {
155
- ensure_recruit_dir
156
-
157
- if jq -e '.architect' "$ROLES_DB" >/dev/null 2>&1; then
158
- return 0
159
- fi
160
-
161
- local roles_json
162
- roles_json=$(cat <<'EOF'
163
- {
164
- "architect": {
165
- "title": "Architect",
166
- "description": "System design, architecture decisions, scalability planning",
167
- "required_skills": ["system-design", "technology-evaluation", "code-review", "documentation"],
168
- "recommended_model": "opus",
169
- "context_needs": ["codebase-architecture", "system-patterns", "past-designs", "dependency-graph"],
170
- "success_metrics": ["design-quality", "implementation-feasibility", "team-alignment"],
171
- "estimated_cost_per_task_usd": 2.5,
172
- "origin": "builtin",
173
- "created_at": "2025-01-01T00:00:00Z"
174
- },
175
- "builder": {
176
- "title": "Builder",
177
- "description": "Feature implementation, core development, code generation",
178
- "required_skills": ["coding", "testing", "debugging", "performance-optimization"],
179
- "recommended_model": "sonnet",
180
- "context_needs": ["codebase-structure", "api-specs", "test-patterns", "build-system"],
181
- "success_metrics": ["tests-passing", "code-quality", "productivity", "bug-rate"],
182
- "estimated_cost_per_task_usd": 1.5,
183
- "origin": "builtin",
184
- "created_at": "2025-01-01T00:00:00Z"
185
- },
186
- "reviewer": {
187
- "title": "Code Reviewer",
188
- "description": "Code review, quality assurance, best practices enforcement",
189
- "required_skills": ["code-review", "static-analysis", "security-review", "best-practices"],
190
- "recommended_model": "sonnet",
191
- "context_needs": ["coding-standards", "previous-reviews", "common-errors", "team-patterns"],
192
- "success_metrics": ["review-quality", "issue-detection-rate", "feedback-clarity"],
193
- "estimated_cost_per_task_usd": 1.2,
194
- "origin": "builtin",
195
- "created_at": "2025-01-01T00:00:00Z"
196
- },
197
- "tester": {
198
- "title": "Test Specialist",
199
- "description": "Test strategy, test case generation, test automation, quality validation",
200
- "required_skills": ["testing", "coverage-analysis", "automation", "edge-case-detection"],
201
- "recommended_model": "sonnet",
202
- "context_needs": ["test-framework", "coverage-metrics", "failure-patterns", "requirements"],
203
- "success_metrics": ["coverage-increase", "bug-detection", "test-execution-time"],
204
- "estimated_cost_per_task_usd": 1.2,
205
- "origin": "builtin",
206
- "created_at": "2025-01-01T00:00:00Z"
207
- },
208
- "security-auditor": {
209
- "title": "Security Auditor",
210
- "description": "Security analysis, vulnerability detection, compliance verification",
211
- "required_skills": ["security-analysis", "threat-modeling", "penetration-testing", "compliance"],
212
- "recommended_model": "opus",
213
- "context_needs": ["security-policies", "vulnerability-database", "threat-models", "compliance-reqs"],
214
- "success_metrics": ["vulnerabilities-found", "severity-accuracy", "remediation-quality"],
215
- "estimated_cost_per_task_usd": 2.0,
216
- "origin": "builtin",
217
- "created_at": "2025-01-01T00:00:00Z"
218
- },
219
- "docs-writer": {
220
- "title": "Documentation Writer",
221
- "description": "Documentation creation, API docs, user guides, onboarding materials",
222
- "required_skills": ["documentation", "clarity", "completeness", "example-generation"],
223
- "recommended_model": "haiku",
224
- "context_needs": ["codebase-knowledge", "api-specs", "user-personas", "doc-templates"],
225
- "success_metrics": ["documentation-completeness", "clarity-score", "example-coverage"],
226
- "estimated_cost_per_task_usd": 0.8,
227
- "origin": "builtin",
228
- "created_at": "2025-01-01T00:00:00Z"
229
- },
230
- "optimizer": {
231
- "title": "Performance Optimizer",
232
- "description": "Performance analysis, optimization, profiling, efficiency improvements",
233
- "required_skills": ["performance-analysis", "profiling", "optimization", "metrics-analysis"],
234
- "recommended_model": "sonnet",
235
- "context_needs": ["performance-benchmarks", "profiling-tools", "optimization-history"],
236
- "success_metrics": ["performance-gain", "memory-efficiency", "latency-reduction"],
237
- "estimated_cost_per_task_usd": 1.5,
238
- "origin": "builtin",
239
- "created_at": "2025-01-01T00:00:00Z"
240
- },
241
- "devops": {
242
- "title": "DevOps Engineer",
243
- "description": "Infrastructure, deployment pipelines, CI/CD, monitoring, reliability",
244
- "required_skills": ["infrastructure-as-code", "deployment", "monitoring", "incident-response"],
245
- "recommended_model": "sonnet",
246
- "context_needs": ["infrastructure-config", "deployment-pipelines", "monitoring-setup", "runbooks"],
247
- "success_metrics": ["deployment-success-rate", "incident-response-time", "uptime"],
248
- "estimated_cost_per_task_usd": 1.8,
249
- "origin": "builtin",
250
- "created_at": "2025-01-01T00:00:00Z"
251
- },
252
- "pm": {
253
- "title": "Project Manager",
254
- "description": "Task decomposition, priority management, stakeholder communication, tracking",
255
- "required_skills": ["task-decomposition", "prioritization", "communication", "planning"],
256
- "recommended_model": "sonnet",
257
- "context_needs": ["project-state", "requirements", "team-capacity", "past-estimates"],
258
- "success_metrics": ["estimation-accuracy", "deadline-met", "scope-management"],
259
- "estimated_cost_per_task_usd": 1.0,
260
- "origin": "builtin",
261
- "created_at": "2025-01-01T00:00:00Z"
262
- },
263
- "incident-responder": {
264
- "title": "Incident Responder",
265
- "description": "Crisis management, root cause analysis, rapid issue resolution, hotfixes",
266
- "required_skills": ["crisis-management", "root-cause-analysis", "debugging", "communication"],
267
- "recommended_model": "opus",
268
- "context_needs": ["incident-history", "system-health", "alerting-rules", "past-incidents"],
269
- "success_metrics": ["incident-resolution-time", "accuracy", "escalation-prevention"],
270
- "estimated_cost_per_task_usd": 2.0,
271
- "origin": "builtin",
272
- "created_at": "2025-01-01T00:00:00Z"
273
- }
274
- }
275
- EOF
276
- )
277
- local _tmp_roles
278
- _tmp_roles=$(mktemp)
279
- trap "rm -f '$_tmp_roles'" RETURN
280
- if echo "$roles_json" | jq '.' > "$_tmp_roles" 2>/dev/null && [[ -s "$_tmp_roles" ]]; then
281
- mv "$_tmp_roles" "$ROLES_DB"
282
- else
283
- rm -f "$_tmp_roles"
284
- error "Failed to initialize roles DB"
285
- return 1
286
- fi
287
- success "Initialized 10 built-in agent roles"
288
- }
289
-
290
- # ═══════════════════════════════════════════════════════════════════════════════
291
- # LLM-POWERED SEMANTIC MATCHING (Tier 1)
292
- # ═══════════════════════════════════════════════════════════════════════════════
293
-
294
- # Heuristic keyword matching (fast fallback)
295
- _recruit_keyword_match() {
296
- local task_description="$1"
297
- local detected_skills=""
298
-
299
- # Always run built-in regex patterns first (most reliable)
300
- [[ "$task_description" =~ (architecture|design|scalability) ]] && detected_skills="${detected_skills}architect "
301
- [[ "$task_description" =~ (build|feature|implement|code) ]] && detected_skills="${detected_skills}builder "
302
- [[ "$task_description" =~ (review|quality|best.practice) ]] && detected_skills="${detected_skills}reviewer "
303
- [[ "$task_description" =~ (test|coverage|automation) ]] && detected_skills="${detected_skills}tester "
304
- [[ "$task_description" =~ (security|vulnerability|compliance) ]] && detected_skills="${detected_skills}security-auditor "
305
- [[ "$task_description" =~ (document|guide|readme|api.doc|write.doc) ]] && detected_skills="${detected_skills}docs-writer "
306
- [[ "$task_description" =~ (performance|optimization|profile|speed|latency|faster) ]] && detected_skills="${detected_skills}optimizer "
307
- [[ "$task_description" =~ (deploy|infra|ci.cd|monitoring|docker|kubernetes) ]] && detected_skills="${detected_skills}devops "
308
- [[ "$task_description" =~ (plan|decompose|estimate|priorit) ]] && detected_skills="${detected_skills}pm "
309
- [[ "$task_description" =~ (urgent|incident|crisis|hotfix|outage) ]] && detected_skills="${detected_skills}incident-responder "
310
-
311
- # Boost with learned keyword weights (override only if no regex match)
312
- if [[ -z "$detected_skills" && -f "$HEURISTICS_DB" ]]; then
313
- local learned_weights
314
- learned_weights=$(jq -r '.keyword_weights // {}' "$HEURISTICS_DB" 2>/dev/null || echo "{}")
315
-
316
- if [[ -n "$learned_weights" && "$learned_weights" != "{}" && "$learned_weights" != "null" ]]; then
317
- local best_role="" best_score=0
318
- local task_lower
319
- task_lower=$(echo "$task_description" | tr '[:upper:]' '[:lower:]')
320
-
321
- while IFS= read -r keyword; do
322
- [[ -z "$keyword" ]] && continue
323
- local kw_lower
324
- kw_lower=$(echo "$keyword" | tr '[:upper:]' '[:lower:]')
325
- if echo "$task_lower" | grep -q "$kw_lower" 2>/dev/null; then
326
- local role_score
327
- role_score=$(echo "$learned_weights" | jq -r --arg k "$keyword" '.[$k] | if type == "object" then .role else "" end' 2>/dev/null || echo "")
328
- local weight
329
- weight=$(echo "$learned_weights" | jq -r --arg k "$keyword" '.[$k] | if type == "object" then .weight else (. // 0) end' 2>/dev/null || echo "0")
330
-
331
- if [[ -n "$role_score" && "$role_score" != "null" && "$role_score" != "" ]]; then
332
- if awk -v w="$weight" -v b="$best_score" 'BEGIN{exit !(w > b)}' 2>/dev/null; then
333
- best_role="$role_score"
334
- best_score="$weight"
335
- fi
336
- fi
337
- fi
338
- done < <(echo "$learned_weights" | jq -r 'keys[]' 2>/dev/null || true)
339
-
340
- if [[ -n "$best_role" ]]; then
341
- detected_skills="$best_role"
342
- fi
343
- fi
344
- fi
345
-
346
- # Default to builder if no match
347
- if [[ -z "$detected_skills" ]]; then
348
- detected_skills="builder"
349
- fi
350
-
351
- echo "$detected_skills"
352
- }
353
-
354
- # LLM-powered semantic matching
355
- _recruit_llm_match() {
356
- local task_description="$1"
357
- local available_roles="$2"
358
-
359
- local prompt
360
- prompt="You are an agent recruitment system. Given a task description, select the best role(s) from the available roles.
361
-
362
- Task: ${task_description}
363
-
364
- Available roles (JSON):
365
- ${available_roles}
366
-
367
- Return ONLY a JSON object with:
368
- {\"primary_role\": \"<role_key>\", \"secondary_roles\": [\"<role_key>\", ...], \"confidence\": <0.0-1.0>, \"reasoning\": \"<one line>\", \"new_role_needed\": false, \"suggested_role\": null}
369
-
370
- If NO existing role is a good fit, set new_role_needed=true and provide:
371
- {\"primary_role\": \"builder\", \"secondary_roles\": [], \"confidence\": 0.3, \"reasoning\": \"...\", \"new_role_needed\": true, \"suggested_role\": {\"key\": \"<kebab-case>\", \"title\": \"<Title>\", \"description\": \"<desc>\", \"required_skills\": [\"<skill>\"], \"recommended_model\": \"sonnet\", \"context_needs\": [\"<need>\"], \"success_metrics\": [\"<metric>\"], \"estimated_cost_per_task_usd\": 1.5}}
372
-
373
- Return JSON only, no markdown fences."
374
-
375
- local result
376
- result=$(_recruit_call_claude "$prompt")
377
-
378
- if [[ -n "$result" ]] && echo "$result" | jq -e '.primary_role' >/dev/null 2>&1; then
379
- echo "$result"
380
- return 0
381
- fi
382
-
383
- echo ""
384
- }
385
-
386
- # Record a match for learning
387
- # Returns the match_id (epoch-based) so callers can pass it downstream for outcome linking
388
- _recruit_record_match() {
389
- local task="$1"
390
- local role="$2"
391
- local method="$3"
392
- local confidence="${4:-0.5}"
393
- local agent_id="${5:-}"
394
-
395
- mkdir -p "$RECRUIT_ROOT"
396
- local match_epoch
397
- match_epoch=$(now_epoch)
398
- local match_id="match-${match_epoch}-$$"
399
-
400
- local record
401
- record=$(jq -c -n \
402
- --arg ts "$(now_iso)" \
403
- --argjson epoch "$match_epoch" \
404
- --arg match_id "$match_id" \
405
- --arg task "$task" \
406
- --arg role "$role" \
407
- --arg method "$method" \
408
- --argjson conf "$confidence" \
409
- --arg agent "$agent_id" \
410
- '{ts: $ts, ts_epoch: $epoch, match_id: $match_id, task: $task, role: $role, method: $method, confidence: $conf, agent_id: $agent, outcome: null}')
411
- echo "$record" >> "$MATCH_HISTORY"
412
-
413
- # Enforce max match history size (from policy)
414
- local max_history="${RECRUIT_MAX_MATCH_HISTORY:-5000}"
415
- local current_lines
416
- current_lines=$(wc -l < "$MATCH_HISTORY" 2>/dev/null | tr -d ' ')
417
- if [[ "$current_lines" -gt "$max_history" ]]; then
418
- local tmp_trunc
419
- tmp_trunc=$(mktemp)
420
- trap "rm -f '$tmp_trunc'" RETURN
421
- tail -n "$max_history" "$MATCH_HISTORY" > "$tmp_trunc" && _recruit_locked_write "$MATCH_HISTORY" "$tmp_trunc" || rm -f "$tmp_trunc"
422
- fi
423
-
424
- # Update role usage stats
425
- _recruit_track_role_usage "$role" "match"
426
-
427
- # Store match_id in global for callers (avoids stdout contamination)
428
- LAST_MATCH_ID="$match_id"
429
- }
430
-
431
- # ═══════════════════════════════════════════════════════════════════════════════
432
- # DYNAMIC ROLE CREATION (Tier 1)
433
- # ═══════════════════════════════════════════════════════════════════════════════
434
-
435
- cmd_create_role() {
436
- local role_key="${1:-}"
437
- local role_title="${2:-}"
438
- local role_desc="${3:-}"
439
-
440
- if [[ -z "$role_key" ]]; then
441
- error "Usage: shipwright recruit create-role <key> [title] [description]"
442
- echo " Or use: shipwright recruit create-role --auto \"<task description>\""
443
- exit 1
444
- fi
445
-
446
- ensure_recruit_dir
447
- initialize_builtin_roles
448
-
449
- # Auto-generate via LLM if --auto flag
450
- if [[ "$role_key" == "--auto" ]]; then
451
- local task_desc="${role_title:-$role_desc}"
452
- if [[ -z "$task_desc" ]]; then
453
- error "Usage: shipwright recruit create-role --auto \"<task description>\""
454
- exit 1
455
- fi
456
-
457
- info "Generating role definition via AI for: ${CYAN}${task_desc}${RESET}"
458
-
459
- local existing_roles
460
- existing_roles=$(jq -r 'keys | join(", ")' "$ROLES_DB" 2>/dev/null || echo "none")
461
-
462
- local prompt
463
- prompt="Create a new agent role definition for a task that doesn't fit existing roles.
464
-
465
- Task description: ${task_desc}
466
- Existing roles: ${existing_roles}
467
-
468
- Return ONLY a JSON object:
469
- {\"key\": \"<kebab-case-unique-key>\", \"title\": \"<Title>\", \"description\": \"<description>\", \"required_skills\": [\"<skill1>\", \"<skill2>\", \"<skill3>\"], \"recommended_model\": \"sonnet\", \"context_needs\": [\"<need1>\", \"<need2>\"], \"success_metrics\": [\"<metric1>\", \"<metric2>\"], \"estimated_cost_per_task_usd\": 1.5}
470
-
471
- Return JSON only."
472
-
473
- local result
474
- result=$(_recruit_call_claude "$prompt")
475
-
476
- if [[ -n "$result" ]] && echo "$result" | jq -e '.key' >/dev/null 2>&1; then
477
- role_key=$(echo "$result" | jq -r '.key')
478
- role_title=$(echo "$result" | jq -r '.title')
479
- role_desc=$(echo "$result" | jq -r '.description')
480
-
481
- # Add origin and timestamp
482
- result=$(echo "$result" | jq --arg ts "$(now_iso)" '. + {origin: "ai-generated", created_at: $ts}')
483
-
484
- # Persist to roles DB
485
- local tmp_file
486
- tmp_file=$(mktemp)
487
- trap "rm -f '$tmp_file'" RETURN
488
- if jq --arg key "$role_key" --argjson role "$(echo "$result" | jq 'del(.key)')" '.[$key] = $role' "$ROLES_DB" > "$tmp_file"; then
489
- _recruit_locked_write "$ROLES_DB" "$tmp_file"
490
- else
491
- rm -f "$tmp_file"
492
- error "Failed to save role to database"
493
- return 1
494
- fi
495
-
496
- # Log the invention
497
- echo "$result" | jq -c --arg trigger "$task_desc" '. + {trigger: $trigger}' >> "$INVENTED_ROLES_LOG" 2>/dev/null || true
498
-
499
- success "Created AI-generated role: ${CYAN}${role_key}${RESET} — ${role_title}"
500
- echo " ${role_desc}"
501
- emit_event "recruit_role_created" "role=${role_key}" "method=ai" "title=${role_title}"
502
- return 0
503
- else
504
- warn "AI generation failed, falling back to manual creation"
505
- fi
506
-
507
- # Generate a slug from the task description for the fallback key
508
- role_key="custom-$(echo "$task_desc" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//;s/-$//' | cut -c1-50)"
509
- role_title="$task_desc"
510
- role_desc="Auto-created role for: ${task_desc}"
511
- fi
512
-
513
- # Manual role creation
514
- if [[ -z "$role_title" ]]; then
515
- role_title="$role_key"
516
- fi
517
- if [[ -z "$role_desc" ]]; then
518
- role_desc="Custom role: ${role_title}"
519
- fi
520
-
521
- local role_json
522
- role_json=$(jq -n \
523
- --arg title "$role_title" \
524
- --arg desc "$role_desc" \
525
- --arg ts "$(now_iso)" \
526
- '{
527
- title: $title,
528
- description: $desc,
529
- required_skills: ["general"],
530
- recommended_model: "sonnet",
531
- context_needs: ["codebase-structure"],
532
- success_metrics: ["task-completion"],
533
- estimated_cost_per_task_usd: 1.5,
534
- origin: "manual",
535
- created_at: $ts
536
- }')
537
-
538
- local tmp_file
539
- tmp_file=$(mktemp)
540
- trap "rm -f '$tmp_file'" RETURN
541
- if jq --arg key "$role_key" --argjson role "$role_json" '.[$key] = $role' "$ROLES_DB" > "$tmp_file"; then
542
- _recruit_locked_write "$ROLES_DB" "$tmp_file"
543
- else
544
- rm -f "$tmp_file"
545
- error "Failed to save role to database"
546
- return 1
547
- fi
548
-
549
- success "Created role: ${CYAN}${role_key}${RESET} — ${role_title}"
550
- emit_event "recruit_role_created" "role=${role_key}" "method=manual" "title=${role_title}"
551
- }
552
-
553
- # ═══════════════════════════════════════════════════════════════════════════════
554
- # CLOSED-LOOP FEEDBACK INTEGRATION (Tier 1)
555
- # ═══════════════════════════════════════════════════════════════════════════════
556
-
557
- # Record task outcome for an agent — called after pipeline completes
558
- cmd_record_outcome() {
559
- local agent_id="${1:-}"
560
- local task_id="${2:-}"
561
- local outcome="${3:-}"
562
- local quality="${4:-}"
563
- local duration_min="${5:-}"
564
-
565
- if [[ -z "$agent_id" || -z "$outcome" ]]; then
566
- error "Usage: shipwright recruit record-outcome <agent-id> <task-id> <success|failure> [quality:0-10] [duration_min]"
567
- exit 1
568
- fi
569
-
570
- ensure_recruit_dir
571
-
572
- # Get or create profile
573
- local profile
574
- profile=$(jq ".\"${agent_id}\" // {}" "$PROFILES_DB" 2>/dev/null || echo "{}")
575
-
576
- local tasks_completed success_count total_time total_quality
577
- tasks_completed=$(echo "$profile" | jq -r '.tasks_completed // 0')
578
- success_count=$(echo "$profile" | jq -r '.success_count // 0')
579
- total_time=$(echo "$profile" | jq -r '.total_time_minutes // 0')
580
- total_quality=$(echo "$profile" | jq -r '.total_quality // 0')
581
- local current_model
582
- current_model=$(echo "$profile" | jq -r '.model // "sonnet"')
583
-
584
- tasks_completed=$((tasks_completed + 1))
585
- [[ "$outcome" == "success" ]] && success_count=$((success_count + 1))
586
-
587
- if [[ -n "$duration_min" && "$duration_min" != "0" ]]; then
588
- total_time=$(awk -v t="$total_time" -v d="$duration_min" 'BEGIN{printf "%.1f", t + d}')
589
- fi
590
- if [[ -n "$quality" && "$quality" != "0" ]]; then
591
- total_quality=$(awk -v tq="$total_quality" -v q="$quality" 'BEGIN{printf "%.1f", tq + q}')
592
- fi
593
-
594
- local success_rate avg_time avg_quality cost_efficiency
595
- success_rate=$(awk -v s="$success_count" -v t="$tasks_completed" 'BEGIN{if(t>0) printf "%.1f", (s/t)*100; else print "0"}')
596
- avg_time=$(awk -v t="$total_time" -v n="$tasks_completed" 'BEGIN{if(n>0) printf "%.1f", t/n; else print "0"}')
597
- avg_quality=$(awk -v tq="$total_quality" -v n="$tasks_completed" 'BEGIN{if(n>0) printf "%.1f", tq/n; else print "0"}')
598
- cost_efficiency=$(awk -v sr="$success_rate" 'BEGIN{printf "%.0f", sr * 0.9}')
599
-
600
- # Build updated profile with specialization tracking
601
- local role_assigned
602
- role_assigned=$(echo "$profile" | jq -r '.role // "builder"')
603
-
604
- local task_history
605
- task_history=$(echo "$profile" | jq -r '.task_history // []')
606
-
607
- # Append to task history (keep last 50)
608
- local new_entry
609
- new_entry=$(jq -c -n \
610
- --arg ts "$(now_iso)" \
611
- --arg task "$task_id" \
612
- --arg outcome "$outcome" \
613
- --argjson quality "${quality:-0}" \
614
- --argjson duration "${duration_min:-0}" \
615
- '{ts: $ts, task: $task, outcome: $outcome, quality: $quality, duration: $duration}')
616
-
617
- local tmp_file
618
- tmp_file=$(mktemp)
619
- trap "rm -f '$tmp_file'" RETURN
620
- jq --arg id "$agent_id" \
621
- --argjson tc "$tasks_completed" \
622
- --argjson sc "$success_count" \
623
- --argjson sr "$success_rate" \
624
- --argjson at "$avg_time" \
625
- --argjson aq "$avg_quality" \
626
- --argjson ce "$cost_efficiency" \
627
- --argjson tt "$total_time" \
628
- --argjson tq "$total_quality" \
629
- --arg model "$current_model" \
630
- --arg role "$role_assigned" \
631
- --argjson entry "$new_entry" \
632
- '.[$id] = {
633
- tasks_completed: $tc,
634
- success_count: $sc,
635
- success_rate: $sr,
636
- avg_time_minutes: $at,
637
- quality_score: $aq,
638
- cost_efficiency: $ce,
639
- total_time_minutes: $tt,
640
- total_quality: $tq,
641
- model: $model,
642
- role: $role,
643
- task_history: ((.[$id].task_history // []) + [$entry] | .[-50:]),
644
- last_updated: (now | todate)
645
- }' "$PROFILES_DB" > "$tmp_file" && _recruit_locked_write "$PROFILES_DB" "$tmp_file" || { rm -f "$tmp_file"; error "Failed to update profile"; return 1; }
646
-
647
- success "Recorded ${outcome} for ${CYAN}${agent_id}${RESET} (${tasks_completed} tasks, ${success_rate}% success)"
648
- emit_event "recruit_outcome" "agent_id=${agent_id}" "outcome=${outcome}" "success_rate=${success_rate}"
649
-
650
- # Track role usage with outcome (closes the role-usage feedback loop)
651
- _recruit_track_role_usage "$role_assigned" "$outcome"
652
-
653
- # Backfill match history with outcome (closes the match→outcome linkage gap)
654
- if [[ -f "$MATCH_HISTORY" ]]; then
655
- local tmp_mh
656
- tmp_mh=$(mktemp)
657
- trap "rm -f '$tmp_mh'" RETURN
658
- # Find the most recent match for this agent_id with null outcome, and backfill
659
- awk -v agent="$agent_id" -v outcome="$outcome" '
660
- BEGIN { found = 0 }
661
- { lines[NR] = $0; count = NR }
662
- END {
663
- # Walk backwards to find the last unresolved match for this agent
664
- for (i = count; i >= 1; i--) {
665
- if (!found && index(lines[i], "\"agent_id\":\"" agent "\"") > 0 && index(lines[i], "\"outcome\":null") > 0) {
666
- gsub(/"outcome":null/, "\"outcome\":\"" outcome "\"", lines[i])
667
- found = 1
668
- }
669
- }
670
- for (i = 1; i <= count; i++) print lines[i]
671
- }' "$MATCH_HISTORY" > "$tmp_mh" && _recruit_locked_write "$MATCH_HISTORY" "$tmp_mh" || rm -f "$tmp_mh"
672
- fi
673
-
674
- # Trigger meta-learning check (warn on failure instead of silencing)
675
- if ! _recruit_meta_learning_check "$agent_id" "$outcome" 2>&1; then
676
- warn "Meta-learning check failed for ${agent_id} (non-fatal)" >&2
677
- fi
678
- }
679
-
680
- # Ingest outcomes from pipeline events.jsonl automatically
681
- cmd_ingest_pipeline() {
682
- local days="${1:-7}"
683
-
684
- ensure_recruit_dir
685
- info "Ingesting pipeline outcomes from last ${days} days..."
686
-
687
- if [[ ! -f "$EVENTS_FILE" ]]; then
688
- warn "No events file found"
689
- return 0
690
- fi
691
-
692
- local now_e
693
- now_e=$(now_epoch)
694
- local cutoff=$((now_e - days * 86400))
695
- local ingested=0
696
-
697
- while IFS= read -r line; do
698
- local event_type ts_epoch result agent_id duration
699
- event_type=$(echo "$line" | jq -r '.type // ""' 2>/dev/null) || continue
700
- ts_epoch=$(echo "$line" | jq -r '.ts_epoch // 0' 2>/dev/null) || continue
701
-
702
- [[ "$ts_epoch" -lt "$cutoff" ]] && continue
703
-
704
- case "$event_type" in
705
- pipeline.completed)
706
- result=$(echo "$line" | jq -r '.result // "unknown"' 2>/dev/null || echo "unknown")
707
- agent_id=$(echo "$line" | jq -r '.agent_id // "default-agent"' 2>/dev/null || echo "default-agent")
708
- duration=$(echo "$line" | jq -r '.duration_s // 0' 2>/dev/null || echo "0")
709
- local dur_min
710
- dur_min=$(awk -v d="$duration" 'BEGIN{printf "%.1f", d/60}')
711
-
712
- local outcome="failure"
713
- [[ "$result" == "success" ]] && outcome="success"
714
-
715
- cmd_record_outcome "$agent_id" "pipeline-$(echo "$line" | jq -r '.ts_epoch // 0')" "$outcome" "5" "$dur_min" 2>/dev/null || true
716
- ingested=$((ingested + 1))
717
- ;;
718
- esac
719
- done < "$EVENTS_FILE"
720
-
721
- success "Ingested ${ingested} pipeline outcomes"
722
- emit_event "recruit_ingest" "count=${ingested}" "days=${days}"
723
-
724
- # Auto-trigger self-tune when new outcomes are ingested (closes the learning loop)
725
- if [[ "$ingested" -gt 0 ]]; then
726
- info "Auto-running self-tune after ingesting ${ingested} outcomes..."
727
- cmd_self_tune 2>/dev/null || warn "Auto self-tune failed (non-fatal)" >&2
728
-
729
- # Auto-trigger evolve when enough outcomes accumulate (policy-driven)
730
- local total_outcomes
731
- total_outcomes=$(jq -r '[.[] | .tasks_completed // 0] | add // 0' "$PROFILES_DB" 2>/dev/null || echo "0")
732
- local evolve_threshold="${RECRUIT_AUTO_EVOLVE_AFTER:-20}"
733
- if [[ "$total_outcomes" -ge "$evolve_threshold" ]]; then
734
- info "Auto-running evolve (${total_outcomes} total outcomes >= ${evolve_threshold} threshold)..."
735
- cmd_evolve 2>/dev/null || warn "Auto evolve failed (non-fatal)" >&2
736
- fi
737
- fi
738
- }
739
-
740
- # ═══════════════════════════════════════════════════════════════════════════════
741
- # ROLE USAGE TRACKING & EVOLUTION (Tier 2)
742
- # ═══════════════════════════════════════════════════════════════════════════════
743
-
744
- _recruit_track_role_usage() {
745
- local role="$1"
746
- local event="${2:-match}"
747
-
748
- [[ ! -f "$ROLE_USAGE_DB" ]] && echo '{}' > "$ROLE_USAGE_DB"
749
-
750
- local tmp_file
751
- tmp_file=$(mktemp)
752
- trap "rm -f '$tmp_file'" RETURN
753
- jq --arg role "$role" --arg event "$event" --arg ts "$(now_iso)" '
754
- .[$role] = (.[$role] // {matches: 0, successes: 0, failures: 0, last_used: ""}) |
755
- .[$role].last_used = $ts |
756
- if $event == "match" then .[$role].matches += 1
757
- elif $event == "success" then .[$role].successes += 1
758
- elif $event == "failure" then .[$role].failures += 1
759
- else . end
760
- ' "$ROLE_USAGE_DB" > "$tmp_file" && _recruit_locked_write "$ROLE_USAGE_DB" "$tmp_file" || rm -f "$tmp_file"
761
- }
762
-
763
- # Analyze role usage and suggest evolution (splits, merges, retirements)
764
- cmd_evolve() {
765
- ensure_recruit_dir
766
- initialize_builtin_roles
767
-
768
- info "Analyzing role evolution opportunities..."
769
- echo ""
770
-
771
- if [[ ! -f "$ROLE_USAGE_DB" || "$(jq 'length' "$ROLE_USAGE_DB" 2>/dev/null || echo 0)" -eq 0 ]]; then
772
- warn "Not enough usage data for evolution analysis"
773
- echo " Run more pipelines and use 'shipwright recruit ingest-pipeline' first"
774
- return 0
775
- fi
776
-
777
- local analysis=""
778
-
779
- # Detect underused roles (no matches in 30+ days)
780
- local stale_roles
781
- stale_roles=$(jq -r --argjson cutoff "$(($(now_epoch) - 2592000))" '
782
- to_entries[] | select(
783
- (.value.last_used == "") or
784
- (.value.matches == 0) or
785
- ((.value.last_used | sub("\\.[0-9]+Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) < $cutoff)
786
- ) | .key
787
- ' "$ROLE_USAGE_DB" 2>/dev/null || true)
788
-
789
- if [[ -n "$stale_roles" ]]; then
790
- echo -e " ${YELLOW}${BOLD}Underused Roles (candidates for retirement):${RESET}"
791
- while IFS= read -r role; do
792
- [[ -z "$role" ]] && continue
793
- local matches
794
- matches=$(jq -r --arg r "$role" '.[$r].matches // 0' "$ROLE_USAGE_DB" 2>/dev/null || echo "0")
795
- echo -e " ${DIM}•${RESET} ${role} (${matches} total matches)"
796
- analysis="${analysis}retire:${role},"
797
- done <<< "$stale_roles"
798
- echo ""
799
- fi
800
-
801
- # Detect high-failure roles (>40% failure rate with 5+ tasks)
802
- local struggling_roles
803
- struggling_roles=$(jq -r '
804
- to_entries[] | select(
805
- (.value.matches >= 5) and
806
- ((.value.failures / .value.matches) > 0.4)
807
- ) | "\(.key):\(.value.failures)/\(.value.matches)"
808
- ' "$ROLE_USAGE_DB" 2>/dev/null || true)
809
-
810
- if [[ -n "$struggling_roles" ]]; then
811
- echo -e " ${RED}${BOLD}Struggling Roles (need specialization or split):${RESET}"
812
- while IFS= read -r entry; do
813
- [[ -z "$entry" ]] && continue
814
- local role="${entry%%:*}"
815
- local ratio="${entry#*:}"
816
- echo -e " ${DIM}•${RESET} ${role} — ${ratio} failures"
817
- analysis="${analysis}split:${role},"
818
- done <<< "$struggling_roles"
819
- echo ""
820
- fi
821
-
822
- # Detect overloaded roles (>60% of all matches go to one role)
823
- local total_matches
824
- total_matches=$(jq '[.[].matches] | add // 0' "$ROLE_USAGE_DB" 2>/dev/null || echo "0")
825
-
826
- if [[ "$total_matches" -gt 10 ]]; then
827
- local overloaded_roles
828
- overloaded_roles=$(jq -r --argjson total "$total_matches" '
829
- to_entries[] | select((.value.matches / $total) > 0.6) |
830
- "\(.key):\(.value.matches)"
831
- ' "$ROLE_USAGE_DB" 2>/dev/null || true)
832
-
833
- if [[ -n "$overloaded_roles" ]]; then
834
- echo -e " ${PURPLE}${BOLD}Overloaded Roles (candidates for splitting):${RESET}"
835
- while IFS= read -r entry; do
836
- [[ -z "$entry" ]] && continue
837
- local role="${entry%%:*}"
838
- local count="${entry#*:}"
839
- echo -e " ${DIM}•${RESET} ${role} — ${count}/${total_matches} matches ($(awk -v c="$count" -v t="$total_matches" 'BEGIN{printf "%.0f", (c/t)*100}')%)"
840
- done <<< "$overloaded_roles"
841
- echo ""
842
- fi
843
- fi
844
-
845
- # LLM-powered evolution suggestions
846
- if [[ -n "$analysis" ]] && _recruit_has_claude; then
847
- info "Generating AI evolution recommendations..."
848
- local roles_summary
849
- roles_summary=$(jq -c '.' "$ROLE_USAGE_DB" 2>/dev/null || echo "{}")
850
-
851
- local prompt
852
- prompt="Analyze agent role usage data and suggest evolution:
853
-
854
- Usage data: ${roles_summary}
855
- Analysis flags: ${analysis}
856
-
857
- Suggest specific actions:
858
- 1. Which roles to retire (unused)
859
- 2. Which roles to split into specializations (high failure or overloaded)
860
- 3. Which roles to merge (overlapping low-use roles)
861
- 4. New hybrid roles to create
862
-
863
- Return a brief text summary (3-5 bullet points). Be specific with role names."
864
-
865
- local suggestions
866
- suggestions=$(_recruit_call_claude "$prompt")
867
- if [[ -n "$suggestions" ]]; then
868
- echo -e " ${CYAN}${BOLD}AI Evolution Recommendations:${RESET}"
869
- echo "$suggestions" | sed 's/^/ /'
870
- fi
871
- fi
872
-
873
- emit_event "recruit_evolve" "analysis=${analysis:0:100}"
874
- }
875
-
876
- # ═══════════════════════════════════════════════════════════════════════════════
877
- # SELF-TUNING THRESHOLDS (Tier 2)
878
- # ═══════════════════════════════════════════════════════════════════════════════
879
-
880
- _recruit_compute_population_stats() {
881
- if [[ ! -f "$PROFILES_DB" || "$(jq 'length' "$PROFILES_DB" 2>/dev/null || echo 0)" -lt 2 ]]; then
882
- echo '{"mean_success":0,"stddev_success":0,"p90_success":0,"p10_success":0,"count":0}'
883
- return
884
- fi
885
-
886
- jq '
887
- [.[].success_rate] as $rates |
888
- ($rates | length) as $n |
889
- ($rates | add / $n) as $mean |
890
- ($rates | map(. - $mean | . * .) | add / $n | sqrt) as $stddev |
891
- ($rates | sort) as $sorted |
892
- {
893
- mean_success: ($mean * 10 | floor / 10),
894
- stddev_success: ($stddev * 10 | floor / 10),
895
- p90_success: ($sorted[($n * 0.9 | floor)] // 0),
896
- p10_success: ($sorted[($n * 0.1 | floor)] // 0),
897
- count: $n
898
- }
899
- ' "$PROFILES_DB" 2>/dev/null || echo '{"mean_success":0,"stddev_success":0,"p90_success":0,"p10_success":0,"count":0}'
900
- }
901
-
902
- # ═══════════════════════════════════════════════════════════════════════════════
903
- # CROSS-AGENT LEARNING (Tier 2)
904
- # ═══════════════════════════════════════════════════════════════════════════════
905
-
906
- # Track which agents excel at which task types
907
- cmd_specializations() {
908
- ensure_recruit_dir
909
-
910
- info "Agent Specialization Analysis:"
911
- echo ""
912
-
913
- if [[ ! -f "$PROFILES_DB" || "$(jq 'length' "$PROFILES_DB" 2>/dev/null || echo 0)" -eq 0 ]]; then
914
- warn "No agent profiles to analyze"
915
- return 0
916
- fi
917
-
918
- # Analyze per-agent task history for patterns
919
- jq -r 'to_entries[] |
920
- .key as $agent |
921
- .value |
922
- " \($agent):" +
923
- "\n Role: \(.role // "unassigned")" +
924
- "\n Success: \(.success_rate // 0)% over \(.tasks_completed // 0) tasks" +
925
- "\n Model: \(.model // "unknown")" +
926
- "\n Strength: " + (
927
- if (.success_rate // 0) >= 90 then "excellent"
928
- elif (.success_rate // 0) >= 75 then "good"
929
- elif (.success_rate // 0) >= 60 then "developing"
930
- else "needs improvement"
931
- end
932
- ) + "\n"
933
- ' "$PROFILES_DB" 2>/dev/null || warn "Could not analyze specializations"
934
-
935
- # Suggest smart routing
936
- local pop_stats
937
- pop_stats=$(_recruit_compute_population_stats)
938
- local mean_success
939
- mean_success=$(echo "$pop_stats" | jq -r '.mean_success')
940
- local agent_count
941
- agent_count=$(echo "$pop_stats" | jq -r '.count')
942
-
943
- if [[ "$agent_count" -gt 0 ]]; then
944
- echo ""
945
- echo -e " ${BOLD}Population Statistics:${RESET}"
946
- echo -e " Mean success rate: ${mean_success}%"
947
- echo -e " Agents tracked: ${agent_count}"
948
- echo -e " P90/P10 spread: $(echo "$pop_stats" | jq -r '.p90_success')% / $(echo "$pop_stats" | jq -r '.p10_success')%"
949
- fi
950
- }
951
-
952
- # Smart routing: given a task, find the best available agent
953
- cmd_route() {
954
- local task_description="${1:-}"
955
-
956
- if [[ -z "$task_description" ]]; then
957
- error "Usage: shipwright recruit route \"<task description>\""
958
- exit 1
959
- fi
960
-
961
- ensure_recruit_dir
962
- initialize_builtin_roles
963
-
964
- info "Smart routing for: ${CYAN}${task_description}${RESET}"
965
- echo ""
966
-
967
- # Step 1: Determine best role
968
- local role_match
969
- role_match=$(_recruit_keyword_match "$task_description")
970
- local primary_role
971
- primary_role=$(echo "$role_match" | awk '{print $1}')
972
-
973
- # Step 2: Find best agent for that role
974
- if [[ -f "$PROFILES_DB" && "$(jq 'length' "$PROFILES_DB" 2>/dev/null || echo 0)" -gt 0 ]]; then
975
- local best_agent
976
- best_agent=$(jq -r --arg role "$primary_role" '
977
- to_entries |
978
- map(select(.value.role == $role and (.value.tasks_completed // 0) >= 3)) |
979
- sort_by(-(.value.success_rate // 0)) |
980
- .[0] // null |
981
- if . then "\(.key) (\(.value.success_rate)% success over \(.value.tasks_completed) tasks)"
982
- else null end
983
- ' "$PROFILES_DB" 2>/dev/null || echo "")
984
-
985
- if [[ -n "$best_agent" && "$best_agent" != "null" ]]; then
986
- success "Best agent: ${CYAN}${best_agent}${RESET}"
987
- else
988
- info "No experienced agent for ${primary_role} role — assign any available agent"
989
- fi
990
- fi
991
-
992
- # Step 3: Get recommended model
993
- local recommended_model
994
- recommended_model=$(jq -r --arg role "$primary_role" '.[$role].recommended_model // "sonnet"' "$ROLES_DB" 2>/dev/null || echo "sonnet")
995
-
996
- echo " Role: ${primary_role}"
997
- echo " Model: ${recommended_model}"
998
- }
999
-
1000
- # ═══════════════════════════════════════════════════════════════════════════════
1001
- # CONTEXT-AWARE TEAM COMPOSITION (Tier 2)
1002
- # ═══════════════════════════════════════════════════════════════════════════════
1003
-
1004
- cmd_team() {
1005
- local json_mode=false
1006
- if [[ "${1:-}" == "--json" ]]; then
1007
- json_mode=true
1008
- shift
1009
- fi
1010
- local issue_or_project="${1:-}"
1011
-
1012
- if [[ -z "$issue_or_project" ]]; then
1013
- error "Usage: shipwright recruit team [--json] <issue|project>"
1014
- exit 1
1015
- fi
1016
-
1017
- ensure_recruit_dir
1018
- initialize_builtin_roles
1019
-
1020
- if ! $json_mode; then
1021
- info "Recommending team composition for: ${CYAN}${issue_or_project}${RESET}"
1022
- echo ""
1023
- fi
1024
-
1025
- local recommended_team=()
1026
- local team_method="heuristic"
1027
-
1028
- # Try LLM-powered team composition first
1029
- if _recruit_has_claude; then
1030
- local available_roles
1031
- 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 "[]")
1032
-
1033
- # Gather codebase context if in a git repo
1034
- local codebase_context=""
1035
- if command -v git >/dev/null 2>&1 && git rev-parse --git-dir >/dev/null 2>&1; then
1036
- local file_count lang_summary
1037
- file_count=$(git ls-files 2>/dev/null | wc -l | tr -d ' ')
1038
- lang_summary=$(git ls-files 2>/dev/null | grep -oE '\.[^.]+$' | sort | uniq -c | sort -rn | head -5 | tr '\n' ';' || echo "unknown")
1039
- codebase_context="Files: ${file_count}, Languages: ${lang_summary}"
1040
- fi
1041
-
1042
- local prompt
1043
- prompt="You are a team composition optimizer. Given a task and available roles, recommend the optimal team.
1044
-
1045
- Task/Issue: ${issue_or_project}
1046
- Codebase context: ${codebase_context:-unknown}
1047
- Available roles: ${available_roles}
1048
-
1049
- Consider:
1050
- - Task complexity (simple tasks need fewer roles)
1051
- - Risk areas (security-sensitive = add security-auditor)
1052
- - Cost efficiency (minimize cost while covering all needs)
1053
-
1054
- Return ONLY a JSON object:
1055
- {\"team\": [\"<role_key>\", ...], \"reasoning\": \"<brief explanation>\", \"estimated_cost\": <total_usd>, \"risk_level\": \"low|medium|high\"}
1056
-
1057
- Return JSON only."
1058
-
1059
- local result
1060
- result=$(_recruit_call_claude "$prompt")
1061
-
1062
- if [[ -n "$result" ]] && echo "$result" | jq -e '.team' >/dev/null 2>&1; then
1063
- while IFS= read -r role; do
1064
- [[ -z "$role" || "$role" == "null" ]] && continue
1065
- recommended_team+=("$role")
1066
- done < <(echo "$result" | jq -r '.team[]' 2>/dev/null)
1067
-
1068
- team_method="ai"
1069
- local reasoning
1070
- reasoning=$(echo "$result" | jq -r '.reasoning // ""')
1071
- local risk_level
1072
- risk_level=$(echo "$result" | jq -r '.risk_level // "medium"')
1073
-
1074
- if [[ -n "$reasoning" ]]; then
1075
- echo -e " ${DIM}AI reasoning: ${reasoning}${RESET}"
1076
- echo -e " ${DIM}Risk level: ${risk_level}${RESET}"
1077
- echo ""
1078
- fi
1079
- fi
1080
- fi
1081
-
1082
- # Fallback: heuristic team composition
1083
- if [[ ${#recommended_team[@]} -eq 0 ]]; then
1084
- recommended_team=("builder" "reviewer" "tester")
1085
-
1086
- if echo "$issue_or_project" | grep -qiE "security|vulnerability|compliance"; then
1087
- recommended_team+=("security-auditor")
1088
- fi
1089
- if echo "$issue_or_project" | grep -qiE "architecture|design|refactor"; then
1090
- recommended_team+=("architect")
1091
- fi
1092
- if echo "$issue_or_project" | grep -qiE "deploy|infra|ci.cd|pipeline"; then
1093
- recommended_team+=("devops")
1094
- fi
1095
- if echo "$issue_or_project" | grep -qiE "performance|speed|latency|optimization"; then
1096
- recommended_team+=("optimizer")
1097
- fi
1098
- fi
1099
-
1100
- # Compute total cost and model list
1101
- local total_cost
1102
- total_cost=$(printf "%.2f" "$(
1103
- for role in "${recommended_team[@]}"; do
1104
- jq ".\"${role}\".estimated_cost_per_task_usd // 1.5" "$ROLES_DB" 2>/dev/null || echo "1.5"
1105
- done | awk '{sum+=$1} END {print sum}'
1106
- )")
1107
-
1108
- # Determine primary model (highest-tier model on the team)
1109
- local team_model="sonnet"
1110
- for role in "${recommended_team[@]}"; do
1111
- local rm
1112
- rm=$(jq -r ".\"${role}\".recommended_model // \"sonnet\"" "$ROLES_DB" 2>/dev/null || echo "sonnet")
1113
- if [[ "$rm" == "opus" ]]; then team_model="opus"; break; fi
1114
- done
1115
-
1116
- emit_event "recruit_team" "size=${#recommended_team[@]}" "method=${team_method}" "cost=${total_cost}"
1117
-
1118
- # JSON mode: structured output for programmatic consumption
1119
- if $json_mode; then
1120
- local roles_json
1121
- roles_json=$(printf '%s\n' "${recommended_team[@]}" | jq -R . | jq -s .)
1122
-
1123
- # Derive template and max_iterations from team size/composition (triage needs these)
1124
- local team_template="full"
1125
- local team_max_iterations=10
1126
- local team_size=${#recommended_team[@]}
1127
- if [[ $team_size -le 2 ]]; then
1128
- team_template="quick-fix"
1129
- team_max_iterations=5
1130
- elif [[ $team_size -ge 5 ]]; then
1131
- team_template="careful"
1132
- team_max_iterations=20
1133
- fi
1134
- # Security tasks get more iterations
1135
- if printf '%s\n' "${recommended_team[@]}" | grep -q "security-auditor"; then
1136
- team_template="careful"
1137
- [[ $team_max_iterations -lt 15 ]] && team_max_iterations=15
1138
- fi
1139
-
1140
- jq -c -n \
1141
- --argjson team "$roles_json" \
1142
- --arg method "$team_method" \
1143
- --argjson cost "$total_cost" \
1144
- --arg model "$team_model" \
1145
- --argjson agents "$team_size" \
1146
- --arg template "$team_template" \
1147
- --argjson max_iterations "$team_max_iterations" \
1148
- '{
1149
- team: $team,
1150
- method: $method,
1151
- estimated_cost: $cost,
1152
- model: $model,
1153
- agents: $agents,
1154
- template: $template,
1155
- max_iterations: $max_iterations
1156
- }'
1157
- return 0
1158
- fi
1159
-
1160
- success "Recommended Team (${#recommended_team[@]} members, via ${team_method}):"
1161
- echo ""
1162
-
1163
- for role in "${recommended_team[@]}"; do
1164
- local role_info
1165
- role_info=$(jq ".\"${role}\"" "$ROLES_DB" 2>/dev/null || echo "null")
1166
- if [[ "$role_info" != "null" ]]; then
1167
- printf " • ${CYAN}%-20s${RESET} (${PURPLE}%s${RESET}) — %s\n" \
1168
- "$role" \
1169
- "$(echo "$role_info" | jq -r '.recommended_model')" \
1170
- "$(echo "$role_info" | jq -r '.title')"
1171
- else
1172
- printf " • ${CYAN}%-20s${RESET} (${PURPLE}%s${RESET}) — %s\n" \
1173
- "$role" "sonnet" "Custom role"
1174
- fi
1175
- done
1176
-
1177
- echo ""
1178
- echo "Estimated Team Cost: \$${total_cost}/task"
1179
- }
1180
-
1181
- # ═══════════════════════════════════════════════════════════════════════════════
1182
- # META-LEARNING: REFLECT ON MATCHING ACCURACY (Tier 3)
1183
- # ═══════════════════════════════════════════════════════════════════════════════
1184
-
1185
- _recruit_meta_learning_check() {
1186
- local agent_id="${1:-}"
1187
- local outcome="${2:-}"
1188
-
1189
- [[ ! -f "$MATCH_HISTORY" ]] && return 0
1190
- [[ ! -f "$META_LEARNING_DB" ]] && return 0
1191
-
1192
- # Find most recent match for this agent (by agent_id if set, else last match)
1193
- local last_match
1194
- last_match=$(tail -50 "$MATCH_HISTORY" | jq -s -r --arg agent "$agent_id" '
1195
- [.[] | select(.role != null) |
1196
- select(.agent_id == $agent or .agent_id == "" or .agent_id == null)] |
1197
- last // null
1198
- ' 2>/dev/null || echo "")
1199
-
1200
- [[ -z "$last_match" || "$last_match" == "null" ]] && return 0
1201
-
1202
- local matched_role method
1203
- matched_role=$(echo "$last_match" | jq -r '.role // ""')
1204
- method=$(echo "$last_match" | jq -r '.method // "keyword"')
1205
-
1206
- [[ -z "$matched_role" ]] && return 0
1207
-
1208
- # Record correction if failure
1209
- if [[ "$outcome" == "failure" ]]; then
1210
- local correction
1211
- correction=$(jq -c -n \
1212
- --arg ts "$(now_iso)" \
1213
- --arg agent "$agent_id" \
1214
- --arg role "$matched_role" \
1215
- --arg method "$method" \
1216
- --arg outcome "$outcome" \
1217
- '{ts: $ts, agent: $agent, role: $role, method: $method, outcome: $outcome}')
1218
-
1219
- local tmp_file
1220
- tmp_file=$(mktemp)
1221
- trap "rm -f '$tmp_file'" RETURN
1222
- jq --argjson corr "$correction" '
1223
- .corrections = ((.corrections // []) + [$corr] | .[-100:])
1224
- ' "$META_LEARNING_DB" > "$tmp_file" && _recruit_locked_write "$META_LEARNING_DB" "$tmp_file" || rm -f "$tmp_file"
1225
- fi
1226
-
1227
- # Every 20 outcomes, reflect on accuracy
1228
- local total_corrections
1229
- total_corrections=$(jq '.corrections | length' "$META_LEARNING_DB" 2>/dev/null || echo "0")
1230
-
1231
- if [[ "$((total_corrections % 20))" -eq 0 && "$total_corrections" -gt 0 ]]; then
1232
- _recruit_reflect || warn "Auto-reflection failed (non-fatal)" >&2
1233
- fi
1234
- }
1235
-
1236
- # Full meta-learning reflection
1237
- cmd_reflect() {
1238
- ensure_recruit_dir
1239
-
1240
- info "Running meta-learning reflection..."
1241
- echo ""
1242
-
1243
- _recruit_reflect
1244
- }
1245
-
1246
- _recruit_reflect() {
1247
- [[ ! -f "$META_LEARNING_DB" ]] && return 0
1248
- [[ ! -f "$MATCH_HISTORY" ]] && return 0
1249
-
1250
- local total_matches
1251
- total_matches=$(wc -l < "$MATCH_HISTORY" 2>/dev/null | tr -d ' ')
1252
- local total_corrections
1253
- total_corrections=$(jq '.corrections | length' "$META_LEARNING_DB" 2>/dev/null || echo "0")
1254
-
1255
- if [[ "$total_matches" -eq 0 ]]; then
1256
- info "No match history to reflect on"
1257
- return 0
1258
- fi
1259
-
1260
- local accuracy
1261
- accuracy=$(awk -v m="$total_matches" -v c="$total_corrections" 'BEGIN{if(m>0) printf "%.1f", ((m-c)/m)*100; else print "0"}')
1262
-
1263
- echo -e " ${BOLD}Matching Accuracy:${RESET} ${accuracy}% (${total_matches} matches, ${total_corrections} corrections)"
1264
-
1265
- # Track accuracy trend
1266
- local tmp_file
1267
- tmp_file=$(mktemp)
1268
- trap "rm -f '$tmp_file'" RETURN
1269
- jq --argjson acc "$accuracy" --arg ts "$(now_iso)" '
1270
- .accuracy_trend = ((.accuracy_trend // []) + [{accuracy: $acc, ts: $ts}] | .[-50:]) |
1271
- .last_reflection = $ts
1272
- ' "$META_LEARNING_DB" > "$tmp_file" && _recruit_locked_write "$META_LEARNING_DB" "$tmp_file" || rm -f "$tmp_file"
1273
-
1274
- # Identify most-failed role assignments
1275
- local failure_patterns
1276
- failure_patterns=$(jq -r '
1277
- .corrections | group_by(.role) |
1278
- map({role: .[0].role, failures: length}) |
1279
- sort_by(-.failures) | .[:3][] |
1280
- " \(.role): \(.failures) failures"
1281
- ' "$META_LEARNING_DB" 2>/dev/null || true)
1282
-
1283
- if [[ -n "$failure_patterns" ]]; then
1284
- echo ""
1285
- echo -e " ${BOLD}Most Mismatched Roles:${RESET}"
1286
- echo "$failure_patterns"
1287
- fi
1288
-
1289
- # LLM-powered reflection
1290
- if _recruit_has_claude && [[ "$total_corrections" -ge 5 ]]; then
1291
- local corrections_json
1292
- corrections_json=$(jq -c '.corrections[-20:]' "$META_LEARNING_DB" 2>/dev/null || echo "[]")
1293
-
1294
- local prompt
1295
- prompt="Analyze these role matching failures and suggest improvements to the matching heuristics.
1296
-
1297
- Recent failures: ${corrections_json}
1298
- Current accuracy: ${accuracy}%
1299
-
1300
- For each failed pattern, suggest:
1301
- 1. What keyword or pattern should have triggered a different role
1302
- 2. Whether a new role should be created for this type of task
1303
-
1304
- Return a brief text summary (3-5 bullet points). Be specific about which keywords map to which roles."
1305
-
1306
- local suggestions
1307
- suggestions=$(_recruit_call_claude "$prompt")
1308
- if [[ -n "$suggestions" ]]; then
1309
- echo ""
1310
- echo -e " ${CYAN}${BOLD}AI Reflection:${RESET}"
1311
- echo "$suggestions" | sed 's/^/ /'
1312
- fi
1313
- fi
1314
-
1315
- emit_event "recruit_reflect" "accuracy=${accuracy}" "corrections=${total_corrections}"
1316
-
1317
- # Meta-loop: validate self-tune effectiveness by comparing accuracy trend
1318
- _recruit_meta_validate_self_tune "$accuracy"
1319
- }
1320
-
1321
- # Meta feedback loop: checks if self-tune is actually improving accuracy
1322
- # If accuracy drops after self-tune, emits a warning and reverts heuristics
1323
- _recruit_meta_validate_self_tune() {
1324
- local current_accuracy="${1:-0}"
1325
- [[ ! -f "$META_LEARNING_DB" ]] && return 0
1326
- [[ ! -f "$HEURISTICS_DB" ]] && return 0
1327
-
1328
- local accuracy_floor="${RECRUIT_META_ACCURACY_FLOOR:-50}"
1329
-
1330
- # Get accuracy trend (last 10 data points)
1331
- local trend_data
1332
- trend_data=$(jq -r '.accuracy_trend // [] | .[-10:]' "$META_LEARNING_DB" 2>/dev/null) || return 0
1333
-
1334
- local trend_count
1335
- trend_count=$(echo "$trend_data" | jq 'length' 2>/dev/null) || return 0
1336
- [[ "$trend_count" -lt 3 ]] && return 0
1337
-
1338
- # Compute moving average of first half vs second half
1339
- local first_half_avg second_half_avg
1340
- first_half_avg=$(echo "$trend_data" | jq '[.[:length/2 | floor][].accuracy] | add / length' 2>/dev/null) || return 0
1341
- second_half_avg=$(echo "$trend_data" | jq '[.[length/2 | floor:][].accuracy] | add / length' 2>/dev/null) || return 0
1342
-
1343
- local is_declining
1344
- is_declining=$(awk -v f="$first_half_avg" -v s="$second_half_avg" 'BEGIN{print (s < f - 5) ? 1 : 0}')
1345
-
1346
- local is_below_floor
1347
- is_below_floor=$(awk -v c="$current_accuracy" -v f="$accuracy_floor" 'BEGIN{print (c < f) ? 1 : 0}')
1348
-
1349
- if [[ "$is_declining" == "1" ]]; then
1350
- warn "META-LOOP: Accuracy DECLINING after self-tune (${first_half_avg}% -> ${second_half_avg}%)"
1351
-
1352
- if [[ "$is_below_floor" == "1" ]]; then
1353
- warn "META-LOOP: Accuracy ${current_accuracy}% below floor ${accuracy_floor}% — reverting heuristics to defaults"
1354
- # Reset heuristics to empty (forces fallback to keyword_match defaults)
1355
- local tmp_heur
1356
- tmp_heur=$(mktemp)
1357
- trap "rm -f '$tmp_heur'" RETURN
1358
- echo '{"keyword_weights": {}, "meta_reverted_at": "'"$(now_iso)"'", "revert_reason": "accuracy_below_floor"}' > "$tmp_heur"
1359
- _recruit_locked_write "$HEURISTICS_DB" "$tmp_heur" || rm -f "$tmp_heur"
1360
- emit_event "recruit_meta_revert" "accuracy=${current_accuracy}" "floor=${accuracy_floor}" "reason=declining_below_floor"
1361
- else
1362
- emit_event "recruit_meta_warning" "accuracy=${current_accuracy}" "trend=declining" "first_half=${first_half_avg}" "second_half=${second_half_avg}"
1363
- fi
1364
- elif [[ "$is_below_floor" == "1" ]]; then
1365
- warn "META-LOOP: Accuracy ${current_accuracy}% below floor ${accuracy_floor}%"
1366
- emit_event "recruit_meta_warning" "accuracy=${current_accuracy}" "floor=${accuracy_floor}" "trend=low"
1367
- fi
1368
- }
1369
-
1370
- # ═══════════════════════════════════════════════════════════════════════════════
1371
- # AUTONOMOUS ROLE INVENTION (Tier 3)
1372
- # ═══════════════════════════════════════════════════════════════════════════════
1373
-
1374
- cmd_invent() {
1375
- ensure_recruit_dir
1376
- initialize_builtin_roles
1377
-
1378
- info "Scanning for unmatched task patterns to invent new roles..."
1379
- echo ""
1380
-
1381
- if [[ ! -f "$MATCH_HISTORY" ]]; then
1382
- warn "No match history — run more tasks first"
1383
- return 0
1384
- fi
1385
-
1386
- # Find tasks that defaulted to builder (low confidence or no keyword match)
1387
- local unmatched_tasks
1388
- unmatched_tasks=$(jq -s -r '
1389
- [.[] | select(
1390
- (.role == "builder" and (.confidence // 0.5) < 0.6) or
1391
- (.method == "keyword" and (.confidence // 0.5) < 0.4)
1392
- ) | .task] | unique | .[:20][]
1393
- ' "$MATCH_HISTORY" 2>/dev/null || true)
1394
-
1395
- if [[ -z "$unmatched_tasks" ]]; then
1396
- success "No unmatched patterns detected — all tasks well-covered"
1397
- return 0
1398
- fi
1399
-
1400
- local task_count
1401
- task_count=$(echo "$unmatched_tasks" | wc -l | tr -d ' ')
1402
- info "Found ${task_count} poorly-matched tasks"
1403
-
1404
- if ! _recruit_has_claude; then
1405
- warn "Claude not available for role invention. Unmatched tasks:"
1406
- echo "$unmatched_tasks" | sed 's/^/ - /'
1407
- return 0
1408
- fi
1409
-
1410
- local existing_roles
1411
- existing_roles=$(jq -r 'to_entries | map("\(.key): \(.value.description)") | join("\n")' "$ROLES_DB" 2>/dev/null || echo "none")
1412
-
1413
- local prompt
1414
- prompt="Analyze these tasks that weren't well-matched to existing agent roles. Identify recurring patterns and suggest new roles.
1415
-
1416
- Poorly-matched tasks:
1417
- ${unmatched_tasks}
1418
-
1419
- Existing roles:
1420
- ${existing_roles}
1421
-
1422
- If you identify a clear pattern (2+ tasks that share a theme), propose a new role:
1423
- {\"roles\": [{\"key\": \"<kebab-case>\", \"title\": \"<Title>\", \"description\": \"<desc>\", \"required_skills\": [\"<skill>\"], \"trigger_keywords\": [\"<keyword>\"], \"recommended_model\": \"sonnet\", \"estimated_cost_per_task_usd\": 1.5}]}
1424
-
1425
- If no new role is needed, return: {\"roles\": [], \"reasoning\": \"existing roles are sufficient\"}
1426
-
1427
- Return JSON only."
1428
-
1429
- local result
1430
- result=$(_recruit_call_claude "$prompt")
1431
-
1432
- if [[ -n "$result" ]] && echo "$result" | jq -e '.roles | length > 0' >/dev/null 2>&1; then
1433
- local new_count
1434
- new_count=$(echo "$result" | jq '.roles | length')
1435
-
1436
- echo ""
1437
- success "Invented ${new_count} new role(s):"
1438
- echo ""
1439
-
1440
- local i=0
1441
- while [[ "$i" -lt "$new_count" ]]; do
1442
- local role_key role_title role_desc
1443
- role_key=$(echo "$result" | jq -r ".roles[$i].key")
1444
- role_title=$(echo "$result" | jq -r ".roles[$i].title")
1445
- role_desc=$(echo "$result" | jq -r ".roles[$i].description")
1446
-
1447
- echo -e " ${CYAN}${BOLD}${role_key}${RESET}: ${role_title}"
1448
- echo -e " ${DIM}${role_desc}${RESET}"
1449
- echo ""
1450
-
1451
- # Auto-create the role
1452
- local role_json
1453
- role_json=$(echo "$result" | jq ".roles[$i] | del(.key) + {origin: \"invented\", created_at: \"$(now_iso)\"}")
1454
-
1455
- local tmp_file
1456
- tmp_file=$(mktemp)
1457
- trap "rm -f '$tmp_file'" RETURN
1458
- 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"
1459
-
1460
- # Update heuristics with trigger keywords
1461
- local keywords
1462
- keywords=$(echo "$result" | jq -r ".roles[$i].trigger_keywords // [] | .[]" 2>/dev/null || true)
1463
- if [[ -n "$keywords" ]]; then
1464
- local heur_tmp
1465
- heur_tmp=$(mktemp)
1466
- trap "rm -f '$heur_tmp'" RETURN
1467
- while IFS= read -r kw; do
1468
- [[ -z "$kw" ]] && continue
1469
- jq --arg kw "$kw" --arg role "$role_key" \
1470
- '.keyword_weights[$kw] = {role: $role, weight: 10, source: "invented"}' \
1471
- "$HEURISTICS_DB" > "$heur_tmp" && mv "$heur_tmp" "$HEURISTICS_DB" || true
1472
- done <<< "$keywords"
1473
- fi
1474
-
1475
- # Log invention
1476
- echo "$role_json" | jq -c --arg key "$role_key" '. + {key: $key}' >> "$INVENTED_ROLES_LOG" 2>/dev/null || true
1477
-
1478
- emit_event "recruit_role_invented" "role=${role_key}" "title=${role_title}"
1479
- i=$((i + 1))
1480
- done
1481
- else
1482
- local reasoning
1483
- reasoning=$(echo "$result" | jq -r '.reasoning // "no analysis available"' 2>/dev/null || echo "no analysis available")
1484
- info "No new roles needed: ${reasoning}"
1485
- fi
1486
- }
1487
-
1488
- # ═══════════════════════════════════════════════════════════════════════════════
1489
- # THEORY OF MIND: PER-AGENT WORKING STYLE PROFILES (Tier 3)
1490
- # ═══════════════════════════════════════════════════════════════════════════════
1491
-
1492
- cmd_mind() {
1493
- local agent_id="${1:-}"
1494
-
1495
- if [[ -z "$agent_id" ]]; then
1496
- # Show all agent minds
1497
- ensure_recruit_dir
1498
- info "Agent Theory of Mind Profiles:"
1499
- echo ""
1500
-
1501
- if [[ ! -f "$AGENT_MINDS_DB" || "$(jq 'length' "$AGENT_MINDS_DB" 2>/dev/null || echo 0)" -eq 0 ]]; then
1502
- warn "No agent mind profiles yet. Use 'shipwright recruit mind <agent-id>' after recording outcomes."
1503
- return 0
1504
- fi
1505
-
1506
- jq -r 'to_entries[] |
1507
- "\(.key):" +
1508
- "\n Style: \(.value.working_style // "unknown")" +
1509
- "\n Strengths: \(.value.strengths // [] | join(", "))" +
1510
- "\n Weaknesses: \(.value.weaknesses // [] | join(", "))" +
1511
- "\n Best with: \(.value.ideal_task_type // "general")" +
1512
- "\n Onboarding: \(.value.onboarding_preference // "standard")\n"
1513
- ' "$AGENT_MINDS_DB" 2>/dev/null || warn "Could not read mind profiles"
1514
- return 0
1515
- fi
1516
-
1517
- ensure_recruit_dir
1518
-
1519
- info "Building theory of mind for: ${CYAN}${agent_id}${RESET}"
1520
- echo ""
1521
-
1522
- # Gather agent's task history
1523
- local profile
1524
- profile=$(jq ".\"${agent_id}\" // {}" "$PROFILES_DB" 2>/dev/null || echo "{}")
1525
-
1526
- if [[ "$profile" == "{}" ]]; then
1527
- warn "No profile data for ${agent_id}"
1528
- return 1
1529
- fi
1530
-
1531
- local task_history
1532
- task_history=$(echo "$profile" | jq -c '.task_history // []')
1533
- local success_rate
1534
- success_rate=$(echo "$profile" | jq -r '.success_rate // 0')
1535
- local avg_time
1536
- avg_time=$(echo "$profile" | jq -r '.avg_time_minutes // 0')
1537
- local tasks_completed
1538
- tasks_completed=$(echo "$profile" | jq -r '.tasks_completed // 0')
1539
-
1540
- # Heuristic mind model
1541
- local working_style="balanced"
1542
- local strengths=()
1543
- local weaknesses=()
1544
- local ideal_task_type="general"
1545
- local onboarding_pref="standard"
1546
-
1547
- # Analyze speed
1548
- if awk -v t="$avg_time" 'BEGIN{exit !(t < 10)}' 2>/dev/null; then
1549
- working_style="fast-iterative"
1550
- strengths+=("speed")
1551
- onboarding_pref="minimal-context"
1552
- elif awk -v t="$avg_time" 'BEGIN{exit !(t > 30)}' 2>/dev/null; then
1553
- working_style="thorough-methodical"
1554
- strengths+=("thoroughness")
1555
- onboarding_pref="detailed-specs"
1556
- fi
1557
-
1558
- # Analyze success rate
1559
- if awk -v s="$success_rate" 'BEGIN{exit !(s >= 90)}' 2>/dev/null; then
1560
- strengths+=("reliability")
1561
- elif awk -v s="$success_rate" 'BEGIN{exit !(s < 60)}' 2>/dev/null; then
1562
- weaknesses+=("consistency")
1563
- fi
1564
-
1565
- # LLM-powered mind profile
1566
- if _recruit_has_claude && [[ "$tasks_completed" -ge 5 ]]; then
1567
- local prompt
1568
- prompt="Build a psychological profile for an AI agent based on its performance history.
1569
-
1570
- Agent: ${agent_id}
1571
- Tasks completed: ${tasks_completed}
1572
- Success rate: ${success_rate}%
1573
- Avg time per task: ${avg_time} minutes
1574
- Recent task history: ${task_history}
1575
-
1576
- Create a working style profile:
1577
- {\"working_style\": \"<fast-iterative|thorough-methodical|balanced|creative-exploratory>\",
1578
- \"strengths\": [\"<strength1>\", \"<strength2>\"],
1579
- \"weaknesses\": [\"<weakness1>\"],
1580
- \"ideal_task_type\": \"<description of best-fit tasks>\",
1581
- \"onboarding_preference\": \"<minimal-context|detailed-specs|example-driven|standard>\",
1582
- \"collaboration_style\": \"<independent|pair-oriented|team-player>\"}
1583
-
1584
- Return JSON only."
1585
-
1586
- local result
1587
- result=$(_recruit_call_claude "$prompt")
1588
-
1589
- if [[ -n "$result" ]] && echo "$result" | jq -e '.working_style' >/dev/null 2>&1; then
1590
- # Save the LLM-generated mind profile
1591
- local tmp_file
1592
- tmp_file=$(mktemp)
1593
- trap "rm -f '$tmp_file'" RETURN
1594
- 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"
1595
-
1596
- success "Mind profile generated:"
1597
- echo "$result" | jq -r '
1598
- " Working style: \(.working_style)" +
1599
- "\n Strengths: \(.strengths | join(", "))" +
1600
- "\n Weaknesses: \(.weaknesses | join(", "))" +
1601
- "\n Ideal tasks: \(.ideal_task_type)" +
1602
- "\n Onboarding: \(.onboarding_preference)" +
1603
- "\n Collaboration: \(.collaboration_style // "standard")"
1604
- '
1605
- emit_event "recruit_mind" "agent_id=${agent_id}"
1606
- return 0
1607
- fi
1608
- fi
1609
-
1610
- # Fallback: save heuristic profile
1611
- local strengths_json weaknesses_json
1612
- if [[ ${#strengths[@]} -gt 0 ]]; then
1613
- strengths_json=$(printf '%s\n' "${strengths[@]}" | jq -R . | jq -s .)
1614
- else
1615
- strengths_json='[]'
1616
- fi
1617
- if [[ ${#weaknesses[@]} -gt 0 ]]; then
1618
- weaknesses_json=$(printf '%s\n' "${weaknesses[@]}" | jq -R . | jq -s .)
1619
- else
1620
- weaknesses_json='[]'
1621
- fi
1622
-
1623
- local mind_json
1624
- mind_json=$(jq -n \
1625
- --arg style "$working_style" \
1626
- --argjson strengths "$strengths_json" \
1627
- --argjson weaknesses "$weaknesses_json" \
1628
- --arg ideal "$ideal_task_type" \
1629
- --arg onboard "$onboarding_pref" \
1630
- --arg ts "$(now_iso)" \
1631
- '{working_style: $style, strengths: $strengths, weaknesses: $weaknesses, ideal_task_type: $ideal, onboarding_preference: $onboard, updated: $ts}')
1632
-
1633
- local tmp_file
1634
- tmp_file=$(mktemp)
1635
- trap "rm -f '$tmp_file'" RETURN
1636
- 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"
1637
-
1638
- local strengths_display="none detected"
1639
- [[ ${#strengths[@]} -gt 0 ]] && strengths_display="${strengths[*]}"
1640
-
1641
- success "Mind profile (heuristic):"
1642
- echo " Working style: ${working_style}"
1643
- echo " Strengths: ${strengths_display}"
1644
- echo " Onboarding: ${onboarding_pref}"
1645
- emit_event "recruit_mind" "agent_id=${agent_id}" "method=heuristic"
1646
- }
1647
-
1648
- # ═══════════════════════════════════════════════════════════════════════════════
1649
- # GOAL DECOMPOSITION (Tier 3)
1650
- # ═══════════════════════════════════════════════════════════════════════════════
1651
-
1652
- cmd_decompose() {
1653
- local goal="${1:-}"
1654
-
1655
- if [[ -z "$goal" ]]; then
1656
- error "Usage: shipwright recruit decompose \"<vague goal or intent>\""
1657
- exit 1
1658
- fi
1659
-
1660
- ensure_recruit_dir
1661
- initialize_builtin_roles
1662
-
1663
- info "Decomposing goal: ${CYAN}${goal}${RESET}"
1664
- echo ""
1665
-
1666
- local available_roles
1667
- available_roles=$(jq -r 'to_entries | map("\(.key): \(.value.title) — \(.value.description)") | join("\n")' "$ROLES_DB" 2>/dev/null || echo "none")
1668
-
1669
- if _recruit_has_claude; then
1670
- local prompt
1671
- prompt="Decompose this high-level goal into specific sub-tasks, and assign the best agent role for each.
1672
-
1673
- Goal: ${goal}
1674
-
1675
- Available agent roles:
1676
- ${available_roles}
1677
-
1678
- Return a JSON object:
1679
- {\"goal\": \"<restated goal>\",
1680
- \"sub_tasks\": [
1681
- {\"task\": \"<specific task>\", \"role\": \"<role_key>\", \"priority\": \"high|medium|low\", \"depends_on\": [], \"estimated_time_min\": 30},
1682
- ...
1683
- ],
1684
- \"capability_gaps\": [\"<any capabilities not covered by existing roles>\"],
1685
- \"total_estimated_time_min\": 120,
1686
- \"risk_assessment\": \"<brief risk summary>\"}
1687
-
1688
- Return JSON only."
1689
-
1690
- local result
1691
- result=$(_recruit_call_claude "$prompt")
1692
-
1693
- if [[ -n "$result" ]] && echo "$result" | jq -e '.sub_tasks' >/dev/null 2>&1; then
1694
- local restated_goal
1695
- restated_goal=$(echo "$result" | jq -r '.goal // ""')
1696
- [[ -n "$restated_goal" ]] && echo -e " ${DIM}Interpreted as: ${restated_goal}${RESET}"
1697
- echo ""
1698
-
1699
- local task_count
1700
- task_count=$(echo "$result" | jq '.sub_tasks | length')
1701
- success "Decomposed into ${task_count} sub-tasks:"
1702
- echo ""
1703
-
1704
- echo "$result" | jq -r '.sub_tasks | to_entries[] |
1705
- " \(.key + 1). [\(.value.priority // "medium")] \(.value.task)" +
1706
- "\n Role: \(.value.role) | Est: \(.value.estimated_time_min // "?")min" +
1707
- (if (.value.depends_on | length) > 0 then "\n Depends on: \(.value.depends_on | join(", "))" else "" end)
1708
- '
1709
-
1710
- # Show capability gaps
1711
- local gaps
1712
- gaps=$(echo "$result" | jq -r '.capability_gaps // [] | .[]' 2>/dev/null || true)
1713
- if [[ -n "$gaps" ]]; then
1714
- echo ""
1715
- warn "Capability gaps detected:"
1716
- echo "$gaps" | sed 's/^/ - /'
1717
- echo " Consider: shipwright recruit create-role --auto \"<gap description>\""
1718
- fi
1719
-
1720
- # Show totals
1721
- local total_time
1722
- total_time=$(echo "$result" | jq -r '.total_estimated_time_min // 0')
1723
- local risk
1724
- risk=$(echo "$result" | jq -r '.risk_assessment // "unknown"')
1725
- echo ""
1726
- echo " Total estimated time: ${total_time} minutes"
1727
- echo " Risk: ${risk}"
1728
-
1729
- emit_event "recruit_decompose" "goal_length=${#goal}" "tasks=${task_count}" "gaps=$(echo "$gaps" | wc -l | tr -d ' ')"
1730
- return 0
1731
- fi
1732
- fi
1733
-
1734
- # Fallback: simple decomposition
1735
- warn "AI decomposition unavailable — showing default breakdown"
1736
- echo ""
1737
- echo " 1. [high] Plan and design the approach"
1738
- echo " Role: architect"
1739
- echo " 2. [high] Implement the solution"
1740
- echo " Role: builder"
1741
- echo " 3. [medium] Write tests"
1742
- echo " Role: tester"
1743
- echo " 4. [medium] Code review"
1744
- echo " Role: reviewer"
1745
- echo " 5. [low] Update documentation"
1746
- echo " Role: docs-writer"
1747
- }
1748
-
1749
- # ═══════════════════════════════════════════════════════════════════════════════
1750
- # SELF-MODIFICATION: REWRITE OWN HEURISTICS (Tier 3)
1751
- # ═══════════════════════════════════════════════════════════════════════════════
1752
-
1753
- cmd_self_tune() {
1754
- ensure_recruit_dir
1755
-
1756
- info "Self-tuning matching heuristics..."
1757
- echo ""
1758
-
1759
- if [[ ! -f "$MATCH_HISTORY" ]]; then
1760
- warn "No match history to learn from"
1761
- return 0
1762
- fi
1763
-
1764
- local total_matches
1765
- total_matches=$(wc -l < "$MATCH_HISTORY" 2>/dev/null | tr -d ' ')
1766
-
1767
- local min_matches="${RECRUIT_SELF_TUNE_MIN_MATCHES:-5}"
1768
- if [[ "$total_matches" -lt "$min_matches" ]]; then
1769
- warn "Need at least ${min_matches} matches to self-tune (have ${total_matches})"
1770
- return 0
1771
- fi
1772
-
1773
- # Analyze which keywords correctly predicted roles
1774
- info "Analyzing ${total_matches} match records..."
1775
-
1776
- # Build keyword frequency map from successful matches
1777
- local keyword_updates=0
1778
-
1779
- # Extract task descriptions grouped by role
1780
- # Note: match history .outcome is not backfilled, so we use all matches
1781
- # and rely on role-usage success/failure counts to weight quality
1782
- local match_data
1783
- match_data=$(jq -s '
1784
- [.[] | select(.role != null and .role != "")] |
1785
- group_by(.role) |
1786
- map({
1787
- role: .[0].role,
1788
- tasks: [.[] | .task],
1789
- count: length
1790
- })
1791
- ' "$MATCH_HISTORY" 2>/dev/null || echo "[]")
1792
-
1793
- # Filter to roles with positive success ratios from role-usage DB
1794
- if [[ -f "$ROLE_USAGE_DB" ]]; then
1795
- local good_roles
1796
- good_roles=$(jq -r '
1797
- to_entries[] |
1798
- select((.value.successes // 0) > (.value.failures // 0)) |
1799
- .key
1800
- ' "$ROLE_USAGE_DB" 2>/dev/null || true)
1801
-
1802
- if [[ -n "$good_roles" ]]; then
1803
- local good_roles_json
1804
- good_roles_json=$(echo "$good_roles" | jq -R . | jq -s .)
1805
- match_data=$(echo "$match_data" | jq --argjson good "$good_roles_json" '
1806
- [.[] | select(.role as $r | $good | index($r) // false)]
1807
- ' 2>/dev/null || echo "$match_data")
1808
- fi
1809
- fi
1810
-
1811
- if [[ "$match_data" == "[]" ]]; then
1812
- info "No successful outcomes recorded yet"
1813
- return 0
1814
- fi
1815
-
1816
- # Extract common words per role (simple TF approach)
1817
- local role_count
1818
- role_count=$(echo "$match_data" | jq 'length')
1819
-
1820
- local tmp_heuristics
1821
- tmp_heuristics=$(mktemp)
1822
- trap "rm -f '$tmp_heuristics'" RETURN
1823
- cp "$HEURISTICS_DB" "$tmp_heuristics"
1824
-
1825
- local i=0
1826
- while [[ "$i" -lt "$role_count" ]]; do
1827
- local role
1828
- role=$(echo "$match_data" | jq -r ".[$i].role")
1829
- local tasks
1830
- tasks=$(echo "$match_data" | jq -r ".[$i].tasks | join(\" \")" | tr '[:upper:]' '[:lower:]')
1831
-
1832
- # Find frequent words (>= 2 occurrences, >= 4 chars)
1833
- local frequent_words
1834
- frequent_words=$(echo "$tasks" | tr -cs '[:alpha:]' '\n' | sort | uniq -c | sort -rn | \
1835
- awk '$1 >= 2 && length($2) >= 4 {print $2}' | head -5)
1836
-
1837
- while IFS= read -r word; do
1838
- [[ -z "$word" ]] && continue
1839
- # Skip common stop words
1840
- case "$word" in
1841
- this|that|with|from|have|will|should|would|could|been|some|more|than|into) continue ;;
1842
- esac
1843
-
1844
- jq --arg kw "$word" --arg role "$role" \
1845
- '.keyword_weights[$kw] = {role: $role, weight: 5, source: "self-tuned"}' \
1846
- "$tmp_heuristics" > "${tmp_heuristics}.new" && mv "${tmp_heuristics}.new" "$tmp_heuristics"
1847
- keyword_updates=$((keyword_updates + 1))
1848
- done <<< "$frequent_words"
1849
-
1850
- i=$((i + 1))
1851
- done
1852
-
1853
- # Persist updated heuristics
1854
- jq --arg ts "$(now_iso)" '.last_tuned = $ts' "$tmp_heuristics" > "${tmp_heuristics}.final"
1855
- mv "${tmp_heuristics}.final" "$HEURISTICS_DB"
1856
- rm -f "$tmp_heuristics"
1857
-
1858
- success "Self-tuned ${keyword_updates} keyword→role mappings"
1859
-
1860
- # Show what changed
1861
- if [[ "$keyword_updates" -gt 0 ]]; then
1862
- echo ""
1863
- echo -e " ${BOLD}Updated Keyword Weights:${RESET}"
1864
- jq -r '.keyword_weights | to_entries | sort_by(-.value.weight) | .[:10][] |
1865
- " \(.key) → \(.value.role) (weight: \(.value.weight), source: \(.value.source))"
1866
- ' "$HEURISTICS_DB" 2>/dev/null || true
1867
- fi
1868
-
1869
- emit_event "recruit_self_tune" "keywords_updated=${keyword_updates}" "total_matches=${total_matches}"
1870
- }
1871
-
1872
- # ═══════════════════════════════════════════════════════════════════════════════
1873
- # ORIGINAL COMMANDS (enhanced)
1874
- # ═══════════════════════════════════════════════════════════════════════════════
1875
-
1876
- cmd_roles() {
1877
- ensure_recruit_dir
1878
- initialize_builtin_roles
1879
-
1880
- info "Available Agent Roles ($(jq 'length' "$ROLES_DB" 2>/dev/null || echo "?") total):"
1881
- echo ""
1882
-
1883
- jq -r 'to_entries | sort_by(.key) | .[] |
1884
- "\(.key): \(.value.title) — \(.value.description)\n Model: \(.value.recommended_model) | Cost: $\(.value.estimated_cost_per_task_usd)/task | Origin: \(.value.origin // "builtin")\n Skills: \(.value.required_skills | join(", "))\n"' \
1885
- "$ROLES_DB"
1886
- }
1887
-
1888
- cmd_match() {
1889
- local json_mode=false
1890
- if [[ "${1:-}" == "--json" ]]; then
1891
- json_mode=true
1892
- shift
1893
- fi
1894
- local task_description="${1:-}"
1895
-
1896
- if [[ -z "$task_description" ]]; then
1897
- error "Usage: shipwright recruit match [--json] \"<task description>\""
1898
- exit 1
1899
- fi
1900
-
1901
- ensure_recruit_dir
1902
- initialize_builtin_roles
1903
-
1904
- if ! $json_mode; then
1905
- info "Analyzing task: ${CYAN}${task_description}${RESET}"
1906
- echo ""
1907
- fi
1908
-
1909
- local primary_role="" secondary_roles="" confidence=0.5 method="keyword" reasoning=""
1910
-
1911
- # Try LLM-powered matching first
1912
- if _recruit_has_claude; then
1913
- local available_roles
1914
- available_roles=$(jq -c '.' "$ROLES_DB" 2>/dev/null || echo "{}")
1915
-
1916
- local llm_result
1917
- llm_result=$(_recruit_llm_match "$task_description" "$available_roles")
1918
-
1919
- if [[ -n "$llm_result" ]] && echo "$llm_result" | jq -e '.primary_role' >/dev/null 2>&1; then
1920
- primary_role=$(echo "$llm_result" | jq -r '.primary_role')
1921
- secondary_roles=$(echo "$llm_result" | jq -r '.secondary_roles // [] | join(", ")')
1922
- confidence=$(echo "$llm_result" | jq -r '.confidence // 0.8')
1923
- reasoning=$(echo "$llm_result" | jq -r '.reasoning // ""')
1924
- method="llm"
1925
-
1926
- # Check if a new role was suggested
1927
- local new_role_needed
1928
- new_role_needed=$(echo "$llm_result" | jq -r '.new_role_needed // false')
1929
- if [[ "$new_role_needed" == "true" ]]; then
1930
- local suggested
1931
- suggested=$(echo "$llm_result" | jq '.suggested_role // null')
1932
- if [[ "$suggested" != "null" ]]; then
1933
- echo ""
1934
- warn "No perfect role match — AI suggests creating a new role:"
1935
- echo " $(echo "$suggested" | jq -r '.title // "Unknown"'): $(echo "$suggested" | jq -r '.description // ""')"
1936
- echo " Run: shipwright recruit create-role --auto \"${task_description}\""
1937
- echo ""
1938
- fi
1939
- fi
1940
- fi
1941
- fi
1942
-
1943
- # Fallback to keyword matching
1944
- if [[ -z "$primary_role" ]]; then
1945
- local detected_skills
1946
- detected_skills=$(_recruit_keyword_match "$task_description")
1947
- primary_role=$(echo "$detected_skills" | awk '{print $1}')
1948
- secondary_roles=$(echo "$detected_skills" | cut -d' ' -f2- | tr ' ' ',' | sed 's/,$//')
1949
- method="keyword"
1950
- confidence=0.5
1951
- fi
1952
-
1953
- # Validate role exists
1954
- if ! jq -e ".\"${primary_role}\"" "$ROLES_DB" >/dev/null 2>&1; then
1955
- primary_role="builder"
1956
- fi
1957
-
1958
- # Record for learning
1959
- _recruit_record_match "$task_description" "$primary_role" "$method" "$confidence"
1960
-
1961
- local role_info
1962
- role_info=$(jq ".\"${primary_role}\"" "$ROLES_DB")
1963
- local recommended_model
1964
- recommended_model=$(echo "$role_info" | jq -r '.recommended_model // "sonnet"')
1965
-
1966
- # JSON mode: structured output for programmatic consumption
1967
- if $json_mode; then
1968
- jq -c -n \
1969
- --arg role "$primary_role" \
1970
- --arg secondary "$secondary_roles" \
1971
- --argjson confidence "$confidence" \
1972
- --arg method "$method" \
1973
- --arg model "$recommended_model" \
1974
- --arg reasoning "$reasoning" \
1975
- '{
1976
- primary_role: $role,
1977
- secondary_roles: ($secondary | split(", ") | map(select(. != ""))),
1978
- confidence: $confidence,
1979
- method: $method,
1980
- model: $model,
1981
- reasoning: $reasoning
1982
- }'
1983
- return 0
1984
- fi
1985
-
1986
- success "Recommended role: ${CYAN}${primary_role}${RESET} ${DIM}(confidence: $(awk -v c="$confidence" 'BEGIN{printf "%.0f", c*100}')%, method: ${method})${RESET}"
1987
- [[ -n "$reasoning" ]] && echo -e " ${DIM}${reasoning}${RESET}"
1988
- echo ""
1989
-
1990
- echo " $(echo "$role_info" | jq -r '.description')"
1991
- echo " Model: ${recommended_model}"
1992
- echo " Skills: $(echo "$role_info" | jq -r '.required_skills | join(", ")')"
1993
-
1994
- if [[ -n "$secondary_roles" && "$secondary_roles" != "null" ]]; then
1995
- echo ""
1996
- warn "Secondary roles: ${secondary_roles}"
1997
- fi
1998
- }
1999
-
2000
- cmd_evaluate() {
2001
- local agent_id="${1:-}"
2002
-
2003
- if [[ -z "$agent_id" ]]; then
2004
- error "Usage: shipwright recruit evaluate <agent-id>"
2005
- exit 1
2006
- fi
2007
-
2008
- ensure_recruit_dir
2009
-
2010
- info "Evaluating agent: ${CYAN}${agent_id}${RESET}"
2011
- echo ""
2012
-
2013
- local profile
2014
- profile=$(jq ".\"${agent_id}\"" "$PROFILES_DB" 2>/dev/null || echo "{}")
2015
-
2016
- if [[ "$profile" == "{}" || "$profile" == "null" ]]; then
2017
- warn "No evaluation history for ${agent_id}"
2018
- return 0
2019
- fi
2020
-
2021
- echo "Performance Metrics:"
2022
- echo " Success Rate: $(echo "$profile" | jq -r '.success_rate // "N/A"')%"
2023
- echo " Avg Time: $(echo "$profile" | jq -r '.avg_time_minutes // "N/A"') minutes"
2024
- echo " Quality Score: $(echo "$profile" | jq -r '.quality_score // "N/A"')/10"
2025
- echo " Cost Efficiency: $(echo "$profile" | jq -r '.cost_efficiency // "N/A"')%"
2026
- echo " Tasks Completed: $(echo "$profile" | jq -r '.tasks_completed // "0"')"
2027
- echo ""
2028
-
2029
- # Use population-aware thresholds for performance evaluation
2030
- local pop_stats
2031
- pop_stats=$(_recruit_compute_population_stats)
2032
- local mean_success
2033
- mean_success=$(echo "$pop_stats" | jq -r '.mean_success')
2034
- local stddev
2035
- stddev=$(echo "$pop_stats" | jq -r '.stddev_success')
2036
- local agent_count
2037
- agent_count=$(echo "$pop_stats" | jq -r '.count')
2038
-
2039
- local success_rate
2040
- success_rate=$(echo "$profile" | jq -r '.success_rate // 0')
2041
-
2042
- if [[ "$agent_count" -ge 3 ]]; then
2043
- # Population-aware evaluation
2044
- local promote_threshold demote_threshold
2045
- promote_threshold=$(awk -v m="$mean_success" -v s="$stddev" 'BEGIN{v=m+s; if(v>95) v=95; printf "%.0f", v}')
2046
- demote_threshold=$(awk -v m="$mean_success" -v s="$stddev" 'BEGIN{v=m-s; if(v<40) v=40; printf "%.0f", v}')
2047
50
 
2048
- echo -e " ${DIM}Population thresholds (${agent_count} agents): promote ≥${promote_threshold}%, demote <${demote_threshold}%${RESET}"
51
+ # ─── File Locking for Concurrent Safety ────────────────────────────────────
52
+ # Usage: _recruit_locked_write <target_file> <tmp_file>
53
+ # Acquires flock, then moves tmp_file to target atomically.
54
+ # Caller is responsible for creating tmp_file and cleaning up on error.
55
+ _recruit_locked_write() {
56
+ local target="$1"
57
+ local tmp_file="$2"
58
+ local lock_file="${target}.lock"
2049
59
 
2050
- if awk -v sr="$success_rate" -v t="$demote_threshold" 'BEGIN{exit !(sr < t)}' 2>/dev/null; then
2051
- warn "Performance below population threshold. Consider downgrading or retraining."
2052
- elif awk -v sr="$success_rate" -v t="$promote_threshold" 'BEGIN{exit !(sr >= t)}' 2>/dev/null; then
2053
- success "Excellent performance (top tier). Consider for promotion."
2054
- else
2055
- success "Acceptable performance. Continue current assignment."
2056
- fi
2057
- else
2058
- # Fallback to fixed thresholds
2059
- if (( $(echo "$success_rate < 70" | bc -l 2>/dev/null || echo "1") )); then
2060
- warn "Performance below threshold. Consider downgrading or retraining."
2061
- elif (( $(echo "$success_rate >= 90" | bc -l 2>/dev/null || echo "0") )); then
2062
- success "Excellent performance. Consider for promotion."
2063
- else
2064
- success "Acceptable performance. Continue current assignment."
60
+ (
61
+ if command -v flock >/dev/null 2>&1; then
62
+ flock -w 5 200 2>/dev/null || true
2065
63
  fi
2066
- fi
2067
- }
2068
-
2069
- cmd_profiles() {
2070
- ensure_recruit_dir
2071
-
2072
- info "Agent Performance Profiles:"
2073
- echo ""
2074
-
2075
- if [[ ! -s "$PROFILES_DB" || "$(jq 'length' "$PROFILES_DB" 2>/dev/null || echo 0)" -eq 0 ]]; then
2076
- warn "No performance profiles recorded yet"
2077
- return 0
2078
- fi
2079
-
2080
- jq -r 'to_entries | .[] |
2081
- "\(.key):\n Success: \(.value.success_rate // "N/A")% | Quality: \(.value.quality_score // "N/A")/10 | Tasks: \(.value.tasks_completed // 0)\n Avg Time: \(.value.avg_time_minutes // "N/A")min | Efficiency: \(.value.cost_efficiency // "N/A")%\n Model: \(.value.model // "unknown") | Role: \(.value.role // "unassigned")\n"' \
2082
- "$PROFILES_DB"
64
+ mv "$tmp_file" "$target"
65
+ ) 200>"$lock_file"
2083
66
  }
2084
67
 
2085
- cmd_promote() {
2086
- local agent_id="${1:-}"
2087
-
2088
- if [[ -z "$agent_id" ]]; then
2089
- error "Usage: shipwright recruit promote <agent-id>"
2090
- exit 1
2091
- fi
2092
-
2093
- ensure_recruit_dir
2094
-
2095
- info "Evaluating promotion eligibility for: ${CYAN}${agent_id}${RESET}"
2096
- echo ""
2097
-
2098
- local profile
2099
- profile=$(jq ".\"${agent_id}\"" "$PROFILES_DB" 2>/dev/null || echo "{}")
2100
-
2101
- if [[ "$profile" == "{}" || "$profile" == "null" ]]; then
2102
- warn "No profile found for ${agent_id}"
2103
- return 1
2104
- fi
2105
-
2106
- local success_rate quality_score
2107
- success_rate=$(echo "$profile" | jq -r '.success_rate // 0')
2108
- quality_score=$(echo "$profile" | jq -r '.quality_score // 0')
2109
-
2110
- local current_model
2111
- current_model=$(echo "$profile" | jq -r '.model // "haiku"')
2112
-
2113
- # Use population-aware thresholds
2114
- local pop_stats
2115
- pop_stats=$(_recruit_compute_population_stats)
2116
- local mean_success
2117
- mean_success=$(echo "$pop_stats" | jq -r '.mean_success')
2118
- local agent_count
2119
- agent_count=$(echo "$pop_stats" | jq -r '.count')
2120
-
2121
- local promote_sr_threshold="${RECRUIT_PROMOTE_SUCCESS:-85}"
2122
- local promote_q_threshold=9
2123
- local demote_sr_threshold=60
2124
- local demote_q_threshold=5
2125
-
2126
- if [[ "$agent_count" -ge 3 ]]; then
2127
- local stddev
2128
- stddev=$(echo "$pop_stats" | jq -r '.stddev_success')
2129
- promote_sr_threshold=$(awk -v m="$mean_success" -v s="$stddev" 'BEGIN{v=m+s; if(v>98) v=98; printf "%.0f", v}')
2130
- 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}')
2131
- fi
2132
-
2133
- local recommended_model="$current_model"
2134
- local promotion_reason=""
2135
-
2136
- if awk -v sr="$success_rate" -v st="$promote_sr_threshold" -v qs="$quality_score" -v qt="$promote_q_threshold" \
2137
- 'BEGIN{exit !(sr >= st && qs >= qt)}' 2>/dev/null; then
2138
- case "$current_model" in
2139
- haiku) recommended_model="sonnet"; promotion_reason="Excellent performance on Haiku" ;;
2140
- sonnet) recommended_model="opus"; promotion_reason="Outstanding results on Sonnet" ;;
2141
- opus) promotion_reason="Already on best model"; recommended_model="opus" ;;
2142
- esac
2143
- elif awk -v sr="$success_rate" -v st="$demote_sr_threshold" -v qs="$quality_score" -v qt="$demote_q_threshold" \
2144
- 'BEGIN{exit !(sr < st || qs < qt)}' 2>/dev/null; then
2145
- case "$current_model" in
2146
- opus) recommended_model="sonnet"; promotion_reason="Struggling on Opus, try Sonnet" ;;
2147
- sonnet) recommended_model="haiku"; promotion_reason="Poor performance, reduce cost" ;;
2148
- haiku) promotion_reason="Consider retraining"; recommended_model="haiku" ;;
2149
- esac
2150
- fi
68
+ # ─── Recruitment Storage Paths ─────────────────────────────────────────────
69
+ RECRUIT_ROOT="${HOME}/.shipwright/recruitment"
70
+ ROLES_DB="${RECRUIT_ROOT}/roles.json"
71
+ PROFILES_DB="${RECRUIT_ROOT}/profiles.json"
72
+ TALENT_DB="${RECRUIT_ROOT}/talent.json"
73
+ ONBOARDING_DB="${RECRUIT_ROOT}/onboarding.json"
74
+ MATCH_HISTORY="${RECRUIT_ROOT}/match-history.jsonl"
75
+ ROLE_USAGE_DB="${RECRUIT_ROOT}/role-usage.json"
76
+ HEURISTICS_DB="${RECRUIT_ROOT}/heuristics.json"
77
+ AGENT_MINDS_DB="${RECRUIT_ROOT}/agent-minds.json"
78
+ INVENTED_ROLES_LOG="${RECRUIT_ROOT}/invented-roles.jsonl"
79
+ META_LEARNING_DB="${RECRUIT_ROOT}/meta-learning.json"
2151
80
 
2152
- if [[ "$recommended_model" != "$current_model" ]]; then
2153
- success "Recommend upgrading from ${CYAN}${current_model}${RESET} to ${PURPLE}${recommended_model}${RESET}"
2154
- echo " Reason: $promotion_reason"
2155
- echo -e " ${DIM}Thresholds: promote ≥${promote_sr_threshold}%, demote <${demote_sr_threshold}% (${agent_count} agents in population)${RESET}"
2156
- emit_event "recruit_promotion" "agent_id=${agent_id}" "from=${current_model}" "to=${recommended_model}" "reason=${promotion_reason}"
81
+ # ─── Policy Integration ──────────────────────────────────────────────────
82
+ POLICY_FILE="${SCRIPT_DIR}/../config/policy.json"
83
+ _recruit_policy() {
84
+ local key="$1"
85
+ local default="$2"
86
+ if [[ -f "$POLICY_FILE" ]] && command -v jq >/dev/null 2>&1; then
87
+ local val
88
+ val=$(jq -r ".recruit.${key} // empty" "$POLICY_FILE" 2>/dev/null) || true
89
+ [[ -n "$val" ]] && echo "$val" || echo "$default"
2157
90
  else
2158
- info "No model change recommended for ${agent_id}"
2159
- echo " Current: ${current_model} | Success: ${success_rate}% | Quality: ${quality_score}/10"
91
+ echo "$default"
2160
92
  fi
2161
93
  }
2162
94
 
2163
- cmd_onboard() {
2164
- local agent_role="${1:-builder}"
2165
- local agent_id="${2:-}"
2166
-
2167
- ensure_recruit_dir
2168
- initialize_builtin_roles
2169
-
2170
- info "Generating onboarding context for: ${CYAN}${agent_role}${RESET}"
2171
- echo ""
2172
-
2173
- local role_info
2174
- role_info=$(jq --arg role "$agent_role" '.[$role]' "$ROLES_DB" 2>/dev/null)
2175
-
2176
- if [[ -z "$role_info" || "$role_info" == "null" ]]; then
2177
- error "Unknown role: ${agent_role}"
2178
- exit 1
2179
- fi
2180
-
2181
- # Build adaptive onboarding based on theory-of-mind if available
2182
- local onboarding_style="standard"
2183
- if [[ -n "$agent_id" && -f "$AGENT_MINDS_DB" ]]; then
2184
- local mind_profile
2185
- mind_profile=$(jq ".\"${agent_id}\"" "$AGENT_MINDS_DB" 2>/dev/null || echo "null")
2186
- if [[ "$mind_profile" != "null" ]]; then
2187
- onboarding_style=$(echo "$mind_profile" | jq -r '.onboarding_preference // "standard"')
2188
- info "Adapting onboarding to agent preference: ${PURPLE}${onboarding_style}${RESET}"
2189
- fi
2190
- fi
2191
-
2192
- # Build onboarding style description outside the heredoc
2193
- local style_desc="Standard onboarding. Review the role profile and codebase structure."
2194
- case "$onboarding_style" in
2195
- minimal-context) style_desc="This agent works best with minimal upfront context. Provide the core task and let them explore." ;;
2196
- detailed-specs) style_desc="This agent prefers detailed specifications. Provide full requirements, edge cases, and examples." ;;
2197
- example-driven) style_desc="This agent learns best from examples. Provide sample inputs/outputs and reference implementations." ;;
2198
- esac
2199
-
2200
- local role_title_val role_desc_val role_model_val role_origin_val role_cost_val
2201
- role_title_val=$(echo "$role_info" | jq -r '.title')
2202
- role_desc_val=$(echo "$role_info" | jq -r '.description')
2203
- role_model_val=$(echo "$role_info" | jq -r '.recommended_model')
2204
- role_origin_val=$(echo "$role_info" | jq -r '.origin // "builtin"')
2205
- role_cost_val=$(echo "$role_info" | jq -r '.estimated_cost_per_task_usd')
2206
- local role_skills_val role_context_val role_metrics_val
2207
- role_skills_val=$(echo "$role_info" | jq -r '.required_skills[]' | sed 's/^/- /')
2208
- role_context_val=$(echo "$role_info" | jq -r '.context_needs[]' | sed 's/^/- /')
2209
- role_metrics_val=$(echo "$role_info" | jq -r '.success_metrics[]' | sed 's/^/- /')
2210
-
2211
- local onboarding_doc
2212
- onboarding_doc="# Onboarding Context: ${agent_role}
2213
-
2214
- ## Role Profile
2215
- **Title:** ${role_title_val}
2216
- **Description:** ${role_desc_val}
2217
- **Recommended Model:** ${role_model_val}
2218
- **Origin:** ${role_origin_val}
2219
-
2220
- ## Required Skills
2221
- ${role_skills_val}
2222
-
2223
- ## Context Needs
2224
- ${role_context_val}
2225
-
2226
- ## Success Metrics
2227
- ${role_metrics_val}
2228
-
2229
- ## Cost Profile
2230
- Estimated cost per task: \$${role_cost_val}
2231
-
2232
- ## Onboarding Style: ${onboarding_style}
2233
- ${style_desc}
2234
-
2235
- ## Getting Started
2236
- 1. Review the role profile above
2237
- 2. Study the codebase architecture
2238
- 3. Familiarize yourself with coding standards
2239
- 4. Review past pipeline runs for patterns
2240
- 5. Ask questions about unclear requirements
2241
-
2242
- ## Resources
2243
- - Codebase: /path/to/repo
2244
- - Documentation: See .claude/ directory
2245
- - Team patterns: Reviewed in memory system
2246
- - Past learnings: Available in ~/.shipwright/memory/"
2247
-
2248
- local onboarding_key
2249
- onboarding_key=$(date +%s)
2250
- jq --arg key "$onboarding_key" --arg doc "$onboarding_doc" '.[$key] = $doc' "$ONBOARDING_DB" > "${ONBOARDING_DB}.tmp"
2251
- mv "${ONBOARDING_DB}.tmp" "$ONBOARDING_DB"
95
+ RECRUIT_CONFIDENCE_THRESHOLD=$(_recruit_policy "match_confidence_threshold" "0.3")
96
+ RECRUIT_MAX_MATCH_HISTORY=$(_recruit_policy "max_match_history_size" "5000")
97
+ RECRUIT_META_ACCURACY_FLOOR=$(_recruit_policy "meta_learning_accuracy_floor" "50")
98
+ RECRUIT_LLM_TIMEOUT=$(_recruit_policy "llm_timeout_seconds" "30")
99
+ RECRUIT_DEFAULT_MODEL=$(_recruit_policy "default_model" "sonnet")
100
+ RECRUIT_SELF_TUNE_MIN_MATCHES=$(_recruit_policy "self_tune_min_matches" "5")
101
+ RECRUIT_PROMOTE_TASKS=$(_recruit_policy "promote_threshold_tasks" "10")
102
+ RECRUIT_PROMOTE_SUCCESS=$(_recruit_policy "promote_threshold_success_rate" "85")
103
+ RECRUIT_AUTO_EVOLVE_AFTER=$(_recruit_policy "auto_evolve_after_outcomes" "20")
2252
104
 
2253
- success "Onboarding context generated for ${agent_role}"
2254
- echo ""
2255
- echo "$onboarding_doc"
2256
- emit_event "recruit_onboarding" "role=${agent_role}" "style=${onboarding_style}" "timestamp=$(now_epoch)"
105
+ ensure_recruit_dir() {
106
+ mkdir -p "$RECRUIT_ROOT"
107
+ [[ -f "$ROLES_DB" ]] || echo '{}' > "$ROLES_DB"
108
+ [[ -f "$PROFILES_DB" ]] || echo '{}' > "$PROFILES_DB"
109
+ [[ -f "$TALENT_DB" ]] || echo '[]' > "$TALENT_DB"
110
+ [[ -f "$ONBOARDING_DB" ]] || echo '{}' > "$ONBOARDING_DB"
111
+ [[ -f "$ROLE_USAGE_DB" ]] || echo '{}' > "$ROLE_USAGE_DB"
112
+ [[ -f "$HEURISTICS_DB" ]] || echo '{"keyword_weights":{},"match_accuracy":[],"last_tuned":"never"}' > "$HEURISTICS_DB"
113
+ [[ -f "$AGENT_MINDS_DB" ]] || echo '{}' > "$AGENT_MINDS_DB"
114
+ [[ -f "$META_LEARNING_DB" ]] || echo '{"corrections":[],"accuracy_trend":[],"last_reflection":"never"}' > "$META_LEARNING_DB"
2257
115
  }
2258
116
 
2259
- cmd_stats() {
2260
- ensure_recruit_dir
2261
-
2262
- info "Recruitment Statistics & Talent Trends:"
2263
- echo ""
2264
-
2265
- local role_count profile_count talent_count
2266
- role_count=$(jq 'length' "$ROLES_DB" 2>/dev/null || echo 0)
2267
- profile_count=$(jq 'length' "$PROFILES_DB" 2>/dev/null || echo 0)
2268
- talent_count=$(jq 'length' "$TALENT_DB" 2>/dev/null || echo 0)
117
+ # ─── Intelligence Engine (optional) ────────────────────────────────────────
118
+ INTELLIGENCE_AVAILABLE=false
119
+ if [[ -f "$SCRIPT_DIR/sw-intelligence.sh" ]]; then
120
+ # shellcheck source=sw-intelligence.sh
121
+ source "$SCRIPT_DIR/sw-intelligence.sh"
122
+ INTELLIGENCE_AVAILABLE=true
123
+ fi
2269
124
 
2270
- local builtin_count custom_count invented_count
2271
- builtin_count=$(jq '[.[] | select(.origin == "builtin" or .origin == null)] | length' "$ROLES_DB" 2>/dev/null || echo 0)
2272
- custom_count=$(jq '[.[] | select(.origin == "manual" or .origin == "ai-generated")] | length' "$ROLES_DB" 2>/dev/null || echo 0)
2273
- invented_count=$(jq '[.[] | select(.origin == "invented")] | length' "$ROLES_DB" 2>/dev/null || echo 0)
125
+ # Check if Claude CLI is available for LLM-powered features
126
+ # Set SW_RECRUIT_NO_LLM=1 to disable LLM calls (e.g., in tests)
127
+ _recruit_has_claude() {
128
+ [[ "${SW_RECRUIT_NO_LLM:-}" == "1" ]] && return 1
129
+ command -v claude >/dev/null 2>&1
130
+ }
2274
131
 
2275
- echo " Roles Defined: $role_count (builtin: ${builtin_count}, custom: ${custom_count}, invented: ${invented_count})"
2276
- echo " Agents Profiled: $profile_count"
2277
- echo " Talent Records: $talent_count"
132
+ # Call Claude with a prompt, return text. Falls back gracefully.
133
+ _recruit_call_claude() {
134
+ local prompt="$1"
135
+ local model="${2:-sonnet}"
2278
136
 
2279
- if [[ -f "$MATCH_HISTORY" ]]; then
2280
- local match_count
2281
- match_count=$(wc -l < "$MATCH_HISTORY" 2>/dev/null | tr -d ' ')
2282
- echo " Match History: ${match_count} records"
2283
- fi
137
+ # Honor the no-LLM flag everywhere (not just _recruit_has_claude)
138
+ [[ "${SW_RECRUIT_NO_LLM:-}" == "1" ]] && { echo ""; return; }
2284
139
 
2285
- if [[ -f "$HEURISTICS_DB" ]]; then
2286
- local keyword_count last_tuned
2287
- keyword_count=$(jq '.keyword_weights | length' "$HEURISTICS_DB" 2>/dev/null || echo 0)
2288
- last_tuned=$(jq -r '.last_tuned // "never"' "$HEURISTICS_DB" 2>/dev/null || echo "never")
2289
- echo " Learned Keywords: ${keyword_count}"
2290
- echo " Last Self-Tuned: ${last_tuned}"
140
+ if [[ "$INTELLIGENCE_AVAILABLE" == "true" ]] && command -v _intelligence_call_claude >/dev/null 2>&1; then
141
+ _intelligence_call_claude "$prompt" 2>/dev/null || echo ""
142
+ return
2291
143
  fi
2292
144
 
2293
- if [[ -f "$META_LEARNING_DB" ]]; then
2294
- local corrections accuracy_points
2295
- corrections=$(jq '.corrections | length' "$META_LEARNING_DB" 2>/dev/null || echo 0)
2296
- accuracy_points=$(jq '.accuracy_trend | length' "$META_LEARNING_DB" 2>/dev/null || echo 0)
2297
- echo " Meta-Learning Corrections: ${corrections}"
2298
- echo " Accuracy Data Points: ${accuracy_points}"
145
+ if _recruit_has_claude; then
146
+ claude -p "$prompt" --model "$model" 2>/dev/null || echo ""
147
+ return
2299
148
  fi
2300
149
 
2301
150
  echo ""
2302
-
2303
- if [[ "$profile_count" -gt 0 ]]; then
2304
- local pop_stats
2305
- pop_stats=$(_recruit_compute_population_stats)
2306
- echo " Population Stats:"
2307
- echo " Mean Success Rate: $(echo "$pop_stats" | jq -r '.mean_success')%"
2308
- echo " Std Dev: $(echo "$pop_stats" | jq -r '.stddev_success')%"
2309
- echo " P90/P10 Spread: $(echo "$pop_stats" | jq -r '.p90_success')% / $(echo "$pop_stats" | jq -r '.p10_success')%"
2310
- echo ""
2311
- fi
2312
-
2313
- success "Use 'shipwright recruit profiles' for detailed breakdown"
2314
151
  }
2315
152
 
2316
- cmd_help() {
2317
- cat <<EOF
2318
- ${BOLD}${CYAN}shipwright recruit${RESET} ${DIM}v${RECRUIT_VERSION}${RESET} — AGI-Level Agent Recruitment & Talent Management
2319
-
2320
- ${BOLD}CORE COMMANDS${RESET}
2321
- ${CYAN}roles${RESET} List all available agent roles (builtin + dynamic)
2322
- ${CYAN}match${RESET} "<task>" Analyze task → recommend role (LLM + keyword fallback)
2323
- ${CYAN}evaluate${RESET} <id> Score agent performance (population-aware thresholds)
2324
- ${CYAN}team${RESET} "<issue>" Recommend optimal team (AI + codebase analysis)
2325
- ${CYAN}profiles${RESET} Show all agent performance profiles
2326
- ${CYAN}promote${RESET} <id> Recommend model upgrades (self-tuning thresholds)
2327
- ${CYAN}onboard${RESET} <role> [agent] Generate adaptive onboarding context
2328
- ${CYAN}stats${RESET} Show recruitment statistics and talent trends
2329
-
2330
- ${BOLD}DYNAMIC ROLES (Tier 1)${RESET}
2331
- ${CYAN}create-role${RESET} <key> [title] [desc] Create a new role manually
2332
- ${CYAN}create-role${RESET} --auto "<task>" AI-generate a role from task description
2333
-
2334
- ${BOLD}FEEDBACK LOOP (Tier 1)${RESET}
2335
- ${CYAN}record-outcome${RESET} <agent> <task> <success|failure> [quality] [duration]
2336
- ${CYAN}ingest-pipeline${RESET} [days] Ingest outcomes from events.jsonl
2337
-
2338
- ${BOLD}INTELLIGENCE (Tier 2)${RESET}
2339
- ${CYAN}evolve${RESET} Analyze role usage → suggest splits/merges/retirements
2340
- ${CYAN}specializations${RESET} Show agent specialization analysis
2341
- ${CYAN}route${RESET} "<task>" Smart-route task to best available agent
2342
-
2343
- ${BOLD}AGI-LEVEL (Tier 3)${RESET}
2344
- ${CYAN}reflect${RESET} Meta-learning: analyze matching accuracy
2345
- ${CYAN}invent${RESET} Autonomously discover & create new roles
2346
- ${CYAN}mind${RESET} [agent-id] Theory of mind: agent working style profiles
2347
- ${CYAN}decompose${RESET} "<goal>" Break vague goals into sub-tasks + role assignments
2348
- ${CYAN}self-tune${RESET} Self-modify keyword→role heuristics from outcomes
2349
- ${CYAN}audit${RESET} Negative-compounding self-audit of all loops and integrations
153
+ # ═══════════════════════════════════════════════════════════════════════════════
154
+ # SOURCE FOCUSED MODULES (Tier-based organization)
155
+ # ═══════════════════════════════════════════════════════════════════════════════
2350
156
 
2351
- ${BOLD}EXAMPLES${RESET}
2352
- ${DIM}shipwright recruit match "Add OAuth2 authentication"${RESET}
2353
- ${DIM}shipwright recruit create-role --auto "Database migration planning"${RESET}
2354
- ${DIM}shipwright recruit record-outcome agent-001 task-42 success 8 15${RESET}
2355
- ${DIM}shipwright recruit decompose "Make the product enterprise-ready"${RESET}
2356
- ${DIM}shipwright recruit invent${RESET}
2357
- ${DIM}shipwright recruit self-tune${RESET}
2358
- ${DIM}shipwright recruit mind agent-builder-001${RESET}
157
+ # shellcheck source=lib/recruit-roles.sh
158
+ [[ -f "$SCRIPT_DIR/lib/recruit-roles.sh" ]] && source "$SCRIPT_DIR/lib/recruit-roles.sh"
2359
159
 
2360
- ${BOLD}ROLE CATALOG${RESET}
2361
- Built-in: architect, builder, reviewer, tester, security-auditor,
2362
- docs-writer, optimizer, devops, pm, incident-responder
2363
- + any dynamically created or invented roles
160
+ # shellcheck source=lib/recruit-learning.sh
161
+ [[ -f "$SCRIPT_DIR/lib/recruit-learning.sh" ]] && source "$SCRIPT_DIR/lib/recruit-learning.sh"
2364
162
 
2365
- ${DIM}Store: ~/.shipwright/recruitment/${RESET}
2366
- EOF
2367
- }
163
+ # shellcheck source=lib/recruit-commands.sh
164
+ [[ -f "$SCRIPT_DIR/lib/recruit-commands.sh" ]] && source "$SCRIPT_DIR/lib/recruit-commands.sh"
2368
165
 
2369
166
  # ═══════════════════════════════════════════════════════════════════════════════
2370
167
  # NEGATIVE-COMPOUNDING FEEDBACK LOOP (Self-Audit)
2371
- #
2372
- # This command systematically asks every hard question about the system:
2373
- # - What's broken? What's not wired? What's not fully implemented?
2374
- # - Are feedback loops closed? Does data actually flow?
2375
- # - Are integrations proven or just claimed?
2376
- #
2377
- # Findings compound: each audit creates a score, the score feeds into the
2378
- # system, and declining scores trigger automated remediation.
2379
168
  # ═══════════════════════════════════════════════════════════════════════════════
2380
169
 
2381
170
  cmd_audit() {
@@ -2390,6 +179,13 @@ cmd_audit() {
2390
179
  local warnings=()
2391
180
  local failures=()
2392
181
 
182
+ # Color fallbacks
183
+ GREEN="${GREEN:-\033[38;2;74;222;128m}"
184
+ RED="${RED:-\033[38;2;248;113;113m}"
185
+ YELLOW="${YELLOW:-\033[38;2;251;204;21m}"
186
+ RESET="${RESET:-\033[0m}"
187
+ BOLD="${BOLD:-\033[1m}"
188
+
2393
189
  _audit_check() {
2394
190
  local name="$1"
2395
191
  local result="$2" # pass|fail|warn
@@ -2422,7 +218,7 @@ cmd_audit() {
2422
218
 
2423
219
  echo -e "${BOLD}2. FEEDBACK LOOPS${RESET}"
2424
220
 
2425
- # Loop 1: Role usage tracking — do successes/failures get updated?
221
+ # Loop 1: Role usage tracking
2426
222
  if [[ -f "$ROLE_USAGE_DB" ]]; then
2427
223
  local has_outcomes
2428
224
  has_outcomes=$(jq '[.[]] | map(select(.successes > 0 or .failures > 0)) | length' "$ROLE_USAGE_DB" 2>/dev/null || echo "0")
@@ -2602,6 +398,63 @@ cmd_audit() {
2602
398
  [[ "$fail_count" -gt 0 ]] && return 1 || return 0
2603
399
  }
2604
400
 
401
+ # ═══════════════════════════════════════════════════════════════════════════════
402
+ # HELP & ROUTING
403
+ # ═══════════════════════════════════════════════════════════════════════════════
404
+
405
+ cmd_help() {
406
+ cat <<EOF
407
+ ${BOLD}${CYAN}shipwright recruit${RESET} ${DIM}v${RECRUIT_VERSION}${RESET} — AGI-Level Agent Recruitment & Talent Management
408
+
409
+ ${BOLD}CORE COMMANDS${RESET}
410
+ ${CYAN}roles${RESET} List all available agent roles (builtin + dynamic)
411
+ ${CYAN}match${RESET} "<task>" Analyze task → recommend role (LLM + keyword fallback)
412
+ ${CYAN}evaluate${RESET} <id> Score agent performance (population-aware thresholds)
413
+ ${CYAN}team${RESET} "<issue>" Recommend optimal team (AI + codebase analysis)
414
+ ${CYAN}profiles${RESET} Show all agent performance profiles
415
+ ${CYAN}promote${RESET} <id> Recommend model upgrades (self-tuning thresholds)
416
+ ${CYAN}onboard${RESET} <role> [agent] Generate adaptive onboarding context
417
+ ${CYAN}stats${RESET} Show recruitment statistics and talent trends
418
+
419
+ ${BOLD}DYNAMIC ROLES (Tier 1)${RESET}
420
+ ${CYAN}create-role${RESET} <key> [title] [desc] Create a new role manually
421
+ ${CYAN}create-role${RESET} --auto "<task>" AI-generate a role from task description
422
+
423
+ ${BOLD}FEEDBACK LOOP (Tier 1)${RESET}
424
+ ${CYAN}record-outcome${RESET} <agent> <task> <success|failure> [quality] [duration]
425
+ ${CYAN}ingest-pipeline${RESET} [days] Ingest outcomes from events.jsonl
426
+
427
+ ${BOLD}INTELLIGENCE (Tier 2)${RESET}
428
+ ${CYAN}evolve${RESET} Analyze role usage → suggest splits/merges/retirements
429
+ ${CYAN}specializations${RESET} Show agent specialization analysis
430
+ ${CYAN}route${RESET} "<task>" Smart-route task to best available agent
431
+
432
+ ${BOLD}AGI-LEVEL (Tier 3)${RESET}
433
+ ${CYAN}reflect${RESET} Meta-learning: analyze matching accuracy
434
+ ${CYAN}invent${RESET} Autonomously discover & create new roles
435
+ ${CYAN}mind${RESET} [agent-id] Theory of mind: agent working style profiles
436
+ ${CYAN}decompose${RESET} "<goal>" Break vague goals into sub-tasks + role assignments
437
+ ${CYAN}self-tune${RESET} Self-modify keyword→role heuristics from outcomes
438
+ ${CYAN}audit${RESET} Negative-compounding self-audit of all loops and integrations
439
+
440
+ ${BOLD}EXAMPLES${RESET}
441
+ ${DIM}shipwright recruit match "Add OAuth2 authentication"${RESET}
442
+ ${DIM}shipwright recruit create-role --auto "Database migration planning"${RESET}
443
+ ${DIM}shipwright recruit record-outcome agent-001 task-42 success 8 15${RESET}
444
+ ${DIM}shipwright recruit decompose "Make the product enterprise-ready"${RESET}
445
+ ${DIM}shipwright recruit invent${RESET}
446
+ ${DIM}shipwright recruit self-tune${RESET}
447
+ ${DIM}shipwright recruit mind agent-builder-001${RESET}
448
+
449
+ ${BOLD}ROLE CATALOG${RESET}
450
+ Built-in: architect, builder, reviewer, tester, security-auditor,
451
+ docs-writer, optimizer, devops, pm, incident-responder
452
+ + any dynamically created or invented roles
453
+
454
+ ${DIM}Store: ~/.shipwright/recruitment/${RESET}
455
+ EOF
456
+ }
457
+
2605
458
  # ─── Main Router ──────────────────────────────────────────────────────────
2606
459
 
2607
460
  if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then