shipwright-cli 3.1.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 (283) 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 +22 -8
  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/config/defaults.json +25 -2
  17. package/config/policy.json +1 -1
  18. package/dashboard/middleware/auth.ts +134 -0
  19. package/dashboard/middleware/constants.ts +21 -0
  20. package/dashboard/public/index.html +8 -6
  21. package/dashboard/public/styles.css +176 -97
  22. package/dashboard/routes/auth.ts +38 -0
  23. package/dashboard/server.ts +117 -25
  24. package/dashboard/services/config.ts +26 -0
  25. package/dashboard/services/db.ts +118 -0
  26. package/dashboard/src/canvas/pixel-agent.ts +298 -0
  27. package/dashboard/src/canvas/pixel-sprites.ts +440 -0
  28. package/dashboard/src/canvas/shipyard-effects.ts +367 -0
  29. package/dashboard/src/canvas/shipyard-scene.ts +616 -0
  30. package/dashboard/src/canvas/submarine-layout.ts +267 -0
  31. package/dashboard/src/components/header.ts +8 -7
  32. package/dashboard/src/core/api.ts +5 -0
  33. package/dashboard/src/core/router.ts +1 -0
  34. package/dashboard/src/design/submarine-theme.ts +253 -0
  35. package/dashboard/src/main.ts +2 -0
  36. package/dashboard/src/types/api.ts +12 -1
  37. package/dashboard/src/views/activity.ts +2 -1
  38. package/dashboard/src/views/metrics.ts +69 -1
  39. package/dashboard/src/views/shipyard.ts +39 -0
  40. package/dashboard/types/index.ts +166 -0
  41. package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
  42. package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
  43. package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
  44. package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
  45. package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
  46. package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
  47. package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
  48. package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
  49. package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
  50. package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
  51. package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
  52. package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
  53. package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
  54. package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
  55. package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
  56. package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
  57. package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
  58. package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
  59. package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
  60. package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
  61. package/docs/research/RESEARCH_INDEX.md +439 -0
  62. package/docs/research/RESEARCH_SOURCES.md +440 -0
  63. package/docs/research/RESEARCH_SUMMARY.txt +275 -0
  64. package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
  65. package/package.json +2 -2
  66. package/scripts/lib/adaptive-model.sh +427 -0
  67. package/scripts/lib/adaptive-timeout.sh +316 -0
  68. package/scripts/lib/audit-trail.sh +309 -0
  69. package/scripts/lib/auto-recovery.sh +471 -0
  70. package/scripts/lib/bandit-selector.sh +431 -0
  71. package/scripts/lib/bootstrap.sh +104 -2
  72. package/scripts/lib/causal-graph.sh +455 -0
  73. package/scripts/lib/compat.sh +126 -0
  74. package/scripts/lib/compound-audit.sh +337 -0
  75. package/scripts/lib/constitutional.sh +454 -0
  76. package/scripts/lib/context-budget.sh +359 -0
  77. package/scripts/lib/convergence.sh +594 -0
  78. package/scripts/lib/cost-optimizer.sh +634 -0
  79. package/scripts/lib/daemon-adaptive.sh +14 -2
  80. package/scripts/lib/daemon-dispatch.sh +106 -17
  81. package/scripts/lib/daemon-failure.sh +34 -4
  82. package/scripts/lib/daemon-patrol.sh +25 -4
  83. package/scripts/lib/daemon-poll-github.sh +361 -0
  84. package/scripts/lib/daemon-poll-health.sh +299 -0
  85. package/scripts/lib/daemon-poll.sh +27 -611
  86. package/scripts/lib/daemon-state.sh +119 -66
  87. package/scripts/lib/daemon-triage.sh +10 -0
  88. package/scripts/lib/dod-scorecard.sh +442 -0
  89. package/scripts/lib/error-actionability.sh +300 -0
  90. package/scripts/lib/formal-spec.sh +461 -0
  91. package/scripts/lib/helpers.sh +180 -5
  92. package/scripts/lib/intent-analysis.sh +409 -0
  93. package/scripts/lib/loop-convergence.sh +350 -0
  94. package/scripts/lib/loop-iteration.sh +682 -0
  95. package/scripts/lib/loop-progress.sh +48 -0
  96. package/scripts/lib/loop-restart.sh +185 -0
  97. package/scripts/lib/memory-effectiveness.sh +506 -0
  98. package/scripts/lib/mutation-executor.sh +352 -0
  99. package/scripts/lib/outcome-feedback.sh +521 -0
  100. package/scripts/lib/pipeline-cli.sh +336 -0
  101. package/scripts/lib/pipeline-commands.sh +1216 -0
  102. package/scripts/lib/pipeline-detection.sh +101 -3
  103. package/scripts/lib/pipeline-execution.sh +897 -0
  104. package/scripts/lib/pipeline-github.sh +28 -3
  105. package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
  106. package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
  107. package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
  108. package/scripts/lib/pipeline-intelligence.sh +104 -1138
  109. package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
  110. package/scripts/lib/pipeline-quality-checks.sh +17 -711
  111. package/scripts/lib/pipeline-quality-gates.sh +563 -0
  112. package/scripts/lib/pipeline-stages-build.sh +730 -0
  113. package/scripts/lib/pipeline-stages-delivery.sh +965 -0
  114. package/scripts/lib/pipeline-stages-intake.sh +1133 -0
  115. package/scripts/lib/pipeline-stages-monitor.sh +407 -0
  116. package/scripts/lib/pipeline-stages-review.sh +1022 -0
  117. package/scripts/lib/pipeline-stages.sh +161 -2901
  118. package/scripts/lib/pipeline-state.sh +36 -5
  119. package/scripts/lib/pipeline-util.sh +487 -0
  120. package/scripts/lib/policy-learner.sh +438 -0
  121. package/scripts/lib/process-reward.sh +493 -0
  122. package/scripts/lib/project-detect.sh +649 -0
  123. package/scripts/lib/quality-profile.sh +334 -0
  124. package/scripts/lib/recruit-commands.sh +885 -0
  125. package/scripts/lib/recruit-learning.sh +739 -0
  126. package/scripts/lib/recruit-roles.sh +648 -0
  127. package/scripts/lib/reward-aggregator.sh +458 -0
  128. package/scripts/lib/rl-optimizer.sh +362 -0
  129. package/scripts/lib/root-cause.sh +427 -0
  130. package/scripts/lib/scope-enforcement.sh +445 -0
  131. package/scripts/lib/session-restart.sh +493 -0
  132. package/scripts/lib/skill-memory.sh +300 -0
  133. package/scripts/lib/skill-registry.sh +775 -0
  134. package/scripts/lib/spec-driven.sh +476 -0
  135. package/scripts/lib/test-helpers.sh +18 -7
  136. package/scripts/lib/test-holdout.sh +429 -0
  137. package/scripts/lib/test-optimizer.sh +511 -0
  138. package/scripts/shipwright-file-suggest.sh +45 -0
  139. package/scripts/skills/adversarial-quality.md +61 -0
  140. package/scripts/skills/api-design.md +44 -0
  141. package/scripts/skills/architecture-design.md +50 -0
  142. package/scripts/skills/brainstorming.md +43 -0
  143. package/scripts/skills/data-pipeline.md +44 -0
  144. package/scripts/skills/deploy-safety.md +64 -0
  145. package/scripts/skills/documentation.md +38 -0
  146. package/scripts/skills/frontend-design.md +45 -0
  147. package/scripts/skills/generated/.gitkeep +0 -0
  148. package/scripts/skills/generated/_refinements/.gitkeep +0 -0
  149. package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
  150. package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
  151. package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
  152. package/scripts/skills/generated/cli-version-management.md +29 -0
  153. package/scripts/skills/generated/collection-system-validation.md +99 -0
  154. package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
  155. package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
  156. package/scripts/skills/generated/test-parallelization-detection.md +65 -0
  157. package/scripts/skills/observability.md +79 -0
  158. package/scripts/skills/performance.md +48 -0
  159. package/scripts/skills/pr-quality.md +49 -0
  160. package/scripts/skills/product-thinking.md +43 -0
  161. package/scripts/skills/security-audit.md +49 -0
  162. package/scripts/skills/systematic-debugging.md +40 -0
  163. package/scripts/skills/testing-strategy.md +47 -0
  164. package/scripts/skills/two-stage-review.md +52 -0
  165. package/scripts/skills/validation-thoroughness.md +55 -0
  166. package/scripts/sw +9 -3
  167. package/scripts/sw-activity.sh +9 -8
  168. package/scripts/sw-adaptive.sh +8 -7
  169. package/scripts/sw-adversarial.sh +2 -1
  170. package/scripts/sw-architecture-enforcer.sh +3 -1
  171. package/scripts/sw-auth.sh +12 -2
  172. package/scripts/sw-autonomous.sh +5 -1
  173. package/scripts/sw-changelog.sh +4 -1
  174. package/scripts/sw-checkpoint.sh +2 -1
  175. package/scripts/sw-ci.sh +15 -6
  176. package/scripts/sw-cleanup.sh +4 -26
  177. package/scripts/sw-code-review.sh +45 -20
  178. package/scripts/sw-connect.sh +2 -1
  179. package/scripts/sw-context.sh +2 -1
  180. package/scripts/sw-cost.sh +107 -5
  181. package/scripts/sw-daemon.sh +71 -11
  182. package/scripts/sw-dashboard.sh +3 -1
  183. package/scripts/sw-db.sh +71 -20
  184. package/scripts/sw-decide.sh +8 -2
  185. package/scripts/sw-decompose.sh +360 -17
  186. package/scripts/sw-deps.sh +4 -1
  187. package/scripts/sw-developer-simulation.sh +4 -1
  188. package/scripts/sw-discovery.sh +378 -5
  189. package/scripts/sw-doc-fleet.sh +4 -1
  190. package/scripts/sw-docs-agent.sh +3 -1
  191. package/scripts/sw-docs.sh +2 -1
  192. package/scripts/sw-doctor.sh +453 -2
  193. package/scripts/sw-dora.sh +4 -1
  194. package/scripts/sw-durable.sh +12 -7
  195. package/scripts/sw-e2e-orchestrator.sh +17 -16
  196. package/scripts/sw-eventbus.sh +13 -4
  197. package/scripts/sw-evidence.sh +364 -12
  198. package/scripts/sw-feedback.sh +550 -9
  199. package/scripts/sw-fix.sh +20 -1
  200. package/scripts/sw-fleet-discover.sh +6 -2
  201. package/scripts/sw-fleet-viz.sh +9 -4
  202. package/scripts/sw-fleet.sh +5 -1
  203. package/scripts/sw-github-app.sh +18 -4
  204. package/scripts/sw-github-checks.sh +3 -2
  205. package/scripts/sw-github-deploy.sh +3 -2
  206. package/scripts/sw-github-graphql.sh +18 -7
  207. package/scripts/sw-guild.sh +5 -1
  208. package/scripts/sw-heartbeat.sh +5 -30
  209. package/scripts/sw-hello.sh +67 -0
  210. package/scripts/sw-hygiene.sh +10 -3
  211. package/scripts/sw-incident.sh +273 -5
  212. package/scripts/sw-init.sh +18 -2
  213. package/scripts/sw-instrument.sh +10 -2
  214. package/scripts/sw-intelligence.sh +44 -7
  215. package/scripts/sw-jira.sh +5 -1
  216. package/scripts/sw-launchd.sh +2 -1
  217. package/scripts/sw-linear.sh +4 -1
  218. package/scripts/sw-logs.sh +4 -1
  219. package/scripts/sw-loop.sh +436 -1076
  220. package/scripts/sw-memory.sh +357 -3
  221. package/scripts/sw-mission-control.sh +6 -1
  222. package/scripts/sw-model-router.sh +483 -27
  223. package/scripts/sw-otel.sh +15 -4
  224. package/scripts/sw-oversight.sh +14 -5
  225. package/scripts/sw-patrol-meta.sh +334 -0
  226. package/scripts/sw-pipeline-composer.sh +7 -1
  227. package/scripts/sw-pipeline-vitals.sh +12 -6
  228. package/scripts/sw-pipeline.sh +54 -2653
  229. package/scripts/sw-pm.sh +16 -8
  230. package/scripts/sw-pr-lifecycle.sh +2 -1
  231. package/scripts/sw-predictive.sh +17 -5
  232. package/scripts/sw-prep.sh +185 -2
  233. package/scripts/sw-ps.sh +5 -25
  234. package/scripts/sw-public-dashboard.sh +17 -4
  235. package/scripts/sw-quality.sh +14 -6
  236. package/scripts/sw-reaper.sh +8 -25
  237. package/scripts/sw-recruit.sh +156 -2303
  238. package/scripts/sw-regression.sh +19 -12
  239. package/scripts/sw-release-manager.sh +3 -1
  240. package/scripts/sw-release.sh +4 -1
  241. package/scripts/sw-remote.sh +3 -1
  242. package/scripts/sw-replay.sh +7 -1
  243. package/scripts/sw-retro.sh +158 -1
  244. package/scripts/sw-review-rerun.sh +3 -1
  245. package/scripts/sw-scale.sh +14 -5
  246. package/scripts/sw-security-audit.sh +6 -1
  247. package/scripts/sw-self-optimize.sh +173 -6
  248. package/scripts/sw-session.sh +9 -3
  249. package/scripts/sw-setup.sh +3 -1
  250. package/scripts/sw-stall-detector.sh +406 -0
  251. package/scripts/sw-standup.sh +15 -7
  252. package/scripts/sw-status.sh +3 -1
  253. package/scripts/sw-strategic.sh +14 -6
  254. package/scripts/sw-stream.sh +13 -4
  255. package/scripts/sw-swarm.sh +20 -7
  256. package/scripts/sw-team-stages.sh +13 -6
  257. package/scripts/sw-templates.sh +7 -31
  258. package/scripts/sw-testgen.sh +17 -6
  259. package/scripts/sw-tmux-pipeline.sh +4 -1
  260. package/scripts/sw-tmux-role-color.sh +2 -0
  261. package/scripts/sw-tmux-status.sh +1 -1
  262. package/scripts/sw-tmux.sh +37 -1
  263. package/scripts/sw-trace.sh +3 -1
  264. package/scripts/sw-tracker-github.sh +3 -0
  265. package/scripts/sw-tracker-jira.sh +3 -0
  266. package/scripts/sw-tracker-linear.sh +3 -0
  267. package/scripts/sw-tracker.sh +3 -1
  268. package/scripts/sw-triage.sh +3 -2
  269. package/scripts/sw-upgrade.sh +3 -1
  270. package/scripts/sw-ux.sh +5 -2
  271. package/scripts/sw-webhook.sh +5 -2
  272. package/scripts/sw-widgets.sh +9 -4
  273. package/scripts/sw-worktree.sh +15 -3
  274. package/scripts/test-skill-injection.sh +1233 -0
  275. package/templates/pipelines/autonomous.json +27 -3
  276. package/templates/pipelines/cost-aware.json +34 -8
  277. package/templates/pipelines/deployed.json +12 -0
  278. package/templates/pipelines/enterprise.json +12 -0
  279. package/templates/pipelines/fast.json +6 -0
  280. package/templates/pipelines/full.json +27 -3
  281. package/templates/pipelines/hotfix.json +6 -0
  282. package/templates/pipelines/standard.json +12 -0
  283. package/templates/pipelines/tdd.json +12 -0
@@ -0,0 +1,648 @@
1
+ #!/usr/bin/env bash
2
+ # shellcheck disable=SC2034,SC2064
3
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
4
+ # ║ lib/recruit-roles.sh — Role Management, Creation, Matching, Evaluation ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+
7
+ [[ -n "${_RECRUIT_ROLES_LOADED:-}" ]] && return 0
8
+ _RECRUIT_ROLES_LOADED=1
9
+
10
+ SCRIPT_DIR="${SCRIPT_DIR:-.}"
11
+ RECRUIT_ROOT="${RECRUIT_ROOT:-${HOME}/.shipwright/recruitment}"
12
+ ROLES_DB="${ROLES_DB:-${RECRUIT_ROOT}/roles.json}"
13
+ PROFILES_DB="${PROFILES_DB:-${RECRUIT_ROOT}/profiles.json}"
14
+ MATCH_HISTORY="${MATCH_HISTORY:-${RECRUIT_ROOT}/match-history.jsonl}"
15
+ ROLE_USAGE_DB="${ROLE_USAGE_DB:-${RECRUIT_ROOT}/role-usage.json}"
16
+ HEURISTICS_DB="${HEURISTICS_DB:-${RECRUIT_ROOT}/heuristics.json}"
17
+
18
+ # Fallback color/output helpers
19
+ [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
20
+ [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
21
+ [[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
22
+ [[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
23
+
24
+ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
25
+ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
26
+ now_epoch() { date +%s; }
27
+ fi
28
+
29
+ # Fallback for color codes
30
+ CYAN="${CYAN:-\033[38;2;0;212;255m}"
31
+ RESET="${RESET:-\033[0m}"
32
+ BOLD="${BOLD:-\033[1m}"
33
+ DIM="${DIM:-\033[2m}"
34
+ PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
35
+
36
+ # ═══════════════════════════════════════════════════════════════════════════════
37
+ # BUILT-IN ROLE DEFINITIONS
38
+ # ═══════════════════════════════════════════════════════════════════════════════
39
+
40
+ initialize_builtin_roles() {
41
+ ensure_recruit_dir
42
+
43
+ if jq -e '.architect' "$ROLES_DB" >/dev/null 2>&1; then
44
+ return 0
45
+ fi
46
+
47
+ local roles_json
48
+ roles_json=$(cat <<'EOF'
49
+ {
50
+ "architect": {
51
+ "title": "Architect",
52
+ "description": "System design, architecture decisions, scalability planning",
53
+ "required_skills": ["system-design", "technology-evaluation", "code-review", "documentation"],
54
+ "recommended_model": "opus",
55
+ "context_needs": ["codebase-architecture", "system-patterns", "past-designs", "dependency-graph"],
56
+ "success_metrics": ["design-quality", "implementation-feasibility", "team-alignment"],
57
+ "estimated_cost_per_task_usd": 2.5,
58
+ "origin": "builtin",
59
+ "created_at": "2025-01-01T00:00:00Z"
60
+ },
61
+ "builder": {
62
+ "title": "Builder",
63
+ "description": "Feature implementation, core development, code generation",
64
+ "required_skills": ["coding", "testing", "debugging", "performance-optimization"],
65
+ "recommended_model": "sonnet",
66
+ "context_needs": ["codebase-structure", "api-specs", "test-patterns", "build-system"],
67
+ "success_metrics": ["tests-passing", "code-quality", "productivity", "bug-rate"],
68
+ "estimated_cost_per_task_usd": 1.5,
69
+ "origin": "builtin",
70
+ "created_at": "2025-01-01T00:00:00Z"
71
+ },
72
+ "reviewer": {
73
+ "title": "Code Reviewer",
74
+ "description": "Code review, quality assurance, best practices enforcement",
75
+ "required_skills": ["code-review", "static-analysis", "security-review", "best-practices"],
76
+ "recommended_model": "sonnet",
77
+ "context_needs": ["coding-standards", "previous-reviews", "common-errors", "team-patterns"],
78
+ "success_metrics": ["review-quality", "issue-detection-rate", "feedback-clarity"],
79
+ "estimated_cost_per_task_usd": 1.2,
80
+ "origin": "builtin",
81
+ "created_at": "2025-01-01T00:00:00Z"
82
+ },
83
+ "tester": {
84
+ "title": "Test Specialist",
85
+ "description": "Test strategy, test case generation, test automation, quality validation",
86
+ "required_skills": ["testing", "coverage-analysis", "automation", "edge-case-detection"],
87
+ "recommended_model": "sonnet",
88
+ "context_needs": ["test-framework", "coverage-metrics", "failure-patterns", "requirements"],
89
+ "success_metrics": ["coverage-increase", "bug-detection", "test-execution-time"],
90
+ "estimated_cost_per_task_usd": 1.2,
91
+ "origin": "builtin",
92
+ "created_at": "2025-01-01T00:00:00Z"
93
+ },
94
+ "security-auditor": {
95
+ "title": "Security Auditor",
96
+ "description": "Security analysis, vulnerability detection, compliance verification",
97
+ "required_skills": ["security-analysis", "threat-modeling", "penetration-testing", "compliance"],
98
+ "recommended_model": "opus",
99
+ "context_needs": ["security-policies", "vulnerability-database", "threat-models", "compliance-reqs"],
100
+ "success_metrics": ["vulnerabilities-found", "severity-accuracy", "remediation-quality"],
101
+ "estimated_cost_per_task_usd": 2.0,
102
+ "origin": "builtin",
103
+ "created_at": "2025-01-01T00:00:00Z"
104
+ },
105
+ "docs-writer": {
106
+ "title": "Documentation Writer",
107
+ "description": "Documentation creation, API docs, user guides, onboarding materials",
108
+ "required_skills": ["documentation", "clarity", "completeness", "example-generation"],
109
+ "recommended_model": "haiku",
110
+ "context_needs": ["codebase-knowledge", "api-specs", "user-personas", "doc-templates"],
111
+ "success_metrics": ["documentation-completeness", "clarity-score", "example-coverage"],
112
+ "estimated_cost_per_task_usd": 0.8,
113
+ "origin": "builtin",
114
+ "created_at": "2025-01-01T00:00:00Z"
115
+ },
116
+ "optimizer": {
117
+ "title": "Performance Optimizer",
118
+ "description": "Performance analysis, optimization, profiling, efficiency improvements",
119
+ "required_skills": ["performance-analysis", "profiling", "optimization", "metrics-analysis"],
120
+ "recommended_model": "sonnet",
121
+ "context_needs": ["performance-benchmarks", "profiling-tools", "optimization-history"],
122
+ "success_metrics": ["performance-gain", "memory-efficiency", "latency-reduction"],
123
+ "estimated_cost_per_task_usd": 1.5,
124
+ "origin": "builtin",
125
+ "created_at": "2025-01-01T00:00:00Z"
126
+ },
127
+ "devops": {
128
+ "title": "DevOps Engineer",
129
+ "description": "Infrastructure, deployment pipelines, CI/CD, monitoring, reliability",
130
+ "required_skills": ["infrastructure-as-code", "deployment", "monitoring", "incident-response"],
131
+ "recommended_model": "sonnet",
132
+ "context_needs": ["infrastructure-config", "deployment-pipelines", "monitoring-setup", "runbooks"],
133
+ "success_metrics": ["deployment-success-rate", "incident-response-time", "uptime"],
134
+ "estimated_cost_per_task_usd": 1.8,
135
+ "origin": "builtin",
136
+ "created_at": "2025-01-01T00:00:00Z"
137
+ },
138
+ "pm": {
139
+ "title": "Project Manager",
140
+ "description": "Task decomposition, priority management, stakeholder communication, tracking",
141
+ "required_skills": ["task-decomposition", "prioritization", "communication", "planning"],
142
+ "recommended_model": "sonnet",
143
+ "context_needs": ["project-state", "requirements", "team-capacity", "past-estimates"],
144
+ "success_metrics": ["estimation-accuracy", "deadline-met", "scope-management"],
145
+ "estimated_cost_per_task_usd": 1.0,
146
+ "origin": "builtin",
147
+ "created_at": "2025-01-01T00:00:00Z"
148
+ },
149
+ "incident-responder": {
150
+ "title": "Incident Responder",
151
+ "description": "Crisis management, root cause analysis, rapid issue resolution, hotfixes",
152
+ "required_skills": ["crisis-management", "root-cause-analysis", "debugging", "communication"],
153
+ "recommended_model": "opus",
154
+ "context_needs": ["incident-history", "system-health", "alerting-rules", "past-incidents"],
155
+ "success_metrics": ["incident-resolution-time", "accuracy", "escalation-prevention"],
156
+ "estimated_cost_per_task_usd": 2.0,
157
+ "origin": "builtin",
158
+ "created_at": "2025-01-01T00:00:00Z"
159
+ }
160
+ }
161
+ EOF
162
+ )
163
+ local _tmp_roles
164
+ _tmp_roles=$(mktemp)
165
+ trap "rm -f '$_tmp_roles'" RETURN
166
+ if echo "$roles_json" | jq '.' > "$_tmp_roles" 2>/dev/null && [[ -s "$_tmp_roles" ]]; then
167
+ mv "$_tmp_roles" "$ROLES_DB"
168
+ else
169
+ rm -f "$_tmp_roles"
170
+ error "Failed to initialize roles DB"
171
+ return 1
172
+ fi
173
+ success "Initialized 10 built-in agent roles"
174
+ }
175
+
176
+ # ═══════════════════════════════════════════════════════════════════════════════
177
+ # LLM-POWERED SEMANTIC MATCHING (Tier 1)
178
+ # ═══════════════════════════════════════════════════════════════════════════════
179
+
180
+ _recruit_keyword_match() {
181
+ local task_description="$1"
182
+ local detected_skills=""
183
+
184
+ # Always run built-in regex patterns first (most reliable)
185
+ [[ "$task_description" =~ (architecture|design|scalability) ]] && detected_skills="${detected_skills}architect "
186
+ [[ "$task_description" =~ (build|feature|implement|code) ]] && detected_skills="${detected_skills}builder "
187
+ [[ "$task_description" =~ (review|quality|best.practice) ]] && detected_skills="${detected_skills}reviewer "
188
+ [[ "$task_description" =~ (test|coverage|automation) ]] && detected_skills="${detected_skills}tester "
189
+ [[ "$task_description" =~ (security|vulnerability|compliance) ]] && detected_skills="${detected_skills}security-auditor "
190
+ [[ "$task_description" =~ (document|guide|readme|api.doc|write.doc) ]] && detected_skills="${detected_skills}docs-writer "
191
+ [[ "$task_description" =~ (performance|optimization|profile|speed|latency|faster) ]] && detected_skills="${detected_skills}optimizer "
192
+ [[ "$task_description" =~ (deploy|infra|ci.cd|monitoring|docker|kubernetes) ]] && detected_skills="${detected_skills}devops "
193
+ [[ "$task_description" =~ (plan|decompose|estimate|priorit) ]] && detected_skills="${detected_skills}pm "
194
+ [[ "$task_description" =~ (urgent|incident|crisis|hotfix|outage) ]] && detected_skills="${detected_skills}incident-responder "
195
+
196
+ # Boost with learned keyword weights (override only if no regex match)
197
+ if [[ -z "$detected_skills" && -f "$HEURISTICS_DB" ]]; then
198
+ local learned_weights
199
+ learned_weights=$(jq -r '.keyword_weights // {}' "$HEURISTICS_DB" 2>/dev/null || echo "{}")
200
+
201
+ if [[ -n "$learned_weights" && "$learned_weights" != "{}" && "$learned_weights" != "null" ]]; then
202
+ local best_role="" best_score=0
203
+ local task_lower
204
+ task_lower=$(echo "$task_description" | tr '[:upper:]' '[:lower:]')
205
+
206
+ while IFS= read -r keyword; do
207
+ [[ -z "$keyword" ]] && continue
208
+ local kw_lower
209
+ kw_lower=$(echo "$keyword" | tr '[:upper:]' '[:lower:]')
210
+ if echo "$task_lower" | grep -q "$kw_lower" 2>/dev/null; then
211
+ local role_score
212
+ role_score=$(echo "$learned_weights" | jq -r --arg k "$keyword" '.[$k] | if type == "object" then .role else "" end' 2>/dev/null || echo "")
213
+ local weight
214
+ weight=$(echo "$learned_weights" | jq -r --arg k "$keyword" '.[$k] | if type == "object" then .weight else (. // 0) end' 2>/dev/null || echo "0")
215
+
216
+ if [[ -n "$role_score" && "$role_score" != "null" && "$role_score" != "" ]]; then
217
+ if awk -v w="$weight" -v b="$best_score" 'BEGIN{exit !(w > b)}' 2>/dev/null; then
218
+ best_role="$role_score"
219
+ best_score="$weight"
220
+ fi
221
+ fi
222
+ fi
223
+ done < <(echo "$learned_weights" | jq -r 'keys[]' 2>/dev/null || true)
224
+
225
+ if [[ -n "$best_role" ]]; then
226
+ detected_skills="$best_role"
227
+ fi
228
+ fi
229
+ fi
230
+
231
+ # Default to builder if no match
232
+ if [[ -z "$detected_skills" ]]; then
233
+ detected_skills="builder"
234
+ fi
235
+
236
+ echo "$detected_skills"
237
+ }
238
+
239
+ # LLM-powered semantic matching
240
+ _recruit_llm_match() {
241
+ local task_description="$1"
242
+ local available_roles="$2"
243
+
244
+ local prompt
245
+ prompt="You are an agent recruitment system. Given a task description, select the best role(s) from the available roles.
246
+
247
+ Task: ${task_description}
248
+
249
+ Available roles (JSON):
250
+ ${available_roles}
251
+
252
+ Return ONLY a JSON object with:
253
+ {\"primary_role\": \"<role_key>\", \"secondary_roles\": [\"<role_key>\", ...], \"confidence\": <0.0-1.0>, \"reasoning\": \"<one line>\", \"new_role_needed\": false, \"suggested_role\": null}
254
+
255
+ If NO existing role is a good fit, set new_role_needed=true and provide:
256
+ {\"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}}
257
+
258
+ Return JSON only, no markdown fences."
259
+
260
+ local result
261
+ result=$(_recruit_call_claude "$prompt")
262
+
263
+ if [[ -n "$result" ]] && echo "$result" | jq -e '.primary_role' >/dev/null 2>&1; then
264
+ echo "$result"
265
+ return 0
266
+ fi
267
+
268
+ echo ""
269
+ }
270
+
271
+ # Record a match for learning
272
+ _recruit_record_match() {
273
+ local task="$1"
274
+ local role="$2"
275
+ local method="$3"
276
+ local confidence="${4:-0.5}"
277
+ local agent_id="${5:-}"
278
+
279
+ mkdir -p "$RECRUIT_ROOT"
280
+ local match_epoch
281
+ match_epoch=$(now_epoch)
282
+ local match_id="match-${match_epoch}-$$"
283
+
284
+ local record
285
+ record=$(jq -c -n \
286
+ --arg ts "$(now_iso)" \
287
+ --argjson epoch "$match_epoch" \
288
+ --arg match_id "$match_id" \
289
+ --arg task "$task" \
290
+ --arg role "$role" \
291
+ --arg method "$method" \
292
+ --argjson conf "$confidence" \
293
+ --arg agent "$agent_id" \
294
+ '{ts: $ts, ts_epoch: $epoch, match_id: $match_id, task: $task, role: $role, method: $method, confidence: $conf, agent_id: $agent, outcome: null}')
295
+ echo "$record" >> "$MATCH_HISTORY"
296
+
297
+ # Enforce max match history size (from policy)
298
+ local max_history="${RECRUIT_MAX_MATCH_HISTORY:-5000}"
299
+ local current_lines
300
+ current_lines=$(wc -l < "$MATCH_HISTORY" 2>/dev/null | tr -d ' ')
301
+ if [[ "$current_lines" -gt "$max_history" ]]; then
302
+ local tmp_trunc
303
+ tmp_trunc=$(mktemp)
304
+ trap "rm -f '$tmp_trunc'" RETURN
305
+ tail -n "$max_history" "$MATCH_HISTORY" > "$tmp_trunc" && _recruit_locked_write "$MATCH_HISTORY" "$tmp_trunc" || rm -f "$tmp_trunc"
306
+ fi
307
+
308
+ # Update role usage stats
309
+ _recruit_track_role_usage "$role" "match"
310
+
311
+ # Store match_id in global for callers (avoids stdout contamination)
312
+ LAST_MATCH_ID="$match_id"
313
+ }
314
+
315
+ # ═══════════════════════════════════════════════════════════════════════════════
316
+ # DYNAMIC ROLE CREATION (Tier 1)
317
+ # ═══════════════════════════════════════════════════════════════════════════════
318
+
319
+ cmd_create_role() {
320
+ local role_key="${1:-}"
321
+ local role_title="${2:-}"
322
+ local role_desc="${3:-}"
323
+
324
+ if [[ -z "$role_key" ]]; then
325
+ error "Usage: shipwright recruit create-role <key> [title] [description]"
326
+ echo " Or use: shipwright recruit create-role --auto \"<task description>\""
327
+ exit 1
328
+ fi
329
+
330
+ ensure_recruit_dir
331
+ initialize_builtin_roles
332
+
333
+ # Auto-generate via LLM if --auto flag
334
+ if [[ "$role_key" == "--auto" ]]; then
335
+ local task_desc="${role_title:-$role_desc}"
336
+ if [[ -z "$task_desc" ]]; then
337
+ error "Usage: shipwright recruit create-role --auto \"<task description>\""
338
+ exit 1
339
+ fi
340
+
341
+ info "Generating role definition via AI for: ${CYAN}${task_desc}${RESET}"
342
+
343
+ local existing_roles
344
+ existing_roles=$(jq -r 'keys | join(", ")' "$ROLES_DB" 2>/dev/null || echo "none")
345
+
346
+ local prompt
347
+ prompt="Create a new agent role definition for a task that doesn't fit existing roles.
348
+
349
+ Task description: ${task_desc}
350
+ Existing roles: ${existing_roles}
351
+
352
+ Return ONLY a JSON object:
353
+ {\"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}
354
+
355
+ Return JSON only."
356
+
357
+ local result
358
+ result=$(_recruit_call_claude "$prompt")
359
+
360
+ if [[ -n "$result" ]] && echo "$result" | jq -e '.key' >/dev/null 2>&1; then
361
+ role_key=$(echo "$result" | jq -r '.key')
362
+ role_title=$(echo "$result" | jq -r '.title')
363
+ role_desc=$(echo "$result" | jq -r '.description')
364
+
365
+ # Add origin and timestamp
366
+ result=$(echo "$result" | jq --arg ts "$(now_iso)" '. + {origin: "ai-generated", created_at: $ts}')
367
+
368
+ # Persist to roles DB
369
+ local tmp_file
370
+ tmp_file=$(mktemp)
371
+ trap "rm -f '$tmp_file'" RETURN
372
+ if jq --arg key "$role_key" --argjson role "$(echo "$result" | jq 'del(.key)')" '.[$key] = $role' "$ROLES_DB" > "$tmp_file"; then
373
+ _recruit_locked_write "$ROLES_DB" "$tmp_file"
374
+ else
375
+ rm -f "$tmp_file"
376
+ error "Failed to save role to database"
377
+ return 1
378
+ fi
379
+
380
+ # Log the invention
381
+ echo "$result" | jq -c --arg trigger "$task_desc" '. + {trigger: $trigger}' >> "$INVENTED_ROLES_LOG" 2>/dev/null || true
382
+
383
+ success "Created AI-generated role: ${CYAN}${role_key}${RESET} — ${role_title}"
384
+ echo " ${role_desc}"
385
+ emit_event "recruit_role_created" "role=${role_key}" "method=ai" "title=${role_title}"
386
+ return 0
387
+ else
388
+ warn "AI generation failed, falling back to manual creation"
389
+ fi
390
+
391
+ # Generate a slug from the task description for the fallback key
392
+ role_key="custom-$(echo "$task_desc" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//;s/-$//' | cut -c1-50)"
393
+ role_title="$task_desc"
394
+ role_desc="Auto-created role for: ${task_desc}"
395
+ fi
396
+
397
+ # Manual role creation
398
+ if [[ -z "$role_title" ]]; then
399
+ role_title="$role_key"
400
+ fi
401
+ if [[ -z "$role_desc" ]]; then
402
+ role_desc="Custom role: ${role_title}"
403
+ fi
404
+
405
+ local role_json
406
+ role_json=$(jq -n \
407
+ --arg title "$role_title" \
408
+ --arg desc "$role_desc" \
409
+ --arg ts "$(now_iso)" \
410
+ '{
411
+ title: $title,
412
+ description: $desc,
413
+ required_skills: ["general"],
414
+ recommended_model: "sonnet",
415
+ context_needs: ["codebase-structure"],
416
+ success_metrics: ["task-completion"],
417
+ estimated_cost_per_task_usd: 1.5,
418
+ origin: "manual",
419
+ created_at: $ts
420
+ }')
421
+
422
+ local tmp_file
423
+ tmp_file=$(mktemp)
424
+ trap "rm -f '$tmp_file'" RETURN
425
+ if jq --arg key "$role_key" --argjson role "$role_json" '.[$key] = $role' "$ROLES_DB" > "$tmp_file"; then
426
+ _recruit_locked_write "$ROLES_DB" "$tmp_file"
427
+ else
428
+ rm -f "$tmp_file"
429
+ error "Failed to save role to database"
430
+ return 1
431
+ fi
432
+
433
+ success "Created role: ${CYAN}${role_key}${RESET} — ${role_title}"
434
+ emit_event "recruit_role_created" "role=${role_key}" "method=manual" "title=${role_title}"
435
+ }
436
+
437
+ # ═══════════════════════════════════════════════════════════════════════════════
438
+ # ORIGINAL COMMANDS (enhanced)
439
+ # ═══════════════════════════════════════════════════════════════════════════════
440
+
441
+ cmd_roles() {
442
+ ensure_recruit_dir
443
+ initialize_builtin_roles
444
+
445
+ info "Available Agent Roles ($(jq 'length' "$ROLES_DB" 2>/dev/null || echo "?") total):"
446
+ echo ""
447
+
448
+ jq -r 'to_entries | sort_by(.key) | .[] |
449
+ "\(.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"' \
450
+ "$ROLES_DB"
451
+ }
452
+
453
+ cmd_match() {
454
+ local json_mode=false
455
+ if [[ "${1:-}" == "--json" ]]; then
456
+ json_mode=true
457
+ shift
458
+ fi
459
+ local task_description="${1:-}"
460
+
461
+ if [[ -z "$task_description" ]]; then
462
+ error "Usage: shipwright recruit match [--json] \"<task description>\""
463
+ exit 1
464
+ fi
465
+
466
+ ensure_recruit_dir
467
+ initialize_builtin_roles
468
+
469
+ if ! $json_mode; then
470
+ info "Analyzing task: ${CYAN}${task_description}${RESET}"
471
+ echo ""
472
+ fi
473
+
474
+ local primary_role="" secondary_roles="" confidence=0.5 method="keyword" reasoning=""
475
+
476
+ # Try LLM-powered matching first
477
+ if _recruit_has_claude; then
478
+ local available_roles
479
+ available_roles=$(jq -c '.' "$ROLES_DB" 2>/dev/null || echo "{}")
480
+
481
+ local llm_result
482
+ llm_result=$(_recruit_llm_match "$task_description" "$available_roles")
483
+
484
+ if [[ -n "$llm_result" ]] && echo "$llm_result" | jq -e '.primary_role' >/dev/null 2>&1; then
485
+ primary_role=$(echo "$llm_result" | jq -r '.primary_role')
486
+ secondary_roles=$(echo "$llm_result" | jq -r '.secondary_roles // [] | join(", ")')
487
+ confidence=$(echo "$llm_result" | jq -r '.confidence // 0.8')
488
+ reasoning=$(echo "$llm_result" | jq -r '.reasoning // ""')
489
+ method="llm"
490
+
491
+ # Check if a new role was suggested
492
+ local new_role_needed
493
+ new_role_needed=$(echo "$llm_result" | jq -r '.new_role_needed // false')
494
+ if [[ "$new_role_needed" == "true" ]]; then
495
+ local suggested
496
+ suggested=$(echo "$llm_result" | jq '.suggested_role // null')
497
+ if [[ "$suggested" != "null" ]]; then
498
+ echo ""
499
+ warn "No perfect role match — AI suggests creating a new role:"
500
+ echo " $(echo "$suggested" | jq -r '.title // "Unknown"'): $(echo "$suggested" | jq -r '.description // ""')"
501
+ echo " Run: shipwright recruit create-role --auto \"${task_description}\""
502
+ echo ""
503
+ fi
504
+ fi
505
+ fi
506
+ fi
507
+
508
+ # Fallback to keyword matching
509
+ if [[ -z "$primary_role" ]]; then
510
+ local detected_skills
511
+ detected_skills=$(_recruit_keyword_match "$task_description")
512
+ primary_role=$(echo "$detected_skills" | awk '{print $1}')
513
+ secondary_roles=$(echo "$detected_skills" | cut -d' ' -f2- | tr ' ' ',' | sed 's/,$//')
514
+ method="keyword"
515
+ confidence=0.5
516
+ fi
517
+
518
+ # Validate role exists
519
+ if ! jq -e ".\"${primary_role}\"" "$ROLES_DB" >/dev/null 2>&1; then
520
+ primary_role="builder"
521
+ fi
522
+
523
+ # Record for learning
524
+ _recruit_record_match "$task_description" "$primary_role" "$method" "$confidence"
525
+
526
+ local role_info
527
+ role_info=$(jq ".\"${primary_role}\"" "$ROLES_DB")
528
+ local recommended_model
529
+ recommended_model=$(echo "$role_info" | jq -r '.recommended_model // "sonnet"')
530
+
531
+ # JSON mode: structured output for programmatic consumption
532
+ if $json_mode; then
533
+ jq -c -n \
534
+ --arg role "$primary_role" \
535
+ --arg secondary "$secondary_roles" \
536
+ --argjson confidence "$confidence" \
537
+ --arg method "$method" \
538
+ --arg model "$recommended_model" \
539
+ --arg reasoning "$reasoning" \
540
+ '{
541
+ primary_role: $role,
542
+ secondary_roles: ($secondary | split(", ") | map(select(. != ""))),
543
+ confidence: $confidence,
544
+ method: $method,
545
+ model: $model,
546
+ reasoning: $reasoning
547
+ }'
548
+ return 0
549
+ fi
550
+
551
+ success "Recommended role: ${CYAN}${primary_role}${RESET} ${DIM}(confidence: $(awk -v c="$confidence" 'BEGIN{printf "%.0f", c*100}')%, method: ${method})${RESET}"
552
+ [[ -n "$reasoning" ]] && echo -e " ${DIM}${reasoning}${RESET}"
553
+ echo ""
554
+
555
+ echo " $(echo "$role_info" | jq -r '.description')"
556
+ echo " Model: ${recommended_model}"
557
+ echo " Skills: $(echo "$role_info" | jq -r '.required_skills | join(", ")')"
558
+
559
+ if [[ -n "$secondary_roles" && "$secondary_roles" != "null" ]]; then
560
+ echo ""
561
+ warn "Secondary roles: ${secondary_roles}"
562
+ fi
563
+ }
564
+
565
+ cmd_evaluate() {
566
+ local agent_id="${1:-}"
567
+
568
+ if [[ -z "$agent_id" ]]; then
569
+ error "Usage: shipwright recruit evaluate <agent-id>"
570
+ exit 1
571
+ fi
572
+
573
+ ensure_recruit_dir
574
+
575
+ info "Evaluating agent: ${CYAN}${agent_id}${RESET}"
576
+ echo ""
577
+
578
+ local profile
579
+ profile=$(jq ".\"${agent_id}\"" "$PROFILES_DB" 2>/dev/null || echo "{}")
580
+
581
+ if [[ "$profile" == "{}" || "$profile" == "null" ]]; then
582
+ warn "No evaluation history for ${agent_id}"
583
+ return 0
584
+ fi
585
+
586
+ echo "Performance Metrics:"
587
+ echo " Success Rate: $(echo "$profile" | jq -r '.success_rate // "N/A"')%"
588
+ echo " Avg Time: $(echo "$profile" | jq -r '.avg_time_minutes // "N/A"') minutes"
589
+ echo " Quality Score: $(echo "$profile" | jq -r '.quality_score // "N/A"')/10"
590
+ echo " Cost Efficiency: $(echo "$profile" | jq -r '.cost_efficiency // "N/A"')%"
591
+ echo " Tasks Completed: $(echo "$profile" | jq -r '.tasks_completed // "0"')"
592
+ echo ""
593
+
594
+ # Use population-aware thresholds for performance evaluation
595
+ local pop_stats
596
+ pop_stats=$(_recruit_compute_population_stats)
597
+ local mean_success
598
+ mean_success=$(echo "$pop_stats" | jq -r '.mean_success')
599
+ local stddev
600
+ stddev=$(echo "$pop_stats" | jq -r '.stddev_success')
601
+ local agent_count
602
+ agent_count=$(echo "$pop_stats" | jq -r '.count')
603
+
604
+ local success_rate
605
+ success_rate=$(echo "$profile" | jq -r '.success_rate // 0')
606
+
607
+ if [[ "$agent_count" -ge 3 ]]; then
608
+ # Population-aware evaluation
609
+ local promote_threshold demote_threshold
610
+ promote_threshold=$(awk -v m="$mean_success" -v s="$stddev" 'BEGIN{v=m+s; if(v>95) v=95; printf "%.0f", v}')
611
+ demote_threshold=$(awk -v m="$mean_success" -v s="$stddev" 'BEGIN{v=m-s; if(v<40) v=40; printf "%.0f", v}')
612
+
613
+ echo -e " ${DIM}Population thresholds (${agent_count} agents): promote ≥${promote_threshold}%, demote <${demote_threshold}%${RESET}"
614
+
615
+ if awk -v sr="$success_rate" -v t="$demote_threshold" 'BEGIN{exit !(sr < t)}' 2>/dev/null; then
616
+ warn "Performance below population threshold. Consider downgrading or retraining."
617
+ elif awk -v sr="$success_rate" -v t="$promote_threshold" 'BEGIN{exit !(sr >= t)}' 2>/dev/null; then
618
+ success "Excellent performance (top tier). Consider for promotion."
619
+ else
620
+ success "Acceptable performance. Continue current assignment."
621
+ fi
622
+ else
623
+ # Fallback to fixed thresholds
624
+ if (( $(echo "$success_rate < 70" | bc -l 2>/dev/null || echo "1") )); then
625
+ warn "Performance below threshold. Consider downgrading or retraining."
626
+ elif (( $(echo "$success_rate >= 90" | bc -l 2>/dev/null || echo "0") )); then
627
+ success "Excellent performance. Consider for promotion."
628
+ else
629
+ success "Acceptable performance. Continue current assignment."
630
+ fi
631
+ fi
632
+ }
633
+
634
+ cmd_profiles() {
635
+ ensure_recruit_dir
636
+
637
+ info "Agent Performance Profiles:"
638
+ echo ""
639
+
640
+ if [[ ! -s "$PROFILES_DB" || "$(jq 'length' "$PROFILES_DB" 2>/dev/null || echo 0)" -eq 0 ]]; then
641
+ warn "No performance profiles recorded yet"
642
+ return 0
643
+ fi
644
+
645
+ jq -r 'to_entries | .[] |
646
+ "\(.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"' \
647
+ "$PROFILES_DB"
648
+ }