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