shipwright-cli 2.3.1 → 3.0.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/README.md +95 -28
- package/completions/_shipwright +1 -1
- package/completions/shipwright.bash +3 -8
- package/completions/shipwright.fish +1 -1
- package/config/defaults.json +111 -0
- package/config/event-schema.json +81 -0
- package/config/policy.json +155 -2
- package/config/policy.schema.json +162 -1
- package/dashboard/coverage/coverage-summary.json +14 -0
- package/dashboard/public/index.html +1 -1
- package/dashboard/server.ts +306 -17
- package/dashboard/src/components/charts/bar.test.ts +79 -0
- package/dashboard/src/components/charts/donut.test.ts +68 -0
- package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
- package/dashboard/src/components/charts/sparkline.test.ts +125 -0
- package/dashboard/src/core/api.test.ts +309 -0
- package/dashboard/src/core/helpers.test.ts +301 -0
- package/dashboard/src/core/router.test.ts +307 -0
- package/dashboard/src/core/router.ts +7 -0
- package/dashboard/src/core/sse.test.ts +144 -0
- package/dashboard/src/views/metrics.test.ts +186 -0
- package/dashboard/src/views/overview.test.ts +173 -0
- package/dashboard/src/views/pipelines.test.ts +183 -0
- package/dashboard/src/views/team.test.ts +253 -0
- package/dashboard/vitest.config.ts +14 -5
- package/docs/TIPS.md +1 -1
- package/docs/patterns/README.md +1 -1
- package/package.json +15 -5
- package/scripts/adapters/docker-deploy.sh +1 -1
- package/scripts/adapters/tmux-adapter.sh +11 -1
- package/scripts/adapters/wezterm-adapter.sh +1 -1
- package/scripts/check-version-consistency.sh +1 -1
- package/scripts/lib/architecture.sh +126 -0
- package/scripts/lib/bootstrap.sh +75 -0
- package/scripts/lib/compat.sh +89 -6
- package/scripts/lib/config.sh +91 -0
- package/scripts/lib/daemon-adaptive.sh +3 -3
- package/scripts/lib/daemon-dispatch.sh +39 -16
- package/scripts/lib/daemon-health.sh +1 -1
- package/scripts/lib/daemon-patrol.sh +24 -12
- package/scripts/lib/daemon-poll.sh +37 -25
- package/scripts/lib/daemon-state.sh +115 -23
- package/scripts/lib/daemon-triage.sh +30 -8
- package/scripts/lib/fleet-failover.sh +63 -0
- package/scripts/lib/helpers.sh +30 -6
- package/scripts/lib/pipeline-detection.sh +2 -2
- package/scripts/lib/pipeline-github.sh +9 -9
- package/scripts/lib/pipeline-intelligence.sh +85 -35
- package/scripts/lib/pipeline-quality-checks.sh +16 -16
- package/scripts/lib/pipeline-quality.sh +1 -1
- package/scripts/lib/pipeline-stages.sh +242 -28
- package/scripts/lib/pipeline-state.sh +40 -4
- package/scripts/lib/test-helpers.sh +247 -0
- package/scripts/postinstall.mjs +3 -11
- package/scripts/sw +10 -4
- package/scripts/sw-activity.sh +1 -11
- package/scripts/sw-adaptive.sh +109 -85
- package/scripts/sw-adversarial.sh +4 -14
- package/scripts/sw-architecture-enforcer.sh +1 -11
- package/scripts/sw-auth.sh +8 -17
- package/scripts/sw-autonomous.sh +111 -49
- package/scripts/sw-changelog.sh +1 -11
- package/scripts/sw-checkpoint.sh +144 -20
- package/scripts/sw-ci.sh +2 -12
- package/scripts/sw-cleanup.sh +13 -17
- package/scripts/sw-code-review.sh +16 -36
- package/scripts/sw-connect.sh +5 -12
- package/scripts/sw-context.sh +9 -26
- package/scripts/sw-cost.sh +6 -16
- package/scripts/sw-daemon.sh +75 -70
- package/scripts/sw-dashboard.sh +57 -17
- package/scripts/sw-db.sh +506 -15
- package/scripts/sw-decompose.sh +1 -11
- package/scripts/sw-deps.sh +15 -25
- package/scripts/sw-developer-simulation.sh +1 -11
- package/scripts/sw-discovery.sh +112 -30
- package/scripts/sw-doc-fleet.sh +7 -17
- package/scripts/sw-docs-agent.sh +6 -16
- package/scripts/sw-docs.sh +4 -12
- package/scripts/sw-doctor.sh +134 -43
- package/scripts/sw-dora.sh +11 -19
- package/scripts/sw-durable.sh +35 -52
- package/scripts/sw-e2e-orchestrator.sh +11 -27
- package/scripts/sw-eventbus.sh +115 -115
- package/scripts/sw-evidence.sh +748 -0
- package/scripts/sw-feedback.sh +3 -13
- package/scripts/sw-fix.sh +2 -20
- package/scripts/sw-fleet-discover.sh +1 -11
- package/scripts/sw-fleet-viz.sh +10 -18
- package/scripts/sw-fleet.sh +13 -17
- package/scripts/sw-github-app.sh +6 -16
- package/scripts/sw-github-checks.sh +1 -11
- package/scripts/sw-github-deploy.sh +1 -11
- package/scripts/sw-github-graphql.sh +2 -12
- package/scripts/sw-guild.sh +1 -11
- package/scripts/sw-heartbeat.sh +49 -12
- package/scripts/sw-hygiene.sh +45 -43
- package/scripts/sw-incident.sh +284 -67
- package/scripts/sw-init.sh +35 -37
- package/scripts/sw-instrument.sh +1 -11
- package/scripts/sw-intelligence.sh +362 -51
- package/scripts/sw-jira.sh +5 -14
- package/scripts/sw-launchd.sh +2 -12
- package/scripts/sw-linear.sh +8 -17
- package/scripts/sw-logs.sh +4 -12
- package/scripts/sw-loop.sh +641 -90
- package/scripts/sw-memory.sh +243 -17
- package/scripts/sw-mission-control.sh +2 -12
- package/scripts/sw-model-router.sh +73 -34
- package/scripts/sw-otel.sh +11 -21
- package/scripts/sw-oversight.sh +1 -11
- package/scripts/sw-patrol-meta.sh +5 -11
- package/scripts/sw-pipeline-composer.sh +7 -17
- package/scripts/sw-pipeline-vitals.sh +1 -11
- package/scripts/sw-pipeline.sh +478 -122
- package/scripts/sw-pm.sh +2 -12
- package/scripts/sw-pr-lifecycle.sh +203 -29
- package/scripts/sw-predictive.sh +16 -22
- package/scripts/sw-prep.sh +6 -16
- package/scripts/sw-ps.sh +1 -11
- package/scripts/sw-public-dashboard.sh +2 -12
- package/scripts/sw-quality.sh +77 -10
- package/scripts/sw-reaper.sh +1 -11
- package/scripts/sw-recruit.sh +15 -25
- package/scripts/sw-regression.sh +11 -21
- package/scripts/sw-release-manager.sh +19 -28
- package/scripts/sw-release.sh +8 -16
- package/scripts/sw-remote.sh +1 -11
- package/scripts/sw-replay.sh +48 -44
- package/scripts/sw-retro.sh +70 -92
- package/scripts/sw-review-rerun.sh +220 -0
- package/scripts/sw-scale.sh +109 -32
- package/scripts/sw-security-audit.sh +12 -22
- package/scripts/sw-self-optimize.sh +239 -23
- package/scripts/sw-session.sh +3 -13
- package/scripts/sw-setup.sh +8 -18
- package/scripts/sw-standup.sh +5 -15
- package/scripts/sw-status.sh +32 -23
- package/scripts/sw-strategic.sh +129 -13
- package/scripts/sw-stream.sh +1 -11
- package/scripts/sw-swarm.sh +76 -36
- package/scripts/sw-team-stages.sh +10 -20
- package/scripts/sw-templates.sh +4 -14
- package/scripts/sw-testgen.sh +3 -13
- package/scripts/sw-tmux-pipeline.sh +1 -19
- package/scripts/sw-tmux-role-color.sh +0 -10
- package/scripts/sw-tmux-status.sh +3 -11
- package/scripts/sw-tmux.sh +2 -20
- package/scripts/sw-trace.sh +1 -19
- package/scripts/sw-tracker-github.sh +0 -10
- package/scripts/sw-tracker-jira.sh +1 -11
- package/scripts/sw-tracker-linear.sh +1 -11
- package/scripts/sw-tracker.sh +7 -24
- package/scripts/sw-triage.sh +24 -34
- package/scripts/sw-upgrade.sh +5 -23
- package/scripts/sw-ux.sh +1 -19
- package/scripts/sw-webhook.sh +18 -32
- package/scripts/sw-widgets.sh +3 -21
- package/scripts/sw-worktree.sh +11 -27
- package/scripts/update-homebrew-sha.sh +67 -0
- package/templates/pipelines/tdd.json +72 -0
- package/scripts/sw-pipeline.sh.mock +0 -7
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="
|
|
9
|
+
VERSION="3.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="${REPO_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
12
12
|
|
|
@@ -17,6 +17,8 @@ REPO_DIR="${REPO_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
|
17
17
|
# Canonical helpers (colors, output, events)
|
|
18
18
|
# shellcheck source=lib/helpers.sh
|
|
19
19
|
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
20
|
+
[[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
|
|
21
|
+
[[ -f "$SCRIPT_DIR/sw-db.sh" ]] && source "$SCRIPT_DIR/sw-db.sh"
|
|
20
22
|
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
21
23
|
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
22
24
|
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
@@ -34,22 +36,104 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
34
36
|
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
35
37
|
}
|
|
36
38
|
fi
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
RESET="${RESET:-\033[0m}"
|
|
39
|
+
# ─── Bootstrap (cold-start) ──────────────────────────────────────────────────
|
|
40
|
+
# Ensure optimization data exists when intelligence is first used
|
|
41
|
+
[[ -f "$SCRIPT_DIR/lib/bootstrap.sh" ]] && source "$SCRIPT_DIR/lib/bootstrap.sh"
|
|
42
|
+
if type bootstrap_optimization &>/dev/null 2>&1; then
|
|
43
|
+
if [[ ! -f "$HOME/.shipwright/optimization/iteration-model.json" ]]; then
|
|
44
|
+
bootstrap_optimization 2>/dev/null || true
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
46
47
|
|
|
47
48
|
# ─── Intelligence Configuration ─────────────────────────────────────────────
|
|
48
49
|
INTELLIGENCE_CACHE="${REPO_DIR}/.claude/intelligence-cache.json"
|
|
49
50
|
INTELLIGENCE_CONFIG_DIR="${HOME}/.shipwright/optimization"
|
|
50
51
|
CACHE_TTL_CONFIG="${INTELLIGENCE_CONFIG_DIR}/cache-ttl.json"
|
|
51
52
|
CACHE_STATS_FILE="${INTELLIGENCE_CONFIG_DIR}/cache-stats.json"
|
|
52
|
-
DEFAULT_CACHE_TTL
|
|
53
|
+
DEFAULT_CACHE_TTL=$(_config_get_int "intelligence.cache_ttl" 3600 2>/dev/null || echo 3600) # 1 hour (fallback)
|
|
54
|
+
|
|
55
|
+
# ─── Adaptive Thresholds ─────────────────────────────────────────────────────
|
|
56
|
+
# Compute thresholds from historical metrics distribution (mean + N*stddev when DB available)
|
|
57
|
+
|
|
58
|
+
adaptive_threshold() {
|
|
59
|
+
local metric_name="$1" default_value="${2:-3.0}" sigma_multiplier="${3:-2.0}"
|
|
60
|
+
|
|
61
|
+
if ! db_available 2>/dev/null; then
|
|
62
|
+
echo "$default_value"
|
|
63
|
+
return
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
local stats
|
|
67
|
+
stats=$(_db_query "SELECT AVG(value) as mean,
|
|
68
|
+
CASE WHEN COUNT(*) > 1 THEN
|
|
69
|
+
SQRT(SUM((value - (SELECT AVG(value) FROM metrics WHERE metric_name = '$metric_name')) *
|
|
70
|
+
(value - (SELECT AVG(value) FROM metrics WHERE metric_name = '$metric_name'))) / (COUNT(*) - 1))
|
|
71
|
+
ELSE 0 END as stddev,
|
|
72
|
+
COUNT(*) as n
|
|
73
|
+
FROM metrics WHERE metric_name = '$metric_name' AND created_at > datetime('now', '-30 days');" 2>/dev/null || echo "")
|
|
74
|
+
|
|
75
|
+
if [[ -z "$stats" ]]; then
|
|
76
|
+
echo "$default_value"
|
|
77
|
+
return
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
local mean stddev n
|
|
81
|
+
IFS='|' read -r mean stddev n <<< "$stats"
|
|
82
|
+
|
|
83
|
+
# Need minimum sample size
|
|
84
|
+
if [[ "${n:-0}" -lt 10 ]]; then
|
|
85
|
+
echo "$default_value"
|
|
86
|
+
return
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# Adaptive: mean + sigma_multiplier * stddev
|
|
90
|
+
awk "BEGIN { printf \"%.2f\", $mean + ($sigma_multiplier * $stddev) }"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Get adaptive anomaly threshold
|
|
94
|
+
get_anomaly_threshold() {
|
|
95
|
+
adaptive_threshold "anomaly_score" "$(_config_get "intelligence.anomaly_threshold" 3.0)" 2.0
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Get adaptive quality threshold
|
|
99
|
+
get_quality_threshold() {
|
|
100
|
+
local base
|
|
101
|
+
base=$(_config_get "quality.gate_score_threshold" 70)
|
|
102
|
+
# For quality, use percentile-based approach: mean - 1*stddev as minimum
|
|
103
|
+
if ! db_available 2>/dev/null; then
|
|
104
|
+
echo "$base"
|
|
105
|
+
return
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
local mean_score
|
|
109
|
+
mean_score=$(_db_query "SELECT AVG(value) FROM metrics WHERE metric_name = 'quality_score' AND created_at > datetime('now', '-30 days');" 2>/dev/null || echo "")
|
|
110
|
+
|
|
111
|
+
if [[ -z "$mean_score" || "$mean_score" == "" ]]; then
|
|
112
|
+
echo "$base"
|
|
113
|
+
return
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# Use the higher of: base threshold or historical mean - 10%
|
|
117
|
+
local adaptive_min
|
|
118
|
+
adaptive_min=$(awk "BEGIN { printf \"%d\", $mean_score * 0.9 }")
|
|
119
|
+
if [[ "$adaptive_min" -gt "$base" ]]; then
|
|
120
|
+
echo "$adaptive_min"
|
|
121
|
+
else
|
|
122
|
+
echo "$base"
|
|
123
|
+
fi
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Store threshold values for debugging
|
|
127
|
+
persist_thresholds() {
|
|
128
|
+
if db_available 2>/dev/null; then
|
|
129
|
+
local anomaly_t quality_t
|
|
130
|
+
anomaly_t=$(get_anomaly_threshold)
|
|
131
|
+
quality_t=$(get_quality_threshold)
|
|
132
|
+
_db_exec "INSERT OR REPLACE INTO _sync_metadata (key, value, updated_at) VALUES
|
|
133
|
+
('threshold.anomaly', '$anomaly_t', '$(now_iso)'),
|
|
134
|
+
('threshold.quality', '$quality_t', '$(now_iso)');" 2>/dev/null || true
|
|
135
|
+
fi
|
|
136
|
+
}
|
|
53
137
|
|
|
54
138
|
# Load adaptive cache TTL from config or use default
|
|
55
139
|
_intelligence_get_cache_ttl() {
|
|
@@ -146,8 +230,15 @@ _intelligence_enabled() {
|
|
|
146
230
|
local config="${REPO_DIR}/.claude/daemon-config.json"
|
|
147
231
|
if [[ -f "$config" ]]; then
|
|
148
232
|
local enabled
|
|
149
|
-
enabled=$(jq -r '.intelligence.enabled //
|
|
150
|
-
[[ "$enabled" == "true" ]]
|
|
233
|
+
enabled=$(jq -r '.intelligence.enabled // "auto"' "$config" 2>/dev/null || echo "auto")
|
|
234
|
+
if [[ "$enabled" == "true" ]]; then
|
|
235
|
+
return 0
|
|
236
|
+
elif [[ "$enabled" == "auto" ]]; then
|
|
237
|
+
# Auto: enabled when Claude CLI is available
|
|
238
|
+
command -v claude &>/dev/null
|
|
239
|
+
else
|
|
240
|
+
return 1
|
|
241
|
+
fi
|
|
151
242
|
else
|
|
152
243
|
return 1
|
|
153
244
|
fi
|
|
@@ -248,9 +339,11 @@ _intelligence_call_claude() {
|
|
|
248
339
|
|
|
249
340
|
# Call Claude (--print mode returns raw text response)
|
|
250
341
|
# Use timeout (gtimeout on macOS via coreutils, timeout on Linux) to prevent hangs
|
|
342
|
+
local _claude_timeout
|
|
343
|
+
_claude_timeout=$(_config_get_int "intelligence.claude_timeout" 60 2>/dev/null || echo 60)
|
|
251
344
|
local _timeout_cmd=""
|
|
252
|
-
if command -v gtimeout
|
|
253
|
-
elif command -v timeout
|
|
345
|
+
if command -v gtimeout >/dev/null 2>&1; then _timeout_cmd="gtimeout $_claude_timeout"
|
|
346
|
+
elif command -v timeout >/dev/null 2>&1; then _timeout_cmd="timeout $_claude_timeout"
|
|
254
347
|
fi
|
|
255
348
|
|
|
256
349
|
local response
|
|
@@ -291,6 +384,86 @@ _intelligence_call_claude() {
|
|
|
291
384
|
return 0
|
|
292
385
|
}
|
|
293
386
|
|
|
387
|
+
# ─── Data-Driven Fallbacks (when Claude unavailable) ─────────────────────────
|
|
388
|
+
|
|
389
|
+
_intelligence_fallback_analyze() {
|
|
390
|
+
local title="$1" body="$2" labels="$3"
|
|
391
|
+
|
|
392
|
+
local complexity=5 risk="medium" probability=50 template="standard"
|
|
393
|
+
|
|
394
|
+
# Try historical data first
|
|
395
|
+
local outcomes_file="$HOME/.shipwright/optimization/outcomes.jsonl"
|
|
396
|
+
if [[ -f "$outcomes_file" ]] && command -v jq &>/dev/null; then
|
|
397
|
+
local sample_count
|
|
398
|
+
sample_count=$(wc -l < "$outcomes_file" 2>/dev/null || echo "0")
|
|
399
|
+
|
|
400
|
+
if [[ "$sample_count" -gt 5 ]]; then
|
|
401
|
+
# Compute average complexity from past outcomes
|
|
402
|
+
local avg_complexity
|
|
403
|
+
avg_complexity=$(tail -100 "$outcomes_file" | jq -s '[.[].complexity // 5 | tonumber] | add / length | floor' 2>/dev/null || echo "5")
|
|
404
|
+
[[ "$avg_complexity" -gt 0 && "$avg_complexity" -le 10 ]] && complexity=$avg_complexity
|
|
405
|
+
|
|
406
|
+
# Compute success probability from past outcomes (result: success|failure)
|
|
407
|
+
local success_rate
|
|
408
|
+
success_rate=$(tail -100 "$outcomes_file" | jq -s '[.[] | select(has("result")) | .result // "failure"] | (map(select(. == "success" or . == "completed")) | length) as $s | length as $t | if $t > 0 then ($s * 100 / $t | floor) else 50 end' 2>/dev/null || echo "50")
|
|
409
|
+
[[ "$success_rate" -gt 0 ]] && probability=$success_rate
|
|
410
|
+
fi
|
|
411
|
+
fi
|
|
412
|
+
|
|
413
|
+
# Label-based heuristics (better than nothing)
|
|
414
|
+
if echo "$labels" | grep -qiE 'bug|fix|hotfix' 2>/dev/null; then
|
|
415
|
+
complexity=$((complexity > 3 ? complexity - 2 : complexity))
|
|
416
|
+
risk="low"
|
|
417
|
+
template="hotfix"
|
|
418
|
+
elif echo "$labels" | grep -qiE 'feature|enhancement' 2>/dev/null; then
|
|
419
|
+
complexity=$((complexity + 1 > 10 ? 10 : complexity + 1))
|
|
420
|
+
template="standard"
|
|
421
|
+
elif echo "$labels" | grep -qiE 'refactor|cleanup' 2>/dev/null; then
|
|
422
|
+
risk="low"
|
|
423
|
+
template="standard"
|
|
424
|
+
elif echo "$labels" | grep -qiE 'security|vulnerability' 2>/dev/null; then
|
|
425
|
+
risk="high"
|
|
426
|
+
template="standard"
|
|
427
|
+
fi
|
|
428
|
+
|
|
429
|
+
# Title-based heuristics — use awk (no wc/tr dependency)
|
|
430
|
+
local word_count
|
|
431
|
+
word_count=$(echo "$title $body" | awk '{print NF}' 2>/dev/null || echo "5")
|
|
432
|
+
if [[ "$word_count" -lt 10 ]]; then
|
|
433
|
+
complexity=$((complexity > 2 ? complexity - 1 : complexity))
|
|
434
|
+
elif [[ "$word_count" -gt 100 ]]; then
|
|
435
|
+
complexity=$((complexity + 2 > 10 ? 10 : complexity + 2))
|
|
436
|
+
fi
|
|
437
|
+
|
|
438
|
+
echo "{\"complexity\":$complexity,\"risk_level\":\"$risk\",\"success_probability\":$probability,\"recommended_template\":\"$template\"}"
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
_intelligence_fallback_cost() {
|
|
442
|
+
local events_file="$HOME/.shipwright/events.jsonl"
|
|
443
|
+
local avg_cost=0
|
|
444
|
+
if [[ -f "$events_file" ]]; then
|
|
445
|
+
avg_cost=$(grep '"pipeline.completed"' "$events_file" | tail -20 | jq -r '.cost_usd // .total_cost // 0' 2>/dev/null | awk '{s+=$1; n++} END{if(n>0) printf "%.2f", s/n; else print "0"}')
|
|
446
|
+
fi
|
|
447
|
+
echo "{\"estimated_cost_usd\":$avg_cost,\"confidence\":\"historical\"}"
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
_intelligence_fallback_compose() {
|
|
451
|
+
local issue_analysis="${1:-"{}"}"
|
|
452
|
+
local complexity risk_level
|
|
453
|
+
complexity=$(echo "$issue_analysis" | jq -r '.complexity // 5' 2>/dev/null || echo "5")
|
|
454
|
+
risk_level=$(echo "$issue_analysis" | jq -r '.risk_level // "medium"' 2>/dev/null || echo "medium")
|
|
455
|
+
# Build sensible default stages from complexity: low=minimal, high=full
|
|
456
|
+
local stages_json
|
|
457
|
+
if [[ "$complexity" -le 3 ]]; then
|
|
458
|
+
stages_json='[{"id":"intake","enabled":true,"model":"sonnet","config":{}},{"id":"build","enabled":true,"model":"sonnet","config":{}},{"id":"test","enabled":true,"model":"sonnet","config":{}},{"id":"pr","enabled":true,"model":"sonnet","config":{}}]'
|
|
459
|
+
elif [[ "$complexity" -ge 8 ]]; then
|
|
460
|
+
stages_json='[{"id":"intake","enabled":true,"model":"opus","config":{}},{"id":"plan","enabled":true,"model":"opus","config":{}},{"id":"design","enabled":true,"model":"opus","config":{}},{"id":"build","enabled":true,"model":"sonnet","config":{}},{"id":"test","enabled":true,"model":"sonnet","config":{}},{"id":"review","enabled":true,"model":"sonnet","config":{}},{"id":"pr","enabled":true,"model":"sonnet","config":{}}]'
|
|
461
|
+
else
|
|
462
|
+
stages_json='[{"id":"intake","enabled":true,"model":"sonnet","config":{}},{"id":"build","enabled":true,"model":"sonnet","config":{}},{"id":"test","enabled":true,"model":"sonnet","config":{}},{"id":"pr","enabled":true,"model":"sonnet","config":{}}]'
|
|
463
|
+
fi
|
|
464
|
+
echo "{\"stages\":$stages_json,\"rationale\":\"data-driven fallback from complexity=$complexity risk=$risk_level\"}"
|
|
465
|
+
}
|
|
466
|
+
|
|
294
467
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
295
468
|
# PUBLIC FUNCTIONS
|
|
296
469
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -300,16 +473,25 @@ _intelligence_call_claude() {
|
|
|
300
473
|
intelligence_analyze_issue() {
|
|
301
474
|
local issue_json="${1:-"{}"}"
|
|
302
475
|
|
|
303
|
-
if ! _intelligence_enabled; then
|
|
304
|
-
echo '{"error":"intelligence_disabled","complexity":5,"risk_level":"medium","success_probability":50,"recommended_template":"standard","key_risks":[],"implementation_hints":[]}'
|
|
305
|
-
return 0
|
|
306
|
-
fi
|
|
307
|
-
|
|
308
476
|
local title body labels
|
|
309
477
|
title=$(echo "$issue_json" | jq -r '.title // "untitled"' 2>/dev/null || echo "untitled")
|
|
310
478
|
body=$(echo "$issue_json" | jq -r '.body // ""' 2>/dev/null || echo "")
|
|
311
479
|
labels=$(echo "$issue_json" | jq -r '(.labels // []) | join(", ")' 2>/dev/null || echo "")
|
|
312
480
|
|
|
481
|
+
if ! _intelligence_enabled; then
|
|
482
|
+
local fallback merged
|
|
483
|
+
fallback=$(_intelligence_fallback_analyze "$title" "$body" "$labels" 2>/dev/null) || true
|
|
484
|
+
if [[ -n "$fallback" ]]; then
|
|
485
|
+
merged=$(printf '%s' "$fallback" | jq -c '. + {error: "intelligence_disabled", key_risks: [], implementation_hints: []}' 2>/dev/null)
|
|
486
|
+
fi
|
|
487
|
+
if [[ -n "${merged:-}" ]]; then
|
|
488
|
+
echo "$merged"
|
|
489
|
+
else
|
|
490
|
+
echo '{"error":"intelligence_disabled","complexity":5,"risk_level":"medium","success_probability":50,"recommended_template":"standard","key_risks":[],"implementation_hints":[]}'
|
|
491
|
+
fi
|
|
492
|
+
return 0
|
|
493
|
+
fi
|
|
494
|
+
|
|
313
495
|
local prompt
|
|
314
496
|
prompt="Analyze this GitHub issue for a software project and return ONLY a JSON object (no markdown, no explanation).
|
|
315
497
|
|
|
@@ -337,8 +519,8 @@ Return JSON with exactly these fields:
|
|
|
337
519
|
valid=$(echo "$result" | jq 'has("complexity") and has("risk_level") and has("success_probability") and has("recommended_template")' 2>/dev/null || echo "false")
|
|
338
520
|
|
|
339
521
|
if [[ "$valid" != "true" ]]; then
|
|
340
|
-
warn "Intelligence response missing required fields, using fallback"
|
|
341
|
-
result
|
|
522
|
+
warn "Intelligence response missing required fields, using data-driven fallback"
|
|
523
|
+
result=$(_intelligence_fallback_analyze "$title" "$body" "$labels" | jq -c '. + {key_risks: ["analysis_incomplete"], implementation_hints: []}')
|
|
342
524
|
fi
|
|
343
525
|
|
|
344
526
|
emit_event "intelligence.analysis" \
|
|
@@ -353,7 +535,9 @@ Return JSON with exactly these fields:
|
|
|
353
535
|
echo "$result"
|
|
354
536
|
return 0
|
|
355
537
|
else
|
|
356
|
-
|
|
538
|
+
local fallback
|
|
539
|
+
fallback=$(_intelligence_fallback_analyze "$title" "$body" "$labels")
|
|
540
|
+
echo "$fallback" | jq -c '. + {error: "analysis_failed", key_risks: ["analysis_failed"], implementation_hints: []}' 2>/dev/null || echo '{"error":"analysis_failed","complexity":5,"risk_level":"medium","success_probability":50,"recommended_template":"standard","key_risks":["analysis_failed"],"implementation_hints":[]}'
|
|
357
541
|
return 0
|
|
358
542
|
fi
|
|
359
543
|
}
|
|
@@ -366,7 +550,9 @@ intelligence_compose_pipeline() {
|
|
|
366
550
|
local budget="${3:-0}"
|
|
367
551
|
|
|
368
552
|
if ! _intelligence_enabled; then
|
|
369
|
-
|
|
553
|
+
local fallback
|
|
554
|
+
fallback=$(_intelligence_fallback_compose "$issue_analysis")
|
|
555
|
+
echo "$fallback" | jq -c '. + {error: "intelligence_disabled"}' 2>/dev/null || echo '{"error":"intelligence_disabled","stages":[]}'
|
|
370
556
|
return 0
|
|
371
557
|
fi
|
|
372
558
|
|
|
@@ -402,8 +588,8 @@ Return ONLY a JSON object (no markdown):
|
|
|
402
588
|
has_stages=$(echo "$result" | jq 'has("stages") and (.stages | type == "array")' 2>/dev/null || echo "false")
|
|
403
589
|
|
|
404
590
|
if [[ "$has_stages" != "true" ]]; then
|
|
405
|
-
warn "Pipeline composition missing stages array, using fallback"
|
|
406
|
-
result
|
|
591
|
+
warn "Pipeline composition missing stages array, using data-driven fallback"
|
|
592
|
+
result=$(_intelligence_fallback_compose "$issue_analysis")
|
|
407
593
|
fi
|
|
408
594
|
|
|
409
595
|
emit_event "intelligence.compose" \
|
|
@@ -413,7 +599,9 @@ Return ONLY a JSON object (no markdown):
|
|
|
413
599
|
echo "$result"
|
|
414
600
|
return 0
|
|
415
601
|
else
|
|
416
|
-
|
|
602
|
+
local fallback
|
|
603
|
+
fallback=$(_intelligence_fallback_compose "$issue_analysis")
|
|
604
|
+
echo "$fallback" | jq -c '. + {error: "composition_failed"}' 2>/dev/null || echo '{"error":"composition_failed","stages":[]}'
|
|
417
605
|
return 0
|
|
418
606
|
fi
|
|
419
607
|
}
|
|
@@ -425,7 +613,9 @@ intelligence_predict_cost() {
|
|
|
425
613
|
local historical_data="${2:-"{}"}"
|
|
426
614
|
|
|
427
615
|
if ! _intelligence_enabled; then
|
|
428
|
-
|
|
616
|
+
local fallback
|
|
617
|
+
fallback=$(_intelligence_fallback_cost)
|
|
618
|
+
echo "$fallback" | jq -c '. + {error: "intelligence_disabled", estimated_iterations: 0, likely_failure_stage: "unknown"}' 2>/dev/null || echo '{"error":"intelligence_disabled","estimated_cost_usd":0,"estimated_iterations":0,"likely_failure_stage":"unknown"}'
|
|
429
619
|
return 0
|
|
430
620
|
fi
|
|
431
621
|
|
|
@@ -456,8 +646,8 @@ Based on similar complexity (${complexity}/10) issues, estimate:
|
|
|
456
646
|
valid=$(echo "$result" | jq 'has("estimated_cost_usd") and has("estimated_iterations")' 2>/dev/null || echo "false")
|
|
457
647
|
|
|
458
648
|
if [[ "$valid" != "true" ]]; then
|
|
459
|
-
warn "Cost prediction missing required fields, using fallback"
|
|
460
|
-
result
|
|
649
|
+
warn "Cost prediction missing required fields, using data-driven fallback"
|
|
650
|
+
result=$(_intelligence_fallback_cost | jq -c '. + {estimated_iterations: 3, estimated_tokens: 500000, likely_failure_stage: "test", confidence: 30}')
|
|
461
651
|
fi
|
|
462
652
|
|
|
463
653
|
emit_event "intelligence.prediction" \
|
|
@@ -468,7 +658,9 @@ Based on similar complexity (${complexity}/10) issues, estimate:
|
|
|
468
658
|
echo "$result"
|
|
469
659
|
return 0
|
|
470
660
|
else
|
|
471
|
-
|
|
661
|
+
local fallback
|
|
662
|
+
fallback=$(_intelligence_fallback_cost)
|
|
663
|
+
echo "$fallback" | jq -c '. + {error: "prediction_failed", estimated_iterations: 0, likely_failure_stage: "unknown"}' 2>/dev/null || echo '{"error":"prediction_failed","estimated_cost_usd":0,"estimated_iterations":0,"likely_failure_stage":"unknown"}'
|
|
472
664
|
return 0
|
|
473
665
|
fi
|
|
474
666
|
}
|
|
@@ -712,11 +904,24 @@ intelligence_recommend_model() {
|
|
|
712
904
|
local model="sonnet"
|
|
713
905
|
local reason="default balanced choice"
|
|
714
906
|
|
|
907
|
+
# Budget-constrained: always use haiku regardless of routing (highest priority)
|
|
908
|
+
if [[ "$budget_remaining" != "" ]] && [[ "$(awk -v b="$budget_remaining" 'BEGIN{print (b+0<5)?1:0}')" == "1" ]]; then
|
|
909
|
+
model="haiku"
|
|
910
|
+
reason="budget constrained (< \$5 remaining)"
|
|
911
|
+
local result
|
|
912
|
+
result=$(jq -n --arg model "$model" --arg reason "$reason" --arg stage "$stage" --argjson complexity "$complexity" \
|
|
913
|
+
'{"model": $model, "reason": $reason, "stage": $stage, "complexity": $complexity, "source": "heuristic"}')
|
|
914
|
+
emit_event "intelligence.model" "stage=$stage" "complexity=$complexity" "model=$model" "source=heuristic"
|
|
915
|
+
echo "$result"
|
|
916
|
+
return 0
|
|
917
|
+
fi
|
|
918
|
+
|
|
715
919
|
# Strategy 1: Check historical model routing data
|
|
716
920
|
local routing_file="${HOME}/.shipwright/optimization/model-routing.json"
|
|
717
921
|
if [[ -f "$routing_file" ]]; then
|
|
922
|
+
# Support both formats: .routes.stage (self-optimize) and .stage (legacy)
|
|
718
923
|
local stage_data
|
|
719
|
-
stage_data=$(jq -r --arg s "$stage" '.[$s] // empty' "$routing_file" 2>/dev/null || true)
|
|
924
|
+
stage_data=$(jq -r --arg s "$stage" '.routes[$s] // .[$s] // empty' "$routing_file" 2>/dev/null || true)
|
|
720
925
|
|
|
721
926
|
if [[ -n "$stage_data" && "$stage_data" != "null" ]]; then
|
|
722
927
|
local recommended sonnet_rate sonnet_samples opus_rate opus_samples
|
|
@@ -769,7 +974,7 @@ intelligence_recommend_model() {
|
|
|
769
974
|
fi
|
|
770
975
|
|
|
771
976
|
if [[ "$use_sonnet" == "true" ]]; then
|
|
772
|
-
if [[ "$budget_remaining" != "" ]] && [[ "$(
|
|
977
|
+
if [[ "$budget_remaining" != "" ]] && [[ "$(awk -v b="$budget_remaining" 'BEGIN{print (b+0<5)?1:0}')" == "1" ]]; then
|
|
773
978
|
model="haiku"
|
|
774
979
|
reason="sonnet viable (${sonnet_rate}% success) but budget constrained"
|
|
775
980
|
else
|
|
@@ -786,7 +991,7 @@ intelligence_recommend_model() {
|
|
|
786
991
|
case "$stage" in
|
|
787
992
|
plan|design|review|compound_quality)
|
|
788
993
|
if [[ "$model" != "opus" ]]; then
|
|
789
|
-
if [[ "$budget_remaining" == "" ]] || [[ "$(
|
|
994
|
+
if [[ "$budget_remaining" == "" ]] || [[ "$(awk -v b="$budget_remaining" 'BEGIN{print (b+0>=10)?1:0}')" == "1" ]]; then
|
|
790
995
|
model="opus"
|
|
791
996
|
reason="high complexity (${complexity}/10) overrides historical for critical stage (${stage})"
|
|
792
997
|
fi
|
|
@@ -812,7 +1017,7 @@ intelligence_recommend_model() {
|
|
|
812
1017
|
|
|
813
1018
|
# Strategy 2: Heuristic fallback (no historical data available)
|
|
814
1019
|
# Budget-constrained: use haiku
|
|
815
|
-
if [[ "$budget_remaining" != "" ]] && [[ "$(
|
|
1020
|
+
if [[ "$budget_remaining" != "" ]] && [[ "$(awk -v b="$budget_remaining" 'BEGIN{print (b+0<5)?1:0}')" == "1" ]]; then
|
|
816
1021
|
model="haiku"
|
|
817
1022
|
reason="budget constrained (< \$5 remaining)"
|
|
818
1023
|
# High complexity + critical stages: use opus
|
|
@@ -865,13 +1070,46 @@ intelligence_recommend_model() {
|
|
|
865
1070
|
|
|
866
1071
|
# ─── Prediction Validation ─────────────────────────────────────────────────
|
|
867
1072
|
|
|
868
|
-
# intelligence_validate_prediction <issue_id> <
|
|
869
|
-
# Compares predicted
|
|
1073
|
+
# intelligence_validate_prediction [<metric>|<issue_id>] <predicted> <actual> [<actual_success>]
|
|
1074
|
+
# Compares predicted vs actual for feedback learning.
|
|
1075
|
+
# Form 1 (complexity): intelligence_validate_prediction <issue_id> <predicted_complexity> <actual_iterations> <actual_success>
|
|
1076
|
+
# Form 2 (iterations): intelligence_validate_prediction "iterations" <predicted> <actual>
|
|
1077
|
+
# Form 3 (cost): intelligence_validate_prediction "cost" <predicted> <actual>
|
|
870
1078
|
intelligence_validate_prediction() {
|
|
871
|
-
local
|
|
872
|
-
local
|
|
873
|
-
local
|
|
874
|
-
local
|
|
1079
|
+
local arg1="${1:-}"
|
|
1080
|
+
local arg2="${2:-0}"
|
|
1081
|
+
local arg3="${3:-0}"
|
|
1082
|
+
local arg4="${4:-false}"
|
|
1083
|
+
|
|
1084
|
+
# Form 2 & 3: metric-based validation (iterations, cost)
|
|
1085
|
+
if [[ "$arg1" == "iterations" || "$arg1" == "cost" ]]; then
|
|
1086
|
+
local metric="$arg1"
|
|
1087
|
+
local predicted="$arg2"
|
|
1088
|
+
local actual="$arg3"
|
|
1089
|
+
[[ -z "$predicted" || "$predicted" == "null" ]] && return 0
|
|
1090
|
+
local validation_file="${HOME}/.shipwright/optimization/prediction-validation.jsonl"
|
|
1091
|
+
mkdir -p "${HOME}/.shipwright/optimization"
|
|
1092
|
+
local delta
|
|
1093
|
+
delta=$(awk -v p="$predicted" -v a="$actual" 'BEGIN { printf "%.2f", p - a }' 2>/dev/null || echo "0")
|
|
1094
|
+
local record
|
|
1095
|
+
record=$(jq -c -n \
|
|
1096
|
+
--arg ts "$(now_iso)" \
|
|
1097
|
+
--arg issue "${ISSUE_NUMBER:-unknown}" \
|
|
1098
|
+
--arg m "$metric" \
|
|
1099
|
+
--arg pred "$predicted" \
|
|
1100
|
+
--arg act "$actual" \
|
|
1101
|
+
--arg d "$delta" \
|
|
1102
|
+
'{ts: $ts, issue: $issue, metric: $m, predicted: ($pred | tonumber? // 0), actual: ($act | tonumber? // 0), delta: ($d | tonumber? // 0)}')
|
|
1103
|
+
echo "$record" >> "$validation_file"
|
|
1104
|
+
emit_event "intelligence.prediction_validated" "metric=$metric" "predicted=$predicted" "actual=$actual" "delta=$delta"
|
|
1105
|
+
return 0
|
|
1106
|
+
fi
|
|
1107
|
+
|
|
1108
|
+
# Form 1: complexity validation (original)
|
|
1109
|
+
local issue_id="$arg1"
|
|
1110
|
+
local predicted_complexity="$arg2"
|
|
1111
|
+
local actual_iterations="$arg3"
|
|
1112
|
+
local actual_success="$arg4"
|
|
875
1113
|
|
|
876
1114
|
if [[ -z "$issue_id" ]]; then
|
|
877
1115
|
error "Usage: intelligence_validate_prediction <issue_id> <predicted> <actual_iterations> <actual_success>"
|
|
@@ -968,7 +1206,7 @@ intelligence_github_enrich() {
|
|
|
968
1206
|
local analysis_json="$1"
|
|
969
1207
|
|
|
970
1208
|
# Skip if GraphQL not available
|
|
971
|
-
type _gh_detect_repo
|
|
1209
|
+
type _gh_detect_repo >/dev/null 2>&1 || { echo "$analysis_json"; return 0; }
|
|
972
1210
|
_gh_detect_repo 2>/dev/null || { echo "$analysis_json"; return 0; }
|
|
973
1211
|
|
|
974
1212
|
local owner="${GH_OWNER:-}" repo="${GH_REPO:-}"
|
|
@@ -976,13 +1214,13 @@ intelligence_github_enrich() {
|
|
|
976
1214
|
|
|
977
1215
|
# Get repo context
|
|
978
1216
|
local repo_context="{}"
|
|
979
|
-
if type gh_repo_context
|
|
1217
|
+
if type gh_repo_context >/dev/null 2>&1; then
|
|
980
1218
|
repo_context=$(gh_repo_context "$owner" "$repo" 2>/dev/null || echo "{}")
|
|
981
1219
|
fi
|
|
982
1220
|
|
|
983
1221
|
# Get security alerts count
|
|
984
1222
|
local security_count=0
|
|
985
|
-
if type gh_security_alerts
|
|
1223
|
+
if type gh_security_alerts >/dev/null 2>&1; then
|
|
986
1224
|
local alerts
|
|
987
1225
|
alerts=$(gh_security_alerts "$owner" "$repo" 2>/dev/null || echo "[]")
|
|
988
1226
|
security_count=$(echo "$alerts" | jq 'length' 2>/dev/null || echo "0")
|
|
@@ -990,7 +1228,7 @@ intelligence_github_enrich() {
|
|
|
990
1228
|
|
|
991
1229
|
# Get dependabot alerts count
|
|
992
1230
|
local dependabot_count=0
|
|
993
|
-
if type gh_dependabot_alerts
|
|
1231
|
+
if type gh_dependabot_alerts >/dev/null 2>&1; then
|
|
994
1232
|
local deps
|
|
995
1233
|
deps=$(gh_dependabot_alerts "$owner" "$repo" 2>/dev/null || echo "[]")
|
|
996
1234
|
dependabot_count=$(echo "$deps" | jq 'length' 2>/dev/null || echo "0")
|
|
@@ -1011,7 +1249,7 @@ intelligence_file_risk_score() {
|
|
|
1011
1249
|
local file_path="$1"
|
|
1012
1250
|
local risk_score=0
|
|
1013
1251
|
|
|
1014
|
-
type _gh_detect_repo
|
|
1252
|
+
type _gh_detect_repo >/dev/null 2>&1 || { echo "0"; return 0; }
|
|
1015
1253
|
_gh_detect_repo 2>/dev/null || { echo "0"; return 0; }
|
|
1016
1254
|
|
|
1017
1255
|
local owner="${GH_OWNER:-}" repo="${GH_REPO:-}"
|
|
@@ -1019,7 +1257,7 @@ intelligence_file_risk_score() {
|
|
|
1019
1257
|
|
|
1020
1258
|
# Factor 1: File churn (high change frequency = higher risk)
|
|
1021
1259
|
local changes=0
|
|
1022
|
-
if type gh_file_change_frequency
|
|
1260
|
+
if type gh_file_change_frequency >/dev/null 2>&1; then
|
|
1023
1261
|
changes=$(gh_file_change_frequency "$owner" "$repo" "$file_path" 30 2>/dev/null || echo "0")
|
|
1024
1262
|
fi
|
|
1025
1263
|
if [[ "${changes:-0}" -gt 20 ]]; then
|
|
@@ -1031,7 +1269,7 @@ intelligence_file_risk_score() {
|
|
|
1031
1269
|
fi
|
|
1032
1270
|
|
|
1033
1271
|
# Factor 2: Security alerts on this file
|
|
1034
|
-
if type gh_security_alerts
|
|
1272
|
+
if type gh_security_alerts >/dev/null 2>&1; then
|
|
1035
1273
|
local file_alerts
|
|
1036
1274
|
file_alerts=$(gh_security_alerts "$owner" "$repo" 2>/dev/null | \
|
|
1037
1275
|
jq --arg path "$file_path" '[.[] | select(.most_recent_instance.location.path == $path)] | length' 2>/dev/null || echo "0")
|
|
@@ -1039,7 +1277,7 @@ intelligence_file_risk_score() {
|
|
|
1039
1277
|
fi
|
|
1040
1278
|
|
|
1041
1279
|
# Factor 3: Many contributors = higher coordination risk
|
|
1042
|
-
if type gh_blame_data
|
|
1280
|
+
if type gh_blame_data >/dev/null 2>&1; then
|
|
1043
1281
|
local author_count
|
|
1044
1282
|
author_count=$(gh_blame_data "$owner" "$repo" "$file_path" 2>/dev/null | jq 'length' 2>/dev/null || echo "0")
|
|
1045
1283
|
[[ "${author_count:-0}" -gt 5 ]] && risk_score=$((risk_score + 10))
|
|
@@ -1053,13 +1291,13 @@ intelligence_file_risk_score() {
|
|
|
1053
1291
|
intelligence_contributor_expertise() {
|
|
1054
1292
|
local file_path="$1"
|
|
1055
1293
|
|
|
1056
|
-
type _gh_detect_repo
|
|
1294
|
+
type _gh_detect_repo >/dev/null 2>&1 || { echo "[]"; return 0; }
|
|
1057
1295
|
_gh_detect_repo 2>/dev/null || { echo "[]"; return 0; }
|
|
1058
1296
|
|
|
1059
1297
|
local owner="${GH_OWNER:-}" repo="${GH_REPO:-}"
|
|
1060
1298
|
[[ -z "$owner" || -z "$repo" ]] && { echo "[]"; return 0; }
|
|
1061
1299
|
|
|
1062
|
-
if type gh_blame_data
|
|
1300
|
+
if type gh_blame_data >/dev/null 2>&1; then
|
|
1063
1301
|
gh_blame_data "$owner" "$repo" "$file_path" 2>/dev/null || echo "[]"
|
|
1064
1302
|
else
|
|
1065
1303
|
echo "[]"
|
|
@@ -1085,6 +1323,7 @@ show_help() {
|
|
|
1085
1323
|
echo -e " ${CYAN}search-memory${RESET} <context> [dir] Search memory by relevance"
|
|
1086
1324
|
echo -e " ${CYAN}estimate-iterations${RESET} <analysis> Estimate build iterations"
|
|
1087
1325
|
echo -e " ${CYAN}recommend-model${RESET} <stage> [cplx] Recommend model for stage"
|
|
1326
|
+
echo -e " ${CYAN}status${RESET} Show intelligence status and config"
|
|
1088
1327
|
echo -e " ${CYAN}cache-stats${RESET} Show cache statistics"
|
|
1089
1328
|
echo -e " ${CYAN}validate-prediction${RESET} <id> <pred> <iters> <success> Validate prediction accuracy"
|
|
1090
1329
|
echo -e " ${CYAN}cache-clear${RESET} Clear intelligence cache"
|
|
@@ -1097,6 +1336,75 @@ show_help() {
|
|
|
1097
1336
|
echo -e "${DIM}Version ${VERSION}${RESET}"
|
|
1098
1337
|
}
|
|
1099
1338
|
|
|
1339
|
+
cmd_status() {
|
|
1340
|
+
# Find daemon-config (project root, cwd, or shipwright install)
|
|
1341
|
+
local config=""
|
|
1342
|
+
for cfg in "$(git rev-parse --show-toplevel 2>/dev/null)/.claude/daemon-config.json" "$(pwd)/.claude/daemon-config.json" "${REPO_DIR}/.claude/daemon-config.json"; do
|
|
1343
|
+
[[ -n "$cfg" && -f "$cfg" ]] && config="$cfg" && break
|
|
1344
|
+
done
|
|
1345
|
+
local intel_enabled="auto"
|
|
1346
|
+
if [[ -n "$config" && -f "$config" ]]; then
|
|
1347
|
+
intel_enabled=$(jq -r '.intelligence.enabled // "auto"' "$config" 2>/dev/null || echo "auto")
|
|
1348
|
+
fi
|
|
1349
|
+
|
|
1350
|
+
# Resolved: is intelligence actually enabled?
|
|
1351
|
+
local resolved="disabled"
|
|
1352
|
+
if [[ "$intel_enabled" == "true" ]]; then
|
|
1353
|
+
resolved="enabled"
|
|
1354
|
+
elif [[ "$intel_enabled" == "auto" ]]; then
|
|
1355
|
+
if command -v claude &>/dev/null; then
|
|
1356
|
+
resolved="enabled (auto)"
|
|
1357
|
+
else
|
|
1358
|
+
resolved="disabled (auto)"
|
|
1359
|
+
fi
|
|
1360
|
+
fi
|
|
1361
|
+
|
|
1362
|
+
# Cached analyses count (use same .claude as config when found)
|
|
1363
|
+
local cache_file="$INTELLIGENCE_CACHE"
|
|
1364
|
+
local cached_count=0
|
|
1365
|
+
if [[ -n "$config" ]]; then
|
|
1366
|
+
cache_file="$(dirname "$config")/intelligence-cache.json"
|
|
1367
|
+
fi
|
|
1368
|
+
if [[ -f "$cache_file" ]]; then
|
|
1369
|
+
cached_count=$(jq '.entries | length' "$cache_file" 2>/dev/null || echo "0")
|
|
1370
|
+
fi
|
|
1371
|
+
|
|
1372
|
+
# Memory entries count
|
|
1373
|
+
local memory_dir="${HOME}/.shipwright/memory"
|
|
1374
|
+
local memory_count=0
|
|
1375
|
+
if [[ -d "$memory_dir" ]]; then
|
|
1376
|
+
memory_count=$(find "$memory_dir" -name "*.json" -o -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
|
|
1377
|
+
fi
|
|
1378
|
+
|
|
1379
|
+
# Claude authenticated
|
|
1380
|
+
local claude_ok="no"
|
|
1381
|
+
if command -v claude &>/dev/null; then
|
|
1382
|
+
if claude --version &>/dev/null; then
|
|
1383
|
+
claude_ok="yes"
|
|
1384
|
+
fi
|
|
1385
|
+
fi
|
|
1386
|
+
|
|
1387
|
+
# Last intelligence call timestamp
|
|
1388
|
+
local last_ts="never"
|
|
1389
|
+
local events_file="${HOME}/.shipwright/events.jsonl"
|
|
1390
|
+
if [[ -f "$events_file" ]]; then
|
|
1391
|
+
local last_event
|
|
1392
|
+
last_event=$(grep -E '"type":"intelligence\.' "$events_file" 2>/dev/null | tail -1)
|
|
1393
|
+
if [[ -n "$last_event" ]]; then
|
|
1394
|
+
last_ts=$(echo "$last_event" | jq -r '.ts // .ts_epoch // "unknown"' 2>/dev/null || echo "unknown")
|
|
1395
|
+
fi
|
|
1396
|
+
fi
|
|
1397
|
+
|
|
1398
|
+
echo ""
|
|
1399
|
+
echo -e "${BOLD}Intelligence Status${RESET}"
|
|
1400
|
+
echo -e " Config: ${CYAN}${intel_enabled}${RESET} — resolved: ${resolved}"
|
|
1401
|
+
echo -e " Cached analyses: ${CYAN}${cached_count}${RESET}"
|
|
1402
|
+
echo -e " Memory entries: ${CYAN}${memory_count}${RESET}"
|
|
1403
|
+
echo -e " Claude auth: ${claude_ok}"
|
|
1404
|
+
echo -e " Last call: ${DIM}${last_ts}${RESET}"
|
|
1405
|
+
echo ""
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1100
1408
|
cmd_cache_stats() {
|
|
1101
1409
|
_intelligence_cache_init
|
|
1102
1410
|
|
|
@@ -1172,6 +1480,9 @@ main() {
|
|
|
1172
1480
|
validate-prediction)
|
|
1173
1481
|
intelligence_validate_prediction "$@"
|
|
1174
1482
|
;;
|
|
1483
|
+
status)
|
|
1484
|
+
cmd_status
|
|
1485
|
+
;;
|
|
1175
1486
|
cache-stats)
|
|
1176
1487
|
cmd_cache_stats
|
|
1177
1488
|
;;
|