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,739 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# shellcheck disable=SC2034,SC2064
|
|
3
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
4
|
+
# ║ lib/recruit-learning.sh — Learning, Evolution, Meta-Learning, Tuning ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
|
|
7
|
+
[[ -n "${_RECRUIT_LEARNING_LOADED:-}" ]] && return 0
|
|
8
|
+
_RECRUIT_LEARNING_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
|
+
MATCH_HISTORY="${MATCH_HISTORY:-${RECRUIT_ROOT}/match-history.jsonl}"
|
|
14
|
+
ROLE_USAGE_DB="${ROLE_USAGE_DB:-${RECRUIT_ROOT}/role-usage.json}"
|
|
15
|
+
HEURISTICS_DB="${HEURISTICS_DB:-${RECRUIT_ROOT}/heuristics.json}"
|
|
16
|
+
META_LEARNING_DB="${META_LEARNING_DB:-${RECRUIT_ROOT}/meta-learning.json}"
|
|
17
|
+
EVENTS_FILE="${EVENTS_FILE:-${HOME}/.shipwright/events.jsonl}"
|
|
18
|
+
|
|
19
|
+
# Fallback color/output helpers
|
|
20
|
+
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
21
|
+
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
22
|
+
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
|
|
23
|
+
[[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
|
|
24
|
+
|
|
25
|
+
if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
26
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
27
|
+
now_epoch() { date +%s; }
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Fallback for color codes
|
|
31
|
+
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
32
|
+
RESET="${RESET:-\033[0m}"
|
|
33
|
+
BOLD="${BOLD:-\033[1m}"
|
|
34
|
+
DIM="${DIM:-\033[2m}"
|
|
35
|
+
YELLOW="${YELLOW:-\033[38;2;251;204;21m}"
|
|
36
|
+
RED="${RED:-\033[38;2;248;113;113m}"
|
|
37
|
+
|
|
38
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
39
|
+
# CLOSED-LOOP FEEDBACK INTEGRATION (Tier 1)
|
|
40
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
41
|
+
|
|
42
|
+
cmd_record_outcome() {
|
|
43
|
+
local agent_id="${1:-}"
|
|
44
|
+
local task_id="${2:-}"
|
|
45
|
+
local outcome="${3:-}"
|
|
46
|
+
local quality="${4:-}"
|
|
47
|
+
local duration_min="${5:-}"
|
|
48
|
+
|
|
49
|
+
if [[ -z "$agent_id" || -z "$outcome" ]]; then
|
|
50
|
+
error "Usage: shipwright recruit record-outcome <agent-id> <task-id> <success|failure> [quality:0-10] [duration_min]"
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
ensure_recruit_dir
|
|
55
|
+
|
|
56
|
+
# Get or create profile
|
|
57
|
+
local profile
|
|
58
|
+
profile=$(jq ".\"${agent_id}\" // {}" "$PROFILES_DB" 2>/dev/null || echo "{}")
|
|
59
|
+
|
|
60
|
+
local tasks_completed success_count total_time total_quality
|
|
61
|
+
tasks_completed=$(echo "$profile" | jq -r '.tasks_completed // 0')
|
|
62
|
+
success_count=$(echo "$profile" | jq -r '.success_count // 0')
|
|
63
|
+
total_time=$(echo "$profile" | jq -r '.total_time_minutes // 0')
|
|
64
|
+
total_quality=$(echo "$profile" | jq -r '.total_quality // 0')
|
|
65
|
+
local current_model
|
|
66
|
+
current_model=$(echo "$profile" | jq -r '.model // "sonnet"')
|
|
67
|
+
|
|
68
|
+
tasks_completed=$((tasks_completed + 1))
|
|
69
|
+
[[ "$outcome" == "success" ]] && success_count=$((success_count + 1))
|
|
70
|
+
|
|
71
|
+
if [[ -n "$duration_min" && "$duration_min" != "0" ]]; then
|
|
72
|
+
total_time=$(awk -v t="$total_time" -v d="$duration_min" 'BEGIN{printf "%.1f", t + d}')
|
|
73
|
+
fi
|
|
74
|
+
if [[ -n "$quality" && "$quality" != "0" ]]; then
|
|
75
|
+
total_quality=$(awk -v tq="$total_quality" -v q="$quality" 'BEGIN{printf "%.1f", tq + q}')
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
local success_rate avg_time avg_quality cost_efficiency
|
|
79
|
+
success_rate=$(awk -v s="$success_count" -v t="$tasks_completed" 'BEGIN{if(t>0) printf "%.1f", (s/t)*100; else print "0"}')
|
|
80
|
+
avg_time=$(awk -v t="$total_time" -v n="$tasks_completed" 'BEGIN{if(n>0) printf "%.1f", t/n; else print "0"}')
|
|
81
|
+
avg_quality=$(awk -v tq="$total_quality" -v n="$tasks_completed" 'BEGIN{if(n>0) printf "%.1f", tq/n; else print "0"}')
|
|
82
|
+
cost_efficiency=$(awk -v sr="$success_rate" 'BEGIN{printf "%.0f", sr * 0.9}')
|
|
83
|
+
|
|
84
|
+
# Build updated profile with specialization tracking
|
|
85
|
+
local role_assigned
|
|
86
|
+
role_assigned=$(echo "$profile" | jq -r '.role // "builder"')
|
|
87
|
+
|
|
88
|
+
local task_history
|
|
89
|
+
task_history=$(echo "$profile" | jq -r '.task_history // []')
|
|
90
|
+
|
|
91
|
+
# Append to task history (keep last 50)
|
|
92
|
+
local new_entry
|
|
93
|
+
new_entry=$(jq -c -n \
|
|
94
|
+
--arg ts "$(now_iso)" \
|
|
95
|
+
--arg task "$task_id" \
|
|
96
|
+
--arg outcome "$outcome" \
|
|
97
|
+
--argjson quality "${quality:-0}" \
|
|
98
|
+
--argjson duration "${duration_min:-0}" \
|
|
99
|
+
'{ts: $ts, task: $task, outcome: $outcome, quality: $quality, duration: $duration}')
|
|
100
|
+
|
|
101
|
+
local tmp_file
|
|
102
|
+
tmp_file=$(mktemp)
|
|
103
|
+
trap "rm -f '$tmp_file'" RETURN
|
|
104
|
+
jq --arg id "$agent_id" \
|
|
105
|
+
--argjson tc "$tasks_completed" \
|
|
106
|
+
--argjson sc "$success_count" \
|
|
107
|
+
--argjson sr "$success_rate" \
|
|
108
|
+
--argjson at "$avg_time" \
|
|
109
|
+
--argjson aq "$avg_quality" \
|
|
110
|
+
--argjson ce "$cost_efficiency" \
|
|
111
|
+
--argjson tt "$total_time" \
|
|
112
|
+
--argjson tq "$total_quality" \
|
|
113
|
+
--arg model "$current_model" \
|
|
114
|
+
--arg role "$role_assigned" \
|
|
115
|
+
--argjson entry "$new_entry" \
|
|
116
|
+
'.[$id] = {
|
|
117
|
+
tasks_completed: $tc,
|
|
118
|
+
success_count: $sc,
|
|
119
|
+
success_rate: $sr,
|
|
120
|
+
avg_time_minutes: $at,
|
|
121
|
+
quality_score: $aq,
|
|
122
|
+
cost_efficiency: $ce,
|
|
123
|
+
total_time_minutes: $tt,
|
|
124
|
+
total_quality: $tq,
|
|
125
|
+
model: $model,
|
|
126
|
+
role: $role,
|
|
127
|
+
task_history: ((.[$id].task_history // []) + [$entry] | .[-50:]),
|
|
128
|
+
last_updated: (now | todate)
|
|
129
|
+
}' "$PROFILES_DB" > "$tmp_file" && _recruit_locked_write "$PROFILES_DB" "$tmp_file" || { rm -f "$tmp_file"; error "Failed to update profile"; return 1; }
|
|
130
|
+
|
|
131
|
+
success "Recorded ${outcome} for ${CYAN}${agent_id}${RESET} (${tasks_completed} tasks, ${success_rate}% success)"
|
|
132
|
+
emit_event "recruit_outcome" "agent_id=${agent_id}" "outcome=${outcome}" "success_rate=${success_rate}"
|
|
133
|
+
|
|
134
|
+
# Track role usage with outcome (closes the role-usage feedback loop)
|
|
135
|
+
_recruit_track_role_usage "$role_assigned" "$outcome"
|
|
136
|
+
|
|
137
|
+
# Backfill match history with outcome (closes the match→outcome linkage gap)
|
|
138
|
+
if [[ -f "$MATCH_HISTORY" ]]; then
|
|
139
|
+
local tmp_mh
|
|
140
|
+
tmp_mh=$(mktemp)
|
|
141
|
+
trap "rm -f '$tmp_mh'" RETURN
|
|
142
|
+
# Find the most recent match for this agent_id with null outcome, and backfill
|
|
143
|
+
awk -v agent="$agent_id" -v outcome="$outcome" '
|
|
144
|
+
BEGIN { found = 0 }
|
|
145
|
+
{ lines[NR] = $0; count = NR }
|
|
146
|
+
END {
|
|
147
|
+
# Walk backwards to find the last unresolved match for this agent
|
|
148
|
+
for (i = count; i >= 1; i--) {
|
|
149
|
+
if (!found && index(lines[i], "\"agent_id\":\"" agent "\"") > 0 && index(lines[i], "\"outcome\":null") > 0) {
|
|
150
|
+
gsub(/"outcome":null/, "\"outcome\":\"" outcome "\"", lines[i])
|
|
151
|
+
found = 1
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (i = 1; i <= count; i++) print lines[i]
|
|
155
|
+
}' "$MATCH_HISTORY" > "$tmp_mh" && _recruit_locked_write "$MATCH_HISTORY" "$tmp_mh" || rm -f "$tmp_mh"
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# Trigger meta-learning check (warn on failure instead of silencing)
|
|
159
|
+
if ! _recruit_meta_learning_check "$agent_id" "$outcome" 2>&1; then
|
|
160
|
+
warn "Meta-learning check failed for ${agent_id} (non-fatal)" >&2
|
|
161
|
+
fi
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Ingest outcomes from pipeline events.jsonl automatically
|
|
165
|
+
cmd_ingest_pipeline() {
|
|
166
|
+
local days="${1:-7}"
|
|
167
|
+
|
|
168
|
+
ensure_recruit_dir
|
|
169
|
+
info "Ingesting pipeline outcomes from last ${days} days..."
|
|
170
|
+
|
|
171
|
+
if [[ ! -f "$EVENTS_FILE" ]]; then
|
|
172
|
+
warn "No events file found"
|
|
173
|
+
return 0
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
local now_e
|
|
177
|
+
now_e=$(now_epoch)
|
|
178
|
+
local cutoff=$((now_e - days * 86400))
|
|
179
|
+
local ingested=0
|
|
180
|
+
|
|
181
|
+
while IFS= read -r line; do
|
|
182
|
+
local event_type ts_epoch result agent_id duration
|
|
183
|
+
event_type=$(echo "$line" | jq -r '.type // ""' 2>/dev/null) || continue
|
|
184
|
+
ts_epoch=$(echo "$line" | jq -r '.ts_epoch // 0' 2>/dev/null) || continue
|
|
185
|
+
|
|
186
|
+
[[ "$ts_epoch" -lt "$cutoff" ]] && continue
|
|
187
|
+
|
|
188
|
+
case "$event_type" in
|
|
189
|
+
pipeline.completed)
|
|
190
|
+
result=$(echo "$line" | jq -r '.result // "unknown"' 2>/dev/null || echo "unknown")
|
|
191
|
+
agent_id=$(echo "$line" | jq -r '.agent_id // "default-agent"' 2>/dev/null || echo "default-agent")
|
|
192
|
+
duration=$(echo "$line" | jq -r '.duration_s // 0' 2>/dev/null || echo "0")
|
|
193
|
+
local dur_min
|
|
194
|
+
dur_min=$(awk -v d="$duration" 'BEGIN{printf "%.1f", d/60}')
|
|
195
|
+
|
|
196
|
+
local outcome="failure"
|
|
197
|
+
[[ "$result" == "success" ]] && outcome="success"
|
|
198
|
+
|
|
199
|
+
cmd_record_outcome "$agent_id" "pipeline-$(echo "$line" | jq -r '.ts_epoch // 0')" "$outcome" "5" "$dur_min" 2>/dev/null || true
|
|
200
|
+
ingested=$((ingested + 1))
|
|
201
|
+
;;
|
|
202
|
+
esac
|
|
203
|
+
done < "$EVENTS_FILE"
|
|
204
|
+
|
|
205
|
+
success "Ingested ${ingested} pipeline outcomes"
|
|
206
|
+
emit_event "recruit_ingest" "count=${ingested}" "days=${days}"
|
|
207
|
+
|
|
208
|
+
# Auto-trigger self-tune when new outcomes are ingested (closes the learning loop)
|
|
209
|
+
if [[ "$ingested" -gt 0 ]]; then
|
|
210
|
+
info "Auto-running self-tune after ingesting ${ingested} outcomes..."
|
|
211
|
+
cmd_self_tune 2>/dev/null || warn "Auto self-tune failed (non-fatal)" >&2
|
|
212
|
+
|
|
213
|
+
# Auto-trigger evolve when enough outcomes accumulate (policy-driven)
|
|
214
|
+
local total_outcomes
|
|
215
|
+
total_outcomes=$(jq -r '[.[] | .tasks_completed // 0] | add // 0' "$PROFILES_DB" 2>/dev/null || echo "0")
|
|
216
|
+
local evolve_threshold="${RECRUIT_AUTO_EVOLVE_AFTER:-20}"
|
|
217
|
+
if [[ "$total_outcomes" -ge "$evolve_threshold" ]]; then
|
|
218
|
+
info "Auto-running evolve (${total_outcomes} total outcomes >= ${evolve_threshold} threshold)..."
|
|
219
|
+
cmd_evolve 2>/dev/null || warn "Auto evolve failed (non-fatal)" >&2
|
|
220
|
+
fi
|
|
221
|
+
fi
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
225
|
+
# ROLE USAGE TRACKING & EVOLUTION (Tier 2)
|
|
226
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
227
|
+
|
|
228
|
+
_recruit_track_role_usage() {
|
|
229
|
+
local role="$1"
|
|
230
|
+
local event="${2:-match}"
|
|
231
|
+
|
|
232
|
+
[[ ! -f "$ROLE_USAGE_DB" ]] && echo '{}' > "$ROLE_USAGE_DB"
|
|
233
|
+
|
|
234
|
+
local tmp_file
|
|
235
|
+
tmp_file=$(mktemp)
|
|
236
|
+
trap "rm -f '$tmp_file'" RETURN
|
|
237
|
+
jq --arg role "$role" --arg event "$event" --arg ts "$(now_iso)" '
|
|
238
|
+
.[$role] = (.[$role] // {matches: 0, successes: 0, failures: 0, last_used: ""}) |
|
|
239
|
+
.[$role].last_used = $ts |
|
|
240
|
+
if $event == "match" then .[$role].matches += 1
|
|
241
|
+
elif $event == "success" then .[$role].successes += 1
|
|
242
|
+
elif $event == "failure" then .[$role].failures += 1
|
|
243
|
+
else . end
|
|
244
|
+
' "$ROLE_USAGE_DB" > "$tmp_file" && _recruit_locked_write "$ROLE_USAGE_DB" "$tmp_file" || rm -f "$tmp_file"
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
cmd_evolve() {
|
|
248
|
+
ensure_recruit_dir
|
|
249
|
+
initialize_builtin_roles
|
|
250
|
+
|
|
251
|
+
info "Analyzing role evolution opportunities..."
|
|
252
|
+
echo ""
|
|
253
|
+
|
|
254
|
+
if [[ ! -f "$ROLE_USAGE_DB" || "$(jq 'length' "$ROLE_USAGE_DB" 2>/dev/null || echo 0)" -eq 0 ]]; then
|
|
255
|
+
warn "Not enough usage data for evolution analysis"
|
|
256
|
+
echo " Run more pipelines and use 'shipwright recruit ingest-pipeline' first"
|
|
257
|
+
return 0
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
local analysis=""
|
|
261
|
+
|
|
262
|
+
# Detect underused roles (no matches in 30+ days)
|
|
263
|
+
local stale_roles
|
|
264
|
+
stale_roles=$(jq -r --argjson cutoff "$(($(now_epoch) - 2592000))" '
|
|
265
|
+
to_entries[] | select(
|
|
266
|
+
(.value.last_used == "") or
|
|
267
|
+
(.value.matches == 0) or
|
|
268
|
+
((.value.last_used | sub("\\.[0-9]+Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) < $cutoff)
|
|
269
|
+
) | .key
|
|
270
|
+
' "$ROLE_USAGE_DB" 2>/dev/null || true)
|
|
271
|
+
|
|
272
|
+
if [[ -n "$stale_roles" ]]; then
|
|
273
|
+
echo -e " ${YELLOW}${BOLD}Underused Roles (candidates for retirement):${RESET}"
|
|
274
|
+
while IFS= read -r role; do
|
|
275
|
+
[[ -z "$role" ]] && continue
|
|
276
|
+
local matches
|
|
277
|
+
matches=$(jq -r --arg r "$role" '.[$r].matches // 0' "$ROLE_USAGE_DB" 2>/dev/null || echo "0")
|
|
278
|
+
echo -e " ${DIM}•${RESET} ${role} (${matches} total matches)"
|
|
279
|
+
analysis="${analysis}retire:${role},"
|
|
280
|
+
done <<< "$stale_roles"
|
|
281
|
+
echo ""
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
# Detect high-failure roles (>40% failure rate with 5+ tasks)
|
|
285
|
+
local struggling_roles
|
|
286
|
+
struggling_roles=$(jq -r '
|
|
287
|
+
to_entries[] | select(
|
|
288
|
+
(.value.matches >= 5) and
|
|
289
|
+
((.value.failures / .value.matches) > 0.4)
|
|
290
|
+
) | "\(.key):\(.value.failures)/\(.value.matches)"
|
|
291
|
+
' "$ROLE_USAGE_DB" 2>/dev/null || true)
|
|
292
|
+
|
|
293
|
+
if [[ -n "$struggling_roles" ]]; then
|
|
294
|
+
echo -e " ${RED}${BOLD}Struggling Roles (need specialization or split):${RESET}"
|
|
295
|
+
while IFS= read -r entry; do
|
|
296
|
+
[[ -z "$entry" ]] && continue
|
|
297
|
+
local role="${entry%%:*}"
|
|
298
|
+
local ratio="${entry#*:}"
|
|
299
|
+
echo -e " ${DIM}•${RESET} ${role} — ${ratio} failures"
|
|
300
|
+
analysis="${analysis}split:${role},"
|
|
301
|
+
done <<< "$struggling_roles"
|
|
302
|
+
echo ""
|
|
303
|
+
fi
|
|
304
|
+
|
|
305
|
+
# Detect overloaded roles (>60% of all matches go to one role)
|
|
306
|
+
local total_matches
|
|
307
|
+
total_matches=$(jq '[.[].matches] | add // 0' "$ROLE_USAGE_DB" 2>/dev/null || echo "0")
|
|
308
|
+
|
|
309
|
+
if [[ "$total_matches" -gt 10 ]]; then
|
|
310
|
+
local overloaded_roles
|
|
311
|
+
overloaded_roles=$(jq -r --argjson total "$total_matches" '
|
|
312
|
+
to_entries[] | select((.value.matches / $total) > 0.6) |
|
|
313
|
+
"\(.key):\(.value.matches)"
|
|
314
|
+
' "$ROLE_USAGE_DB" 2>/dev/null || true)
|
|
315
|
+
|
|
316
|
+
if [[ -n "$overloaded_roles" ]]; then
|
|
317
|
+
echo -e " ${PURPLE}${BOLD}Overloaded Roles (candidates for splitting):${RESET}"
|
|
318
|
+
while IFS= read -r entry; do
|
|
319
|
+
[[ -z "$entry" ]] && continue
|
|
320
|
+
local role="${entry%%:*}"
|
|
321
|
+
local count="${entry#*:}"
|
|
322
|
+
echo -e " ${DIM}•${RESET} ${role} — ${count}/${total_matches} matches ($(awk -v c="$count" -v t="$total_matches" 'BEGIN{printf "%.0f", (c/t)*100}')%)"
|
|
323
|
+
done <<< "$overloaded_roles"
|
|
324
|
+
echo ""
|
|
325
|
+
fi
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
# LLM-powered evolution suggestions
|
|
329
|
+
if [[ -n "$analysis" ]] && _recruit_has_claude; then
|
|
330
|
+
info "Generating AI evolution recommendations..."
|
|
331
|
+
local roles_summary
|
|
332
|
+
roles_summary=$(jq -c '.' "$ROLE_USAGE_DB" 2>/dev/null || echo "{}")
|
|
333
|
+
|
|
334
|
+
local prompt
|
|
335
|
+
prompt="Analyze agent role usage data and suggest evolution:
|
|
336
|
+
|
|
337
|
+
Usage data: ${roles_summary}
|
|
338
|
+
Analysis flags: ${analysis}
|
|
339
|
+
|
|
340
|
+
Suggest specific actions:
|
|
341
|
+
1. Which roles to retire (unused)
|
|
342
|
+
2. Which roles to split into specializations (high failure or overloaded)
|
|
343
|
+
3. Which roles to merge (overlapping low-use roles)
|
|
344
|
+
4. New hybrid roles to create
|
|
345
|
+
|
|
346
|
+
Return a brief text summary (3-5 bullet points). Be specific with role names."
|
|
347
|
+
|
|
348
|
+
local suggestions
|
|
349
|
+
suggestions=$(_recruit_call_claude "$prompt")
|
|
350
|
+
if [[ -n "$suggestions" ]]; then
|
|
351
|
+
echo -e " ${CYAN}${BOLD}AI Evolution Recommendations:${RESET}"
|
|
352
|
+
echo "$suggestions" | sed 's/^/ /'
|
|
353
|
+
fi
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
emit_event "recruit_evolve" "analysis=${analysis:0:100}"
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
360
|
+
# SELF-TUNING THRESHOLDS (Tier 2)
|
|
361
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
362
|
+
|
|
363
|
+
_recruit_compute_population_stats() {
|
|
364
|
+
if [[ ! -f "$PROFILES_DB" || "$(jq 'length' "$PROFILES_DB" 2>/dev/null || echo 0)" -lt 2 ]]; then
|
|
365
|
+
echo '{"mean_success":0,"stddev_success":0,"p90_success":0,"p10_success":0,"count":0}'
|
|
366
|
+
return
|
|
367
|
+
fi
|
|
368
|
+
|
|
369
|
+
jq '
|
|
370
|
+
[.[].success_rate] as $rates |
|
|
371
|
+
($rates | length) as $n |
|
|
372
|
+
($rates | add / $n) as $mean |
|
|
373
|
+
($rates | map(. - $mean | . * .) | add / $n | sqrt) as $stddev |
|
|
374
|
+
($rates | sort) as $sorted |
|
|
375
|
+
{
|
|
376
|
+
mean_success: ($mean * 10 | floor / 10),
|
|
377
|
+
stddev_success: ($stddev * 10 | floor / 10),
|
|
378
|
+
p90_success: ($sorted[($n * 0.9 | floor)] // 0),
|
|
379
|
+
p10_success: ($sorted[($n * 0.1 | floor)] // 0),
|
|
380
|
+
count: $n
|
|
381
|
+
}
|
|
382
|
+
' "$PROFILES_DB" 2>/dev/null || echo '{"mean_success":0,"stddev_success":0,"p90_success":0,"p10_success":0,"count":0}'
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
386
|
+
# CROSS-AGENT LEARNING (Tier 2)
|
|
387
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
388
|
+
|
|
389
|
+
cmd_specializations() {
|
|
390
|
+
ensure_recruit_dir
|
|
391
|
+
|
|
392
|
+
info "Agent Specialization Analysis:"
|
|
393
|
+
echo ""
|
|
394
|
+
|
|
395
|
+
if [[ ! -f "$PROFILES_DB" || "$(jq 'length' "$PROFILES_DB" 2>/dev/null || echo 0)" -eq 0 ]]; then
|
|
396
|
+
warn "No agent profiles to analyze"
|
|
397
|
+
return 0
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
# Analyze per-agent task history for patterns
|
|
401
|
+
jq -r 'to_entries[] |
|
|
402
|
+
.key as $agent |
|
|
403
|
+
.value |
|
|
404
|
+
" \($agent):" +
|
|
405
|
+
"\n Role: \(.role // "unassigned")" +
|
|
406
|
+
"\n Success: \(.success_rate // 0)% over \(.tasks_completed // 0) tasks" +
|
|
407
|
+
"\n Model: \(.model // "unknown")" +
|
|
408
|
+
"\n Strength: " + (
|
|
409
|
+
if (.success_rate // 0) >= 90 then "excellent"
|
|
410
|
+
elif (.success_rate // 0) >= 75 then "good"
|
|
411
|
+
elif (.success_rate // 0) >= 60 then "developing"
|
|
412
|
+
else "needs improvement"
|
|
413
|
+
end
|
|
414
|
+
) + "\n"
|
|
415
|
+
' "$PROFILES_DB" 2>/dev/null || warn "Could not analyze specializations"
|
|
416
|
+
|
|
417
|
+
# Suggest smart routing
|
|
418
|
+
local pop_stats
|
|
419
|
+
pop_stats=$(_recruit_compute_population_stats)
|
|
420
|
+
local mean_success
|
|
421
|
+
mean_success=$(echo "$pop_stats" | jq -r '.mean_success')
|
|
422
|
+
local agent_count
|
|
423
|
+
agent_count=$(echo "$pop_stats" | jq -r '.count')
|
|
424
|
+
|
|
425
|
+
if [[ "$agent_count" -gt 0 ]]; then
|
|
426
|
+
echo ""
|
|
427
|
+
echo -e " ${BOLD}Population Statistics:${RESET}"
|
|
428
|
+
echo -e " Mean success rate: ${mean_success}%"
|
|
429
|
+
echo -e " Agents tracked: ${agent_count}"
|
|
430
|
+
echo -e " P90/P10 spread: $(echo "$pop_stats" | jq -r '.p90_success')% / $(echo "$pop_stats" | jq -r '.p10_success')%"
|
|
431
|
+
fi
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
435
|
+
# META-LEARNING: REFLECT ON MATCHING ACCURACY (Tier 3)
|
|
436
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
437
|
+
|
|
438
|
+
_recruit_meta_learning_check() {
|
|
439
|
+
local agent_id="${1:-}"
|
|
440
|
+
local outcome="${2:-}"
|
|
441
|
+
|
|
442
|
+
[[ ! -f "$MATCH_HISTORY" ]] && return 0
|
|
443
|
+
[[ ! -f "$META_LEARNING_DB" ]] && return 0
|
|
444
|
+
|
|
445
|
+
# Find most recent match for this agent (by agent_id if set, else last match)
|
|
446
|
+
local last_match
|
|
447
|
+
last_match=$(tail -50 "$MATCH_HISTORY" | jq -s -r --arg agent "$agent_id" '
|
|
448
|
+
[.[] | select(.role != null) |
|
|
449
|
+
select(.agent_id == $agent or .agent_id == "" or .agent_id == null)] |
|
|
450
|
+
last // null
|
|
451
|
+
' 2>/dev/null || echo "")
|
|
452
|
+
|
|
453
|
+
[[ -z "$last_match" || "$last_match" == "null" ]] && return 0
|
|
454
|
+
|
|
455
|
+
local matched_role method
|
|
456
|
+
matched_role=$(echo "$last_match" | jq -r '.role // ""')
|
|
457
|
+
method=$(echo "$last_match" | jq -r '.method // "keyword"')
|
|
458
|
+
|
|
459
|
+
[[ -z "$matched_role" ]] && return 0
|
|
460
|
+
|
|
461
|
+
# Record correction if failure
|
|
462
|
+
if [[ "$outcome" == "failure" ]]; then
|
|
463
|
+
local correction
|
|
464
|
+
correction=$(jq -c -n \
|
|
465
|
+
--arg ts "$(now_iso)" \
|
|
466
|
+
--arg agent "$agent_id" \
|
|
467
|
+
--arg role "$matched_role" \
|
|
468
|
+
--arg method "$method" \
|
|
469
|
+
--arg outcome "$outcome" \
|
|
470
|
+
'{ts: $ts, agent: $agent, role: $role, method: $method, outcome: $outcome}')
|
|
471
|
+
|
|
472
|
+
local tmp_file
|
|
473
|
+
tmp_file=$(mktemp)
|
|
474
|
+
trap "rm -f '$tmp_file'" RETURN
|
|
475
|
+
jq --argjson corr "$correction" '
|
|
476
|
+
.corrections = ((.corrections // []) + [$corr] | .[-100:])
|
|
477
|
+
' "$META_LEARNING_DB" > "$tmp_file" && _recruit_locked_write "$META_LEARNING_DB" "$tmp_file" || rm -f "$tmp_file"
|
|
478
|
+
fi
|
|
479
|
+
|
|
480
|
+
# Every 20 outcomes, reflect on accuracy
|
|
481
|
+
local total_corrections
|
|
482
|
+
total_corrections=$(jq '.corrections | length' "$META_LEARNING_DB" 2>/dev/null || echo "0")
|
|
483
|
+
|
|
484
|
+
if [[ "$((total_corrections % 20))" -eq 0 && "$total_corrections" -gt 0 ]]; then
|
|
485
|
+
_recruit_reflect || warn "Auto-reflection failed (non-fatal)" >&2
|
|
486
|
+
fi
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
cmd_reflect() {
|
|
490
|
+
ensure_recruit_dir
|
|
491
|
+
|
|
492
|
+
info "Running meta-learning reflection..."
|
|
493
|
+
echo ""
|
|
494
|
+
|
|
495
|
+
_recruit_reflect
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
_recruit_reflect() {
|
|
499
|
+
[[ ! -f "$META_LEARNING_DB" ]] && return 0
|
|
500
|
+
[[ ! -f "$MATCH_HISTORY" ]] && return 0
|
|
501
|
+
|
|
502
|
+
local total_matches
|
|
503
|
+
total_matches=$(wc -l < "$MATCH_HISTORY" 2>/dev/null | tr -d ' ')
|
|
504
|
+
local total_corrections
|
|
505
|
+
total_corrections=$(jq '.corrections | length' "$META_LEARNING_DB" 2>/dev/null || echo "0")
|
|
506
|
+
|
|
507
|
+
if [[ "$total_matches" -eq 0 ]]; then
|
|
508
|
+
info "No match history to reflect on"
|
|
509
|
+
return 0
|
|
510
|
+
fi
|
|
511
|
+
|
|
512
|
+
local accuracy
|
|
513
|
+
accuracy=$(awk -v m="$total_matches" -v c="$total_corrections" 'BEGIN{if(m>0) printf "%.1f", ((m-c)/m)*100; else print "0"}')
|
|
514
|
+
|
|
515
|
+
echo -e " ${BOLD}Matching Accuracy:${RESET} ${accuracy}% (${total_matches} matches, ${total_corrections} corrections)"
|
|
516
|
+
|
|
517
|
+
# Track accuracy trend
|
|
518
|
+
local tmp_file
|
|
519
|
+
tmp_file=$(mktemp)
|
|
520
|
+
trap "rm -f '$tmp_file'" RETURN
|
|
521
|
+
jq --argjson acc "$accuracy" --arg ts "$(now_iso)" '
|
|
522
|
+
.accuracy_trend = ((.accuracy_trend // []) + [{accuracy: $acc, ts: $ts}] | .[-50:]) |
|
|
523
|
+
.last_reflection = $ts
|
|
524
|
+
' "$META_LEARNING_DB" > "$tmp_file" && _recruit_locked_write "$META_LEARNING_DB" "$tmp_file" || rm -f "$tmp_file"
|
|
525
|
+
|
|
526
|
+
# Identify most-failed role assignments
|
|
527
|
+
local failure_patterns
|
|
528
|
+
failure_patterns=$(jq -r '
|
|
529
|
+
.corrections | group_by(.role) |
|
|
530
|
+
map({role: .[0].role, failures: length}) |
|
|
531
|
+
sort_by(-.failures) | .[:3][] |
|
|
532
|
+
" \(.role): \(.failures) failures"
|
|
533
|
+
' "$META_LEARNING_DB" 2>/dev/null || true)
|
|
534
|
+
|
|
535
|
+
if [[ -n "$failure_patterns" ]]; then
|
|
536
|
+
echo ""
|
|
537
|
+
echo -e " ${BOLD}Most Mismatched Roles:${RESET}"
|
|
538
|
+
echo "$failure_patterns"
|
|
539
|
+
fi
|
|
540
|
+
|
|
541
|
+
# LLM-powered reflection
|
|
542
|
+
if _recruit_has_claude && [[ "$total_corrections" -ge 5 ]]; then
|
|
543
|
+
local corrections_json
|
|
544
|
+
corrections_json=$(jq -c '.corrections[-20:]' "$META_LEARNING_DB" 2>/dev/null || echo "[]")
|
|
545
|
+
|
|
546
|
+
local prompt
|
|
547
|
+
prompt="Analyze these role matching failures and suggest improvements to the matching heuristics.
|
|
548
|
+
|
|
549
|
+
Recent failures: ${corrections_json}
|
|
550
|
+
Current accuracy: ${accuracy}%
|
|
551
|
+
|
|
552
|
+
For each failed pattern, suggest:
|
|
553
|
+
1. What keyword or pattern should have triggered a different role
|
|
554
|
+
2. Whether a new role should be created for this type of task
|
|
555
|
+
|
|
556
|
+
Return a brief text summary (3-5 bullet points). Be specific about which keywords map to which roles."
|
|
557
|
+
|
|
558
|
+
local suggestions
|
|
559
|
+
suggestions=$(_recruit_call_claude "$prompt")
|
|
560
|
+
if [[ -n "$suggestions" ]]; then
|
|
561
|
+
echo ""
|
|
562
|
+
echo -e " ${CYAN}${BOLD}AI Reflection:${RESET}"
|
|
563
|
+
echo "$suggestions" | sed 's/^/ /'
|
|
564
|
+
fi
|
|
565
|
+
fi
|
|
566
|
+
|
|
567
|
+
emit_event "recruit_reflect" "accuracy=${accuracy}" "corrections=${total_corrections}"
|
|
568
|
+
|
|
569
|
+
# Meta-loop: validate self-tune effectiveness by comparing accuracy trend
|
|
570
|
+
_recruit_meta_validate_self_tune "$accuracy"
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
_recruit_meta_validate_self_tune() {
|
|
574
|
+
local current_accuracy="${1:-0}"
|
|
575
|
+
[[ ! -f "$META_LEARNING_DB" ]] && return 0
|
|
576
|
+
[[ ! -f "$HEURISTICS_DB" ]] && return 0
|
|
577
|
+
|
|
578
|
+
local accuracy_floor="${RECRUIT_META_ACCURACY_FLOOR:-50}"
|
|
579
|
+
|
|
580
|
+
# Get accuracy trend (last 10 data points)
|
|
581
|
+
local trend_data
|
|
582
|
+
trend_data=$(jq -r '.accuracy_trend // [] | .[-10:]' "$META_LEARNING_DB" 2>/dev/null) || return 0
|
|
583
|
+
|
|
584
|
+
local trend_count
|
|
585
|
+
trend_count=$(echo "$trend_data" | jq 'length' 2>/dev/null) || return 0
|
|
586
|
+
[[ "$trend_count" -lt 3 ]] && return 0
|
|
587
|
+
|
|
588
|
+
# Compute moving average of first half vs second half
|
|
589
|
+
local first_half_avg second_half_avg
|
|
590
|
+
first_half_avg=$(echo "$trend_data" | jq '[.[:length/2 | floor][].accuracy] | add / length' 2>/dev/null) || return 0
|
|
591
|
+
second_half_avg=$(echo "$trend_data" | jq '[.[length/2 | floor:][].accuracy] | add / length' 2>/dev/null) || return 0
|
|
592
|
+
|
|
593
|
+
local is_declining
|
|
594
|
+
is_declining=$(awk -v f="$first_half_avg" -v s="$second_half_avg" 'BEGIN{print (s < f - 5) ? 1 : 0}')
|
|
595
|
+
|
|
596
|
+
local is_below_floor
|
|
597
|
+
is_below_floor=$(awk -v c="$current_accuracy" -v f="$accuracy_floor" 'BEGIN{print (c < f) ? 1 : 0}')
|
|
598
|
+
|
|
599
|
+
if [[ "$is_declining" == "1" ]]; then
|
|
600
|
+
warn "META-LOOP: Accuracy DECLINING after self-tune (${first_half_avg}% -> ${second_half_avg}%)"
|
|
601
|
+
|
|
602
|
+
if [[ "$is_below_floor" == "1" ]]; then
|
|
603
|
+
warn "META-LOOP: Accuracy ${current_accuracy}% below floor ${accuracy_floor}% — reverting heuristics to defaults"
|
|
604
|
+
# Reset heuristics to empty (forces fallback to keyword_match defaults)
|
|
605
|
+
local tmp_heur
|
|
606
|
+
tmp_heur=$(mktemp)
|
|
607
|
+
trap "rm -f '$tmp_heur'" RETURN
|
|
608
|
+
echo '{"keyword_weights": {}, "meta_reverted_at": "'"$(now_iso)"'", "revert_reason": "accuracy_below_floor"}' > "$tmp_heur"
|
|
609
|
+
_recruit_locked_write "$HEURISTICS_DB" "$tmp_heur" || rm -f "$tmp_heur"
|
|
610
|
+
emit_event "recruit_meta_revert" "accuracy=${current_accuracy}" "floor=${accuracy_floor}" "reason=declining_below_floor"
|
|
611
|
+
else
|
|
612
|
+
emit_event "recruit_meta_warning" "accuracy=${current_accuracy}" "trend=declining" "first_half=${first_half_avg}" "second_half=${second_half_avg}"
|
|
613
|
+
fi
|
|
614
|
+
elif [[ "$is_below_floor" == "1" ]]; then
|
|
615
|
+
warn "META-LOOP: Accuracy ${current_accuracy}% below floor ${accuracy_floor}%"
|
|
616
|
+
emit_event "recruit_meta_warning" "accuracy=${current_accuracy}" "floor=${accuracy_floor}" "trend=low"
|
|
617
|
+
fi
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
621
|
+
# SELF-MODIFICATION: REWRITE OWN HEURISTICS (Tier 3)
|
|
622
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
623
|
+
|
|
624
|
+
cmd_self_tune() {
|
|
625
|
+
ensure_recruit_dir
|
|
626
|
+
|
|
627
|
+
info "Self-tuning matching heuristics..."
|
|
628
|
+
echo ""
|
|
629
|
+
|
|
630
|
+
if [[ ! -f "$MATCH_HISTORY" ]]; then
|
|
631
|
+
warn "No match history to learn from"
|
|
632
|
+
return 0
|
|
633
|
+
fi
|
|
634
|
+
|
|
635
|
+
local total_matches
|
|
636
|
+
total_matches=$(wc -l < "$MATCH_HISTORY" 2>/dev/null | tr -d ' ')
|
|
637
|
+
|
|
638
|
+
local min_matches="${RECRUIT_SELF_TUNE_MIN_MATCHES:-5}"
|
|
639
|
+
if [[ "$total_matches" -lt "$min_matches" ]]; then
|
|
640
|
+
warn "Need at least ${min_matches} matches to self-tune (have ${total_matches})"
|
|
641
|
+
return 0
|
|
642
|
+
fi
|
|
643
|
+
|
|
644
|
+
# Analyze which keywords correctly predicted roles
|
|
645
|
+
info "Analyzing ${total_matches} match records..."
|
|
646
|
+
|
|
647
|
+
# Build keyword frequency map from successful matches
|
|
648
|
+
local keyword_updates=0
|
|
649
|
+
|
|
650
|
+
# Extract task descriptions grouped by role
|
|
651
|
+
local match_data
|
|
652
|
+
match_data=$(jq -s '
|
|
653
|
+
[.[] | select(.role != null and .role != "")] |
|
|
654
|
+
group_by(.role) |
|
|
655
|
+
map({
|
|
656
|
+
role: .[0].role,
|
|
657
|
+
tasks: [.[] | .task],
|
|
658
|
+
count: length
|
|
659
|
+
})
|
|
660
|
+
' "$MATCH_HISTORY" 2>/dev/null || echo "[]")
|
|
661
|
+
|
|
662
|
+
# Filter to roles with positive success ratios from role-usage DB
|
|
663
|
+
if [[ -f "$ROLE_USAGE_DB" ]]; then
|
|
664
|
+
local good_roles
|
|
665
|
+
good_roles=$(jq -r '
|
|
666
|
+
to_entries[] |
|
|
667
|
+
select((.value.successes // 0) > (.value.failures // 0)) |
|
|
668
|
+
.key
|
|
669
|
+
' "$ROLE_USAGE_DB" 2>/dev/null || true)
|
|
670
|
+
|
|
671
|
+
if [[ -n "$good_roles" ]]; then
|
|
672
|
+
local good_roles_json
|
|
673
|
+
good_roles_json=$(echo "$good_roles" | jq -R . | jq -s .)
|
|
674
|
+
match_data=$(echo "$match_data" | jq --argjson good "$good_roles_json" '
|
|
675
|
+
[.[] | select(.role as $r | $good | index($r) // false)]
|
|
676
|
+
' 2>/dev/null || echo "$match_data")
|
|
677
|
+
fi
|
|
678
|
+
fi
|
|
679
|
+
|
|
680
|
+
if [[ "$match_data" == "[]" ]]; then
|
|
681
|
+
info "No successful outcomes recorded yet"
|
|
682
|
+
return 0
|
|
683
|
+
fi
|
|
684
|
+
|
|
685
|
+
# Extract common words per role (simple TF approach)
|
|
686
|
+
local role_count
|
|
687
|
+
role_count=$(echo "$match_data" | jq 'length')
|
|
688
|
+
|
|
689
|
+
local tmp_heuristics
|
|
690
|
+
tmp_heuristics=$(mktemp)
|
|
691
|
+
trap "rm -f '$tmp_heuristics'" RETURN
|
|
692
|
+
cp "$HEURISTICS_DB" "$tmp_heuristics"
|
|
693
|
+
|
|
694
|
+
local i=0
|
|
695
|
+
while [[ "$i" -lt "$role_count" ]]; do
|
|
696
|
+
local role
|
|
697
|
+
role=$(echo "$match_data" | jq -r ".[$i].role")
|
|
698
|
+
local tasks
|
|
699
|
+
tasks=$(echo "$match_data" | jq -r ".[$i].tasks | join(\" \")" | tr '[:upper:]' '[:lower:]')
|
|
700
|
+
|
|
701
|
+
# Find frequent words (>= 2 occurrences, >= 4 chars)
|
|
702
|
+
local frequent_words
|
|
703
|
+
frequent_words=$(echo "$tasks" | tr -cs '[:alpha:]' '\n' | sort | uniq -c | sort -rn | \
|
|
704
|
+
awk '$1 >= 2 && length($2) >= 4 {print $2}' | head -5)
|
|
705
|
+
|
|
706
|
+
while IFS= read -r word; do
|
|
707
|
+
[[ -z "$word" ]] && continue
|
|
708
|
+
# Skip common stop words
|
|
709
|
+
case "$word" in
|
|
710
|
+
this|that|with|from|have|will|should|would|could|been|some|more|than|into) continue ;;
|
|
711
|
+
esac
|
|
712
|
+
|
|
713
|
+
jq --arg kw "$word" --arg role "$role" \
|
|
714
|
+
'.keyword_weights[$kw] = {role: $role, weight: 5, source: "self-tuned"}' \
|
|
715
|
+
"$tmp_heuristics" > "${tmp_heuristics}.new" && mv "${tmp_heuristics}.new" "$tmp_heuristics"
|
|
716
|
+
keyword_updates=$((keyword_updates + 1))
|
|
717
|
+
done <<< "$frequent_words"
|
|
718
|
+
|
|
719
|
+
i=$((i + 1))
|
|
720
|
+
done
|
|
721
|
+
|
|
722
|
+
# Persist updated heuristics
|
|
723
|
+
jq --arg ts "$(now_iso)" '.last_tuned = $ts' "$tmp_heuristics" > "${tmp_heuristics}.final"
|
|
724
|
+
mv "${tmp_heuristics}.final" "$HEURISTICS_DB"
|
|
725
|
+
rm -f "$tmp_heuristics"
|
|
726
|
+
|
|
727
|
+
success "Self-tuned ${keyword_updates} keyword→role mappings"
|
|
728
|
+
|
|
729
|
+
# Show what changed
|
|
730
|
+
if [[ "$keyword_updates" -gt 0 ]]; then
|
|
731
|
+
echo ""
|
|
732
|
+
echo -e " ${BOLD}Updated Keyword Weights:${RESET}"
|
|
733
|
+
jq -r '.keyword_weights | to_entries | sort_by(-.value.weight) | .[:10][] |
|
|
734
|
+
" \(.key) → \(.value.role) (weight: \(.value.weight), source: \(.value.source))"
|
|
735
|
+
' "$HEURISTICS_DB" 2>/dev/null || true
|
|
736
|
+
fi
|
|
737
|
+
|
|
738
|
+
emit_event "recruit_self_tune" "keywords_updated=${keyword_updates}" "total_matches=${total_matches}"
|
|
739
|
+
}
|