shipwright-cli 2.1.2 → 2.2.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/devops-engineer.md +14 -12
- package/.claude/agents/doc-fleet-agent.md +99 -0
- package/.claude/agents/test-specialist.md +5 -3
- package/README.md +48 -27
- package/claude-code/CLAUDE.md.shipwright +2 -2
- package/config/policy.json +73 -0
- package/config/policy.schema.json +75 -0
- package/docs/AGI-PLATFORM-PLAN.md +122 -0
- package/docs/AGI-WHATS-NEXT.md +69 -0
- package/docs/KNOWN-ISSUES.md +1 -23
- package/docs/PLATFORM-TODO-BACKLOG.md +41 -0
- package/docs/PLATFORM-TODO-TRIAGE.md +56 -0
- package/docs/README.md +83 -0
- package/docs/TIPS.md +39 -2
- package/docs/config-policy.md +40 -0
- package/docs/definition-of-done.example.md +2 -0
- package/docs/patterns/README.md +5 -0
- package/docs/strategy/02-mission-and-brand.md +3 -3
- package/docs/strategy/README.md +4 -3
- package/docs/tmux-research/TMUX-AUDIT.md +2 -0
- package/docs/tmux-research/TMUX-RESEARCH-INDEX.md +17 -0
- package/package.json +3 -2
- package/scripts/lib/daemon-health.sh +32 -0
- package/scripts/lib/pipeline-quality.sh +23 -0
- package/scripts/lib/policy.sh +32 -0
- package/scripts/sw +5 -1
- package/scripts/sw-activity.sh +35 -46
- package/scripts/sw-adaptive.sh +30 -39
- package/scripts/sw-adversarial.sh +30 -36
- package/scripts/sw-architecture-enforcer.sh +30 -33
- package/scripts/sw-auth.sh +30 -42
- package/scripts/sw-autonomous.sh +60 -40
- package/scripts/sw-changelog.sh +29 -30
- package/scripts/sw-checkpoint.sh +30 -18
- package/scripts/sw-ci.sh +30 -42
- package/scripts/sw-cleanup.sh +32 -15
- package/scripts/sw-code-review.sh +26 -32
- package/scripts/sw-connect.sh +30 -19
- package/scripts/sw-context.sh +30 -19
- package/scripts/sw-cost.sh +30 -40
- package/scripts/sw-daemon.sh +66 -36
- package/scripts/sw-dashboard.sh +31 -40
- package/scripts/sw-db.sh +30 -20
- package/scripts/sw-decompose.sh +30 -38
- package/scripts/sw-deps.sh +30 -41
- package/scripts/sw-developer-simulation.sh +30 -36
- package/scripts/sw-discovery.sh +36 -19
- package/scripts/sw-doc-fleet.sh +822 -0
- package/scripts/sw-docs-agent.sh +30 -36
- package/scripts/sw-docs.sh +29 -31
- package/scripts/sw-doctor.sh +52 -20
- package/scripts/sw-dora.sh +29 -34
- package/scripts/sw-durable.sh +30 -20
- package/scripts/sw-e2e-orchestrator.sh +36 -21
- package/scripts/sw-eventbus.sh +30 -17
- package/scripts/sw-feedback.sh +30 -41
- package/scripts/sw-fix.sh +30 -40
- package/scripts/sw-fleet-discover.sh +30 -41
- package/scripts/sw-fleet-viz.sh +30 -20
- package/scripts/sw-fleet.sh +30 -40
- package/scripts/sw-github-app.sh +30 -41
- package/scripts/sw-github-checks.sh +30 -41
- package/scripts/sw-github-deploy.sh +30 -41
- package/scripts/sw-github-graphql.sh +30 -38
- package/scripts/sw-guild.sh +30 -37
- package/scripts/sw-heartbeat.sh +30 -19
- package/scripts/sw-hygiene.sh +134 -42
- package/scripts/sw-incident.sh +30 -39
- package/scripts/sw-init.sh +31 -14
- package/scripts/sw-instrument.sh +30 -41
- package/scripts/sw-intelligence.sh +39 -44
- package/scripts/sw-jira.sh +31 -41
- package/scripts/sw-launchd.sh +30 -17
- package/scripts/sw-linear.sh +31 -41
- package/scripts/sw-logs.sh +32 -17
- package/scripts/sw-loop.sh +32 -19
- package/scripts/sw-memory.sh +32 -43
- package/scripts/sw-mission-control.sh +31 -40
- package/scripts/sw-model-router.sh +30 -20
- package/scripts/sw-otel.sh +30 -20
- package/scripts/sw-oversight.sh +30 -36
- package/scripts/sw-patrol-meta.sh +31 -0
- package/scripts/sw-pipeline-composer.sh +30 -39
- package/scripts/sw-pipeline-vitals.sh +30 -44
- package/scripts/sw-pipeline.sh +275 -6388
- package/scripts/sw-pm.sh +31 -41
- package/scripts/sw-pr-lifecycle.sh +30 -42
- package/scripts/sw-predictive.sh +32 -34
- package/scripts/sw-prep.sh +30 -19
- package/scripts/sw-ps.sh +32 -17
- package/scripts/sw-public-dashboard.sh +30 -40
- package/scripts/sw-quality.sh +42 -40
- package/scripts/sw-reaper.sh +32 -15
- package/scripts/sw-recruit.sh +428 -48
- package/scripts/sw-regression.sh +30 -38
- package/scripts/sw-release-manager.sh +30 -38
- package/scripts/sw-release.sh +29 -31
- package/scripts/sw-remote.sh +31 -40
- package/scripts/sw-replay.sh +30 -18
- package/scripts/sw-retro.sh +33 -42
- package/scripts/sw-scale.sh +41 -24
- package/scripts/sw-security-audit.sh +30 -20
- package/scripts/sw-self-optimize.sh +33 -37
- package/scripts/sw-session.sh +31 -15
- package/scripts/sw-setup.sh +30 -16
- package/scripts/sw-standup.sh +30 -20
- package/scripts/sw-status.sh +33 -13
- package/scripts/sw-strategic.sh +55 -43
- package/scripts/sw-stream.sh +33 -37
- package/scripts/sw-swarm.sh +30 -21
- package/scripts/sw-team-stages.sh +30 -38
- package/scripts/sw-templates.sh +31 -16
- package/scripts/sw-testgen.sh +30 -31
- package/scripts/sw-tmux-pipeline.sh +29 -31
- package/scripts/sw-tmux-role-color.sh +31 -0
- package/scripts/sw-tmux-status.sh +31 -0
- package/scripts/sw-tmux.sh +31 -15
- package/scripts/sw-trace.sh +30 -19
- package/scripts/sw-tracker-github.sh +31 -0
- package/scripts/sw-tracker-jira.sh +31 -0
- package/scripts/sw-tracker-linear.sh +31 -0
- package/scripts/sw-tracker.sh +30 -40
- package/scripts/sw-triage.sh +68 -61
- package/scripts/sw-upgrade.sh +30 -16
- package/scripts/sw-ux.sh +30 -35
- package/scripts/sw-webhook.sh +30 -25
- package/scripts/sw-widgets.sh +30 -19
- package/scripts/sw-worktree.sh +32 -15
- package/tmux/templates/doc-fleet.json +43 -0
package/scripts/sw-reaper.sh
CHANGED
|
@@ -11,24 +11,41 @@
|
|
|
11
11
|
# ║ shipwright reaper --watch Continuous loop (default: 5s) ║
|
|
12
12
|
# ║ shipwright reaper --dry-run Preview what would be reaped ║
|
|
13
13
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
14
|
-
VERSION="2.
|
|
14
|
+
VERSION="2.2.0"
|
|
15
15
|
set -euo pipefail
|
|
16
16
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
|
|
20
|
+
# Canonical helpers (colors, output, events)
|
|
21
|
+
# shellcheck source=lib/helpers.sh
|
|
22
|
+
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
23
|
+
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
24
|
+
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
25
|
+
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
26
|
+
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
|
|
27
|
+
[[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
|
|
28
|
+
if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
29
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
30
|
+
now_epoch() { date +%s; }
|
|
31
|
+
fi
|
|
32
|
+
if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
33
|
+
emit_event() {
|
|
34
|
+
local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
|
|
35
|
+
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
36
|
+
while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
|
|
37
|
+
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
38
|
+
}
|
|
39
|
+
fi
|
|
40
|
+
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
41
|
+
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
42
|
+
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
43
|
+
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
44
|
+
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
45
|
+
RED="${RED:-\033[38;2;248;113;113m}"
|
|
46
|
+
DIM="${DIM:-\033[2m}"
|
|
47
|
+
BOLD="${BOLD:-\033[1m}"
|
|
48
|
+
RESET="${RESET:-\033[0m}"
|
|
32
49
|
|
|
33
50
|
# ─── Defaults ──────────────────────────────────────────────────────────────
|
|
34
51
|
WATCH=false
|
package/scripts/sw-recruit.sh
CHANGED
|
@@ -22,50 +22,38 @@ if ! command -v jq &>/dev/null; then
|
|
|
22
22
|
exit 1
|
|
23
23
|
fi
|
|
24
24
|
|
|
25
|
-
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
26
|
-
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
27
|
-
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
28
|
-
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
29
|
-
GREEN='\033[38;2;74;222;128m' # success
|
|
30
|
-
YELLOW='\033[38;2;250;204;21m' # warning
|
|
31
|
-
RED='\033[38;2;248;113;113m' # error
|
|
32
|
-
DIM='\033[2m'
|
|
33
|
-
BOLD='\033[1m'
|
|
34
|
-
RESET='\033[0m'
|
|
35
|
-
|
|
36
25
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
37
26
|
# shellcheck source=lib/compat.sh
|
|
38
27
|
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
39
|
-
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
emit_event() {
|
|
53
|
-
local event_type="$1"
|
|
54
|
-
|
|
55
|
-
local
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
28
|
+
# Canonical helpers (colors, output, events)
|
|
29
|
+
# shellcheck source=lib/helpers.sh
|
|
30
|
+
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
31
|
+
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
32
|
+
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
33
|
+
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
34
|
+
[[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
|
|
35
|
+
[[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
|
|
36
|
+
if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
37
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
38
|
+
now_epoch() { date +%s; }
|
|
39
|
+
fi
|
|
40
|
+
if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
41
|
+
emit_event() {
|
|
42
|
+
local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
|
|
43
|
+
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
44
|
+
while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
|
|
45
|
+
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
46
|
+
}
|
|
47
|
+
fi
|
|
48
|
+
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
49
|
+
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
50
|
+
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
51
|
+
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
52
|
+
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
53
|
+
RED="${RED:-\033[38;2;248;113;113m}"
|
|
54
|
+
DIM="${DIM:-\033[2m}"
|
|
55
|
+
BOLD="${BOLD:-\033[1m}"
|
|
56
|
+
RESET="${RESET:-\033[0m}"
|
|
69
57
|
|
|
70
58
|
# ─── File Locking for Concurrent Safety ────────────────────────────────────
|
|
71
59
|
# Usage: _recruit_locked_write <target_file> <tmp_file>
|
|
@@ -97,6 +85,30 @@ AGENT_MINDS_DB="${RECRUIT_ROOT}/agent-minds.json"
|
|
|
97
85
|
INVENTED_ROLES_LOG="${RECRUIT_ROOT}/invented-roles.jsonl"
|
|
98
86
|
META_LEARNING_DB="${RECRUIT_ROOT}/meta-learning.json"
|
|
99
87
|
|
|
88
|
+
# ─── Policy Integration ──────────────────────────────────────────────────
|
|
89
|
+
POLICY_FILE="${SCRIPT_DIR}/../config/policy.json"
|
|
90
|
+
_recruit_policy() {
|
|
91
|
+
local key="$1"
|
|
92
|
+
local default="$2"
|
|
93
|
+
if [[ -f "$POLICY_FILE" ]] && command -v jq &>/dev/null; then
|
|
94
|
+
local val
|
|
95
|
+
val=$(jq -r ".recruit.${key} // empty" "$POLICY_FILE" 2>/dev/null) || true
|
|
96
|
+
[[ -n "$val" ]] && echo "$val" || echo "$default"
|
|
97
|
+
else
|
|
98
|
+
echo "$default"
|
|
99
|
+
fi
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
RECRUIT_CONFIDENCE_THRESHOLD=$(_recruit_policy "match_confidence_threshold" "0.3")
|
|
103
|
+
RECRUIT_MAX_MATCH_HISTORY=$(_recruit_policy "max_match_history_size" "5000")
|
|
104
|
+
RECRUIT_META_ACCURACY_FLOOR=$(_recruit_policy "meta_learning_accuracy_floor" "50")
|
|
105
|
+
RECRUIT_LLM_TIMEOUT=$(_recruit_policy "llm_timeout_seconds" "30")
|
|
106
|
+
RECRUIT_DEFAULT_MODEL=$(_recruit_policy "default_model" "sonnet")
|
|
107
|
+
RECRUIT_SELF_TUNE_MIN_MATCHES=$(_recruit_policy "self_tune_min_matches" "5")
|
|
108
|
+
RECRUIT_PROMOTE_TASKS=$(_recruit_policy "promote_threshold_tasks" "10")
|
|
109
|
+
RECRUIT_PROMOTE_SUCCESS=$(_recruit_policy "promote_threshold_success_rate" "85")
|
|
110
|
+
RECRUIT_AUTO_EVOLVE_AFTER=$(_recruit_policy "auto_evolve_after_outcomes" "20")
|
|
111
|
+
|
|
100
112
|
ensure_recruit_dir() {
|
|
101
113
|
mkdir -p "$RECRUIT_ROOT"
|
|
102
114
|
[[ -f "$ROLES_DB" ]] || echo '{}' > "$ROLES_DB"
|
|
@@ -373,6 +385,7 @@ Return JSON only, no markdown fences."
|
|
|
373
385
|
}
|
|
374
386
|
|
|
375
387
|
# Record a match for learning
|
|
388
|
+
# Returns the match_id (epoch-based) so callers can pass it downstream for outcome linking
|
|
376
389
|
_recruit_record_match() {
|
|
377
390
|
local task="$1"
|
|
378
391
|
local role="$2"
|
|
@@ -381,20 +394,38 @@ _recruit_record_match() {
|
|
|
381
394
|
local agent_id="${5:-}"
|
|
382
395
|
|
|
383
396
|
mkdir -p "$RECRUIT_ROOT"
|
|
397
|
+
local match_epoch
|
|
398
|
+
match_epoch=$(now_epoch)
|
|
399
|
+
local match_id="match-${match_epoch}-$$"
|
|
400
|
+
|
|
384
401
|
local record
|
|
385
402
|
record=$(jq -c -n \
|
|
386
403
|
--arg ts "$(now_iso)" \
|
|
387
|
-
--argjson epoch "$
|
|
404
|
+
--argjson epoch "$match_epoch" \
|
|
405
|
+
--arg match_id "$match_id" \
|
|
388
406
|
--arg task "$task" \
|
|
389
407
|
--arg role "$role" \
|
|
390
408
|
--arg method "$method" \
|
|
391
409
|
--argjson conf "$confidence" \
|
|
392
410
|
--arg agent "$agent_id" \
|
|
393
|
-
'{ts: $ts, ts_epoch: $epoch, task: $task, role: $role, method: $method, confidence: $conf, agent_id: $agent, outcome: null}')
|
|
411
|
+
'{ts: $ts, ts_epoch: $epoch, match_id: $match_id, task: $task, role: $role, method: $method, confidence: $conf, agent_id: $agent, outcome: null}')
|
|
394
412
|
echo "$record" >> "$MATCH_HISTORY"
|
|
395
413
|
|
|
414
|
+
# Enforce max match history size (from policy)
|
|
415
|
+
local max_history="${RECRUIT_MAX_MATCH_HISTORY:-5000}"
|
|
416
|
+
local current_lines
|
|
417
|
+
current_lines=$(wc -l < "$MATCH_HISTORY" 2>/dev/null | tr -d ' ')
|
|
418
|
+
if [[ "$current_lines" -gt "$max_history" ]]; then
|
|
419
|
+
local tmp_trunc
|
|
420
|
+
tmp_trunc=$(mktemp)
|
|
421
|
+
tail -n "$max_history" "$MATCH_HISTORY" > "$tmp_trunc" && _recruit_locked_write "$MATCH_HISTORY" "$tmp_trunc" || rm -f "$tmp_trunc"
|
|
422
|
+
fi
|
|
423
|
+
|
|
396
424
|
# Update role usage stats
|
|
397
425
|
_recruit_track_role_usage "$role" "match"
|
|
426
|
+
|
|
427
|
+
# Store match_id in global for callers (avoids stdout contamination)
|
|
428
|
+
LAST_MATCH_ID="$match_id"
|
|
398
429
|
}
|
|
399
430
|
|
|
400
431
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -613,6 +644,29 @@ cmd_record_outcome() {
|
|
|
613
644
|
success "Recorded ${outcome} for ${CYAN}${agent_id}${RESET} (${tasks_completed} tasks, ${success_rate}% success)"
|
|
614
645
|
emit_event "recruit_outcome" "agent_id=${agent_id}" "outcome=${outcome}" "success_rate=${success_rate}"
|
|
615
646
|
|
|
647
|
+
# Track role usage with outcome (closes the role-usage feedback loop)
|
|
648
|
+
_recruit_track_role_usage "$role_assigned" "$outcome"
|
|
649
|
+
|
|
650
|
+
# Backfill match history with outcome (closes the match→outcome linkage gap)
|
|
651
|
+
if [[ -f "$MATCH_HISTORY" ]]; then
|
|
652
|
+
local tmp_mh
|
|
653
|
+
tmp_mh=$(mktemp)
|
|
654
|
+
# Find the most recent match for this agent_id with null outcome, and backfill
|
|
655
|
+
awk -v agent="$agent_id" -v outcome="$outcome" '
|
|
656
|
+
BEGIN { found = 0 }
|
|
657
|
+
{ lines[NR] = $0; count = NR }
|
|
658
|
+
END {
|
|
659
|
+
# Walk backwards to find the last unresolved match for this agent
|
|
660
|
+
for (i = count; i >= 1; i--) {
|
|
661
|
+
if (!found && index(lines[i], "\"agent_id\":\"" agent "\"") > 0 && index(lines[i], "\"outcome\":null") > 0) {
|
|
662
|
+
gsub(/"outcome":null/, "\"outcome\":\"" outcome "\"", lines[i])
|
|
663
|
+
found = 1
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
for (i = 1; i <= count; i++) print lines[i]
|
|
667
|
+
}' "$MATCH_HISTORY" > "$tmp_mh" && _recruit_locked_write "$MATCH_HISTORY" "$tmp_mh" || rm -f "$tmp_mh"
|
|
668
|
+
fi
|
|
669
|
+
|
|
616
670
|
# Trigger meta-learning check (warn on failure instead of silencing)
|
|
617
671
|
if ! _recruit_meta_learning_check "$agent_id" "$outcome" 2>&1; then
|
|
618
672
|
warn "Meta-learning check failed for ${agent_id} (non-fatal)" >&2
|
|
@@ -662,6 +716,21 @@ cmd_ingest_pipeline() {
|
|
|
662
716
|
|
|
663
717
|
success "Ingested ${ingested} pipeline outcomes"
|
|
664
718
|
emit_event "recruit_ingest" "count=${ingested}" "days=${days}"
|
|
719
|
+
|
|
720
|
+
# Auto-trigger self-tune when new outcomes are ingested (closes the learning loop)
|
|
721
|
+
if [[ "$ingested" -gt 0 ]]; then
|
|
722
|
+
info "Auto-running self-tune after ingesting ${ingested} outcomes..."
|
|
723
|
+
cmd_self_tune 2>/dev/null || warn "Auto self-tune failed (non-fatal)" >&2
|
|
724
|
+
|
|
725
|
+
# Auto-trigger evolve when enough outcomes accumulate (policy-driven)
|
|
726
|
+
local total_outcomes
|
|
727
|
+
total_outcomes=$(jq -r '[.[] | .tasks_completed // 0] | add // 0' "$PROFILES_DB" 2>/dev/null || echo "0")
|
|
728
|
+
local evolve_threshold="${RECRUIT_AUTO_EVOLVE_AFTER:-20}"
|
|
729
|
+
if [[ "$total_outcomes" -ge "$evolve_threshold" ]]; then
|
|
730
|
+
info "Auto-running evolve (${total_outcomes} total outcomes >= ${evolve_threshold} threshold)..."
|
|
731
|
+
cmd_evolve 2>/dev/null || warn "Auto evolve failed (non-fatal)" >&2
|
|
732
|
+
fi
|
|
733
|
+
fi
|
|
665
734
|
}
|
|
666
735
|
|
|
667
736
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1045,18 +1114,40 @@ Return JSON only."
|
|
|
1045
1114
|
if $json_mode; then
|
|
1046
1115
|
local roles_json
|
|
1047
1116
|
roles_json=$(printf '%s\n' "${recommended_team[@]}" | jq -R . | jq -s .)
|
|
1117
|
+
|
|
1118
|
+
# Derive template and max_iterations from team size/composition (triage needs these)
|
|
1119
|
+
local team_template="full"
|
|
1120
|
+
local team_max_iterations=10
|
|
1121
|
+
local team_size=${#recommended_team[@]}
|
|
1122
|
+
if [[ $team_size -le 2 ]]; then
|
|
1123
|
+
team_template="quick-fix"
|
|
1124
|
+
team_max_iterations=5
|
|
1125
|
+
elif [[ $team_size -ge 5 ]]; then
|
|
1126
|
+
team_template="careful"
|
|
1127
|
+
team_max_iterations=20
|
|
1128
|
+
fi
|
|
1129
|
+
# Security tasks get more iterations
|
|
1130
|
+
if printf '%s\n' "${recommended_team[@]}" | grep -q "security-auditor"; then
|
|
1131
|
+
team_template="careful"
|
|
1132
|
+
[[ $team_max_iterations -lt 15 ]] && team_max_iterations=15
|
|
1133
|
+
fi
|
|
1134
|
+
|
|
1048
1135
|
jq -c -n \
|
|
1049
1136
|
--argjson team "$roles_json" \
|
|
1050
1137
|
--arg method "$team_method" \
|
|
1051
1138
|
--argjson cost "$total_cost" \
|
|
1052
1139
|
--arg model "$team_model" \
|
|
1053
|
-
--argjson agents "$
|
|
1140
|
+
--argjson agents "$team_size" \
|
|
1141
|
+
--arg template "$team_template" \
|
|
1142
|
+
--argjson max_iterations "$team_max_iterations" \
|
|
1054
1143
|
'{
|
|
1055
1144
|
team: $team,
|
|
1056
1145
|
method: $method,
|
|
1057
1146
|
estimated_cost: $cost,
|
|
1058
1147
|
model: $model,
|
|
1059
|
-
agents: $agents
|
|
1148
|
+
agents: $agents,
|
|
1149
|
+
template: $template,
|
|
1150
|
+
max_iterations: $max_iterations
|
|
1060
1151
|
}'
|
|
1061
1152
|
return 0
|
|
1062
1153
|
fi
|
|
@@ -1215,6 +1306,57 @@ Return a brief text summary (3-5 bullet points). Be specific about which keyword
|
|
|
1215
1306
|
fi
|
|
1216
1307
|
|
|
1217
1308
|
emit_event "recruit_reflect" "accuracy=${accuracy}" "corrections=${total_corrections}"
|
|
1309
|
+
|
|
1310
|
+
# Meta-loop: validate self-tune effectiveness by comparing accuracy trend
|
|
1311
|
+
_recruit_meta_validate_self_tune "$accuracy"
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
# Meta feedback loop: checks if self-tune is actually improving accuracy
|
|
1315
|
+
# If accuracy drops after self-tune, emits a warning and reverts heuristics
|
|
1316
|
+
_recruit_meta_validate_self_tune() {
|
|
1317
|
+
local current_accuracy="${1:-0}"
|
|
1318
|
+
[[ ! -f "$META_LEARNING_DB" ]] && return 0
|
|
1319
|
+
[[ ! -f "$HEURISTICS_DB" ]] && return 0
|
|
1320
|
+
|
|
1321
|
+
local accuracy_floor="${RECRUIT_META_ACCURACY_FLOOR:-50}"
|
|
1322
|
+
|
|
1323
|
+
# Get accuracy trend (last 10 data points)
|
|
1324
|
+
local trend_data
|
|
1325
|
+
trend_data=$(jq -r '.accuracy_trend // [] | .[-10:]' "$META_LEARNING_DB" 2>/dev/null) || return 0
|
|
1326
|
+
|
|
1327
|
+
local trend_count
|
|
1328
|
+
trend_count=$(echo "$trend_data" | jq 'length' 2>/dev/null) || return 0
|
|
1329
|
+
[[ "$trend_count" -lt 3 ]] && return 0
|
|
1330
|
+
|
|
1331
|
+
# Compute moving average of first half vs second half
|
|
1332
|
+
local first_half_avg second_half_avg
|
|
1333
|
+
first_half_avg=$(echo "$trend_data" | jq '[.[:length/2 | floor][].accuracy] | add / length' 2>/dev/null) || return 0
|
|
1334
|
+
second_half_avg=$(echo "$trend_data" | jq '[.[length/2 | floor:][].accuracy] | add / length' 2>/dev/null) || return 0
|
|
1335
|
+
|
|
1336
|
+
local is_declining
|
|
1337
|
+
is_declining=$(awk -v f="$first_half_avg" -v s="$second_half_avg" 'BEGIN{print (s < f - 5) ? 1 : 0}')
|
|
1338
|
+
|
|
1339
|
+
local is_below_floor
|
|
1340
|
+
is_below_floor=$(awk -v c="$current_accuracy" -v f="$accuracy_floor" 'BEGIN{print (c < f) ? 1 : 0}')
|
|
1341
|
+
|
|
1342
|
+
if [[ "$is_declining" == "1" ]]; then
|
|
1343
|
+
warn "META-LOOP: Accuracy DECLINING after self-tune (${first_half_avg}% -> ${second_half_avg}%)"
|
|
1344
|
+
|
|
1345
|
+
if [[ "$is_below_floor" == "1" ]]; then
|
|
1346
|
+
warn "META-LOOP: Accuracy ${current_accuracy}% below floor ${accuracy_floor}% — reverting heuristics to defaults"
|
|
1347
|
+
# Reset heuristics to empty (forces fallback to keyword_match defaults)
|
|
1348
|
+
local tmp_heur
|
|
1349
|
+
tmp_heur=$(mktemp)
|
|
1350
|
+
echo '{"keyword_weights": {}, "meta_reverted_at": "'"$(now_iso)"'", "revert_reason": "accuracy_below_floor"}' > "$tmp_heur"
|
|
1351
|
+
_recruit_locked_write "$HEURISTICS_DB" "$tmp_heur" || rm -f "$tmp_heur"
|
|
1352
|
+
emit_event "recruit_meta_revert" "accuracy=${current_accuracy}" "floor=${accuracy_floor}" "reason=declining_below_floor"
|
|
1353
|
+
else
|
|
1354
|
+
emit_event "recruit_meta_warning" "accuracy=${current_accuracy}" "trend=declining" "first_half=${first_half_avg}" "second_half=${second_half_avg}"
|
|
1355
|
+
fi
|
|
1356
|
+
elif [[ "$is_below_floor" == "1" ]]; then
|
|
1357
|
+
warn "META-LOOP: Accuracy ${current_accuracy}% below floor ${accuracy_floor}%"
|
|
1358
|
+
emit_event "recruit_meta_warning" "accuracy=${current_accuracy}" "floor=${accuracy_floor}" "trend=low"
|
|
1359
|
+
fi
|
|
1218
1360
|
}
|
|
1219
1361
|
|
|
1220
1362
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1610,8 +1752,9 @@ cmd_self_tune() {
|
|
|
1610
1752
|
local total_matches
|
|
1611
1753
|
total_matches=$(wc -l < "$MATCH_HISTORY" 2>/dev/null | tr -d ' ')
|
|
1612
1754
|
|
|
1613
|
-
|
|
1614
|
-
|
|
1755
|
+
local min_matches="${RECRUIT_SELF_TUNE_MIN_MATCHES:-5}"
|
|
1756
|
+
if [[ "$total_matches" -lt "$min_matches" ]]; then
|
|
1757
|
+
warn "Need at least ${min_matches} matches to self-tune (have ${total_matches})"
|
|
1615
1758
|
return 0
|
|
1616
1759
|
fi
|
|
1617
1760
|
|
|
@@ -1962,7 +2105,7 @@ cmd_promote() {
|
|
|
1962
2105
|
local agent_count
|
|
1963
2106
|
agent_count=$(echo "$pop_stats" | jq -r '.count')
|
|
1964
2107
|
|
|
1965
|
-
local promote_sr_threshold=
|
|
2108
|
+
local promote_sr_threshold="${RECRUIT_PROMOTE_SUCCESS:-85}"
|
|
1966
2109
|
local promote_q_threshold=9
|
|
1967
2110
|
local demote_sr_threshold=60
|
|
1968
2111
|
local demote_q_threshold=5
|
|
@@ -2190,6 +2333,7 @@ ${BOLD}AGI-LEVEL (Tier 3)${RESET}
|
|
|
2190
2333
|
${CYAN}mind${RESET} [agent-id] Theory of mind: agent working style profiles
|
|
2191
2334
|
${CYAN}decompose${RESET} "<goal>" Break vague goals into sub-tasks + role assignments
|
|
2192
2335
|
${CYAN}self-tune${RESET} Self-modify keyword→role heuristics from outcomes
|
|
2336
|
+
${CYAN}audit${RESET} Negative-compounding self-audit of all loops and integrations
|
|
2193
2337
|
|
|
2194
2338
|
${BOLD}EXAMPLES${RESET}
|
|
2195
2339
|
${DIM}shipwright recruit match "Add OAuth2 authentication"${RESET}
|
|
@@ -2209,6 +2353,241 @@ ${DIM}Store: ~/.shipwright/recruitment/${RESET}
|
|
|
2209
2353
|
EOF
|
|
2210
2354
|
}
|
|
2211
2355
|
|
|
2356
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
2357
|
+
# NEGATIVE-COMPOUNDING FEEDBACK LOOP (Self-Audit)
|
|
2358
|
+
#
|
|
2359
|
+
# This command systematically asks every hard question about the system:
|
|
2360
|
+
# - What's broken? What's not wired? What's not fully implemented?
|
|
2361
|
+
# - Are feedback loops closed? Does data actually flow?
|
|
2362
|
+
# - Are integrations proven or just claimed?
|
|
2363
|
+
#
|
|
2364
|
+
# Findings compound: each audit creates a score, the score feeds into the
|
|
2365
|
+
# system, and declining scores trigger automated remediation.
|
|
2366
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
2367
|
+
|
|
2368
|
+
cmd_audit() {
|
|
2369
|
+
ensure_recruit_dir
|
|
2370
|
+
|
|
2371
|
+
info "Running negative-compounding self-audit..."
|
|
2372
|
+
echo ""
|
|
2373
|
+
|
|
2374
|
+
local total_checks=0
|
|
2375
|
+
local pass_count=0
|
|
2376
|
+
local fail_count=0
|
|
2377
|
+
local warnings=()
|
|
2378
|
+
local failures=()
|
|
2379
|
+
|
|
2380
|
+
_audit_check() {
|
|
2381
|
+
local name="$1"
|
|
2382
|
+
local result="$2" # pass|fail|warn
|
|
2383
|
+
local detail="$3"
|
|
2384
|
+
total_checks=$((total_checks + 1))
|
|
2385
|
+
case "$result" in
|
|
2386
|
+
pass) pass_count=$((pass_count + 1)); echo -e " ${GREEN}✓${RESET} $name" ;;
|
|
2387
|
+
fail) fail_count=$((fail_count + 1)); failures+=("$name: $detail"); echo -e " ${RED}✗${RESET} $name — $detail" ;;
|
|
2388
|
+
warn) pass_count=$((pass_count + 1)); warnings+=("$name: $detail"); echo -e " ${YELLOW}⚠${RESET} $name — $detail" ;;
|
|
2389
|
+
esac
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
echo -e "${BOLD}1. DATA STORES${RESET}"
|
|
2393
|
+
|
|
2394
|
+
# Check all data stores exist and are valid JSON
|
|
2395
|
+
for db_name in ROLES_DB PROFILES_DB ROLE_USAGE_DB HEURISTICS_DB META_LEARNING_DB AGENT_MINDS_DB; do
|
|
2396
|
+
local db_path="${!db_name}"
|
|
2397
|
+
if [[ -f "$db_path" ]]; then
|
|
2398
|
+
if jq empty "$db_path" 2>/dev/null; then
|
|
2399
|
+
_audit_check "$db_name is valid JSON" "pass" ""
|
|
2400
|
+
else
|
|
2401
|
+
_audit_check "$db_name is valid JSON" "fail" "corrupted JSON"
|
|
2402
|
+
fi
|
|
2403
|
+
else
|
|
2404
|
+
_audit_check "$db_name exists" "warn" "not yet created (will be on first use)"
|
|
2405
|
+
fi
|
|
2406
|
+
done
|
|
2407
|
+
[[ -f "$MATCH_HISTORY" ]] && _audit_check "MATCH_HISTORY exists" "pass" "" || _audit_check "MATCH_HISTORY exists" "warn" "no matches yet"
|
|
2408
|
+
echo ""
|
|
2409
|
+
|
|
2410
|
+
echo -e "${BOLD}2. FEEDBACK LOOPS${RESET}"
|
|
2411
|
+
|
|
2412
|
+
# Loop 1: Role usage tracking — do successes/failures get updated?
|
|
2413
|
+
if [[ -f "$ROLE_USAGE_DB" ]]; then
|
|
2414
|
+
local has_outcomes
|
|
2415
|
+
has_outcomes=$(jq '[.[]] | map(select(.successes > 0 or .failures > 0)) | length' "$ROLE_USAGE_DB" 2>/dev/null || echo "0")
|
|
2416
|
+
if [[ "$has_outcomes" -gt 0 ]]; then
|
|
2417
|
+
_audit_check "Role usage tracks outcomes (successes/failures)" "pass" ""
|
|
2418
|
+
else
|
|
2419
|
+
_audit_check "Role usage tracks outcomes (successes/failures)" "warn" "all roles have 0 successes & 0 failures — run pipelines first"
|
|
2420
|
+
fi
|
|
2421
|
+
else
|
|
2422
|
+
_audit_check "Role usage tracks outcomes" "warn" "no role-usage.json yet"
|
|
2423
|
+
fi
|
|
2424
|
+
|
|
2425
|
+
# Loop 2: Match → outcome linkage
|
|
2426
|
+
if [[ -f "$MATCH_HISTORY" ]]; then
|
|
2427
|
+
local has_match_ids
|
|
2428
|
+
has_match_ids=$(head -5 "$MATCH_HISTORY" | jq -r '.match_id // empty' 2>/dev/null | head -1)
|
|
2429
|
+
if [[ -n "$has_match_ids" ]]; then
|
|
2430
|
+
_audit_check "Match history has match_id for outcome linkage" "pass" ""
|
|
2431
|
+
else
|
|
2432
|
+
_audit_check "Match history has match_id for outcome linkage" "fail" "old records lack match_id — run new matches"
|
|
2433
|
+
fi
|
|
2434
|
+
|
|
2435
|
+
local resolved_outcomes
|
|
2436
|
+
resolved_outcomes=$(grep -cE '"outcome":"(success|failure)"' "$MATCH_HISTORY" 2>/dev/null | tr -d '[:space:]' || true)
|
|
2437
|
+
resolved_outcomes="${resolved_outcomes:-0}"
|
|
2438
|
+
local total_mh
|
|
2439
|
+
total_mh=$(wc -l < "$MATCH_HISTORY" 2>/dev/null | tr -d ' ')
|
|
2440
|
+
if [[ "$resolved_outcomes" -gt 0 ]]; then
|
|
2441
|
+
_audit_check "Match outcomes backfilled" "pass" "${resolved_outcomes}/${total_mh} resolved"
|
|
2442
|
+
else
|
|
2443
|
+
_audit_check "Match outcomes backfilled" "warn" "0/${total_mh} resolved — need pipeline outcomes"
|
|
2444
|
+
fi
|
|
2445
|
+
else
|
|
2446
|
+
_audit_check "Match → outcome linkage" "warn" "no match history yet"
|
|
2447
|
+
fi
|
|
2448
|
+
|
|
2449
|
+
# Loop 3: Self-tune effectiveness
|
|
2450
|
+
if [[ -f "$HEURISTICS_DB" ]]; then
|
|
2451
|
+
local kw_count
|
|
2452
|
+
kw_count=$(jq '.keyword_weights | length' "$HEURISTICS_DB" 2>/dev/null || echo "0")
|
|
2453
|
+
if [[ "$kw_count" -gt 0 ]]; then
|
|
2454
|
+
_audit_check "Self-tune has learned keyword weights" "pass" "${kw_count} keywords"
|
|
2455
|
+
else
|
|
2456
|
+
_audit_check "Self-tune has learned keyword weights" "warn" "empty — need more match/outcome data"
|
|
2457
|
+
fi
|
|
2458
|
+
else
|
|
2459
|
+
_audit_check "Self-tune active" "warn" "no heuristics.json yet"
|
|
2460
|
+
fi
|
|
2461
|
+
|
|
2462
|
+
# Loop 4: Meta-learning accuracy trend
|
|
2463
|
+
if [[ -f "$META_LEARNING_DB" ]]; then
|
|
2464
|
+
local trend_len
|
|
2465
|
+
trend_len=$(jq '.accuracy_trend | length' "$META_LEARNING_DB" 2>/dev/null || echo "0")
|
|
2466
|
+
if [[ "$trend_len" -ge 3 ]]; then
|
|
2467
|
+
local latest_acc
|
|
2468
|
+
latest_acc=$(jq '.accuracy_trend[-1].accuracy' "$META_LEARNING_DB" 2>/dev/null || echo "0")
|
|
2469
|
+
local floor="${RECRUIT_META_ACCURACY_FLOOR:-50}"
|
|
2470
|
+
if awk -v a="$latest_acc" -v f="$floor" 'BEGIN{exit !(a >= f)}'; then
|
|
2471
|
+
_audit_check "Meta-learning accuracy above floor" "pass" "${latest_acc}% >= ${floor}%"
|
|
2472
|
+
else
|
|
2473
|
+
_audit_check "Meta-learning accuracy above floor" "fail" "${latest_acc}% < ${floor}%"
|
|
2474
|
+
fi
|
|
2475
|
+
else
|
|
2476
|
+
_audit_check "Meta-learning has accuracy trend" "warn" "only ${trend_len} data points (need 3+)"
|
|
2477
|
+
fi
|
|
2478
|
+
else
|
|
2479
|
+
_audit_check "Meta-learning active" "warn" "no meta-learning.json yet"
|
|
2480
|
+
fi
|
|
2481
|
+
echo ""
|
|
2482
|
+
|
|
2483
|
+
echo -e "${BOLD}3. INTEGRATION WIRING${RESET}"
|
|
2484
|
+
|
|
2485
|
+
# Check each integration exists in the source
|
|
2486
|
+
for script_check in \
|
|
2487
|
+
"sw-pipeline.sh:sw-recruit.sh.*match.*--json:pipeline model selection" \
|
|
2488
|
+
"sw-pipeline.sh:sw-recruit.sh.*ingest-pipeline:pipeline auto-ingest" \
|
|
2489
|
+
"sw-pipeline.sh:agent_id=.*PIPELINE_AGENT_ID:pipeline agent_id in events" \
|
|
2490
|
+
"sw-pm.sh:sw-recruit.sh.*team.*--json:PM team integration" \
|
|
2491
|
+
"sw-triage.sh:sw-recruit.sh.*team.*--json:triage team integration" \
|
|
2492
|
+
"sw-loop.sh:sw-recruit.sh.*team.*--json:loop role assignment" \
|
|
2493
|
+
"sw-loop.sh:recruit_roles_db:loop recruit DB descriptions" \
|
|
2494
|
+
"sw-swarm.sh:sw-recruit.sh.*match.*--json:swarm type selection" \
|
|
2495
|
+
"sw-autonomous.sh:sw-recruit.sh.*match.*--json:autonomous model selection" \
|
|
2496
|
+
"sw-autonomous.sh:sw-recruit.sh.*team.*--json:autonomous team recommendation" \
|
|
2497
|
+
"sw-pipeline.sh:intelligence_validate_prediction:pipeline intelligence validation" \
|
|
2498
|
+
"sw-pipeline.sh:confirm-anomaly:pipeline predictive anomaly confirmation" \
|
|
2499
|
+
"sw-pipeline.sh:fix-outcome.*true.*false:pipeline memory negative fix-outcome" \
|
|
2500
|
+
"sw-triage.sh:gh_available=false:triage offline fallback support"; do
|
|
2501
|
+
local sc="${script_check%%:*}"; local rest="${script_check#*:}"
|
|
2502
|
+
local pat="${rest%%:*}"; local desc="${rest#*:}"
|
|
2503
|
+
if [[ -f "$SCRIPT_DIR/$sc" ]] && grep -qE "$pat" "$SCRIPT_DIR/$sc" 2>/dev/null; then
|
|
2504
|
+
_audit_check "$desc ($sc)" "pass" ""
|
|
2505
|
+
else
|
|
2506
|
+
_audit_check "$desc ($sc)" "fail" "pattern not found"
|
|
2507
|
+
fi
|
|
2508
|
+
done
|
|
2509
|
+
echo ""
|
|
2510
|
+
|
|
2511
|
+
echo -e "${BOLD}4. POLICY GOVERNANCE${RESET}"
|
|
2512
|
+
|
|
2513
|
+
if [[ -f "$POLICY_FILE" ]]; then
|
|
2514
|
+
local has_recruit_section
|
|
2515
|
+
has_recruit_section=$(jq '.recruit // empty' "$POLICY_FILE" 2>/dev/null)
|
|
2516
|
+
if [[ -n "$has_recruit_section" ]]; then
|
|
2517
|
+
_audit_check "policy.json has recruit section" "pass" ""
|
|
2518
|
+
else
|
|
2519
|
+
_audit_check "policy.json has recruit section" "fail" "missing recruit section"
|
|
2520
|
+
fi
|
|
2521
|
+
else
|
|
2522
|
+
_audit_check "policy.json exists" "fail" "config/policy.json not found"
|
|
2523
|
+
fi
|
|
2524
|
+
echo ""
|
|
2525
|
+
|
|
2526
|
+
echo -e "${BOLD}5. AUTOMATION TRIGGERS${RESET}"
|
|
2527
|
+
|
|
2528
|
+
grep -q "cmd_self_tune.*2>/dev/null" "$SCRIPT_DIR/sw-recruit.sh" && \
|
|
2529
|
+
_audit_check "Self-tune auto-triggers after ingest" "pass" "" || \
|
|
2530
|
+
_audit_check "Self-tune auto-triggers after ingest" "fail" "not wired"
|
|
2531
|
+
|
|
2532
|
+
grep -q "cmd_evolve.*2>/dev/null" "$SCRIPT_DIR/sw-recruit.sh" && \
|
|
2533
|
+
_audit_check "Evolve auto-triggers after sufficient outcomes" "pass" "" || \
|
|
2534
|
+
_audit_check "Evolve auto-triggers after sufficient outcomes" "fail" "not wired"
|
|
2535
|
+
|
|
2536
|
+
grep -q "_recruit_meta_validate_self_tune" "$SCRIPT_DIR/sw-recruit.sh" && \
|
|
2537
|
+
_audit_check "Meta-validation runs during reflect" "pass" "" || \
|
|
2538
|
+
_audit_check "Meta-validation runs during reflect" "fail" "not wired"
|
|
2539
|
+
echo ""
|
|
2540
|
+
|
|
2541
|
+
# ── Compute score ────────────────────────────────────────────────────────
|
|
2542
|
+
local score
|
|
2543
|
+
score=$(awk -v p="$pass_count" -v t="$total_checks" 'BEGIN{if(t>0) printf "%.1f", (p/t)*100; else print "0"}')
|
|
2544
|
+
|
|
2545
|
+
echo "════════════════════════════════════════════════════════════════"
|
|
2546
|
+
echo -e "${BOLD}AUDIT SCORE:${RESET} ${score}% (${pass_count}/${total_checks} checks passed, ${fail_count} failures, ${#warnings[@]} warnings)"
|
|
2547
|
+
echo "════════════════════════════════════════════════════════════════"
|
|
2548
|
+
|
|
2549
|
+
# Record audit result in events for trend tracking
|
|
2550
|
+
emit_event "recruit_audit" "score=${score}" "passed=${pass_count}" "failed=${fail_count}" "warnings=${#warnings[@]}" "total=${total_checks}"
|
|
2551
|
+
|
|
2552
|
+
# Track audit score trend in meta-learning DB
|
|
2553
|
+
if [[ -f "$META_LEARNING_DB" ]]; then
|
|
2554
|
+
local tmp_audit
|
|
2555
|
+
tmp_audit=$(mktemp)
|
|
2556
|
+
jq --argjson score "$score" --arg ts "$(now_iso)" --argjson fails "$fail_count" '
|
|
2557
|
+
.audit_trend = ((.audit_trend // []) + [{score: $score, ts: $ts, failures: $fails}] | .[-50:])
|
|
2558
|
+
' "$META_LEARNING_DB" > "$tmp_audit" && _recruit_locked_write "$META_LEARNING_DB" "$tmp_audit" || rm -f "$tmp_audit"
|
|
2559
|
+
fi
|
|
2560
|
+
|
|
2561
|
+
# Negative compounding: if score is declining, escalate
|
|
2562
|
+
if [[ -f "$META_LEARNING_DB" ]]; then
|
|
2563
|
+
local audit_trend_len
|
|
2564
|
+
audit_trend_len=$(jq '.audit_trend // [] | length' "$META_LEARNING_DB" 2>/dev/null || echo "0")
|
|
2565
|
+
if [[ "$audit_trend_len" -ge 3 ]]; then
|
|
2566
|
+
local prev_score
|
|
2567
|
+
prev_score=$(jq '.audit_trend[-2].score // 100' "$META_LEARNING_DB" 2>/dev/null || echo "100")
|
|
2568
|
+
if awk -v c="$score" -v p="$prev_score" 'BEGIN{exit !(c < p - 5)}'; then
|
|
2569
|
+
echo ""
|
|
2570
|
+
warn "NEGATIVE COMPOUND: Audit score DECLINED from ${prev_score}% to ${score}%"
|
|
2571
|
+
warn "System health is degrading. Failures that compound:"
|
|
2572
|
+
for f in "${failures[@]}"; do
|
|
2573
|
+
echo -e " ${RED}→${RESET} $f"
|
|
2574
|
+
done
|
|
2575
|
+
emit_event "recruit_audit_decline" "from=${prev_score}" "to=${score}" "failures=${fail_count}"
|
|
2576
|
+
fi
|
|
2577
|
+
fi
|
|
2578
|
+
fi
|
|
2579
|
+
|
|
2580
|
+
if [[ ${#failures[@]} -gt 0 ]]; then
|
|
2581
|
+
echo ""
|
|
2582
|
+
echo -e "${RED}${BOLD}FAILURES REQUIRING ACTION:${RESET}"
|
|
2583
|
+
for f in "${failures[@]}"; do
|
|
2584
|
+
echo -e " ${RED}→${RESET} $f"
|
|
2585
|
+
done
|
|
2586
|
+
fi
|
|
2587
|
+
|
|
2588
|
+
[[ "$fail_count" -gt 0 ]] && return 1 || return 0
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2212
2591
|
# ─── Main Router ──────────────────────────────────────────────────────────
|
|
2213
2592
|
|
|
2214
2593
|
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
@@ -2237,6 +2616,7 @@ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
|
2237
2616
|
mind) cmd_mind "$@" ;;
|
|
2238
2617
|
decompose) cmd_decompose "$@" ;;
|
|
2239
2618
|
self-tune) cmd_self_tune ;;
|
|
2619
|
+
audit) cmd_audit ;;
|
|
2240
2620
|
help|--help|-h) cmd_help ;;
|
|
2241
2621
|
*)
|
|
2242
2622
|
error "Unknown command: ${cmd}"
|