shipwright-cli 2.4.0 → 3.1.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 +16 -11
- package/completions/_shipwright +248 -94
- package/completions/shipwright.bash +68 -19
- package/completions/shipwright.fish +310 -42
- package/config/decision-tiers.json +55 -0
- package/config/defaults.json +111 -0
- package/config/event-schema.json +218 -0
- package/config/policy.json +21 -18
- 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 +7 -9
- 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 +127 -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 +63 -17
- package/scripts/lib/daemon-failure.sh +0 -0
- package/scripts/lib/daemon-health.sh +1 -1
- package/scripts/lib/daemon-patrol.sh +64 -17
- package/scripts/lib/daemon-poll.sh +54 -25
- package/scripts/lib/daemon-state.sh +125 -23
- package/scripts/lib/daemon-triage.sh +31 -9
- package/scripts/lib/decide-autonomy.sh +295 -0
- package/scripts/lib/decide-scoring.sh +228 -0
- package/scripts/lib/decide-signals.sh +462 -0
- package/scripts/lib/fleet-failover.sh +63 -0
- package/scripts/lib/helpers.sh +29 -6
- package/scripts/lib/pipeline-detection.sh +2 -2
- package/scripts/lib/pipeline-github.sh +9 -9
- package/scripts/lib/pipeline-intelligence.sh +105 -38
- package/scripts/lib/pipeline-quality-checks.sh +17 -16
- package/scripts/lib/pipeline-quality.sh +1 -1
- package/scripts/lib/pipeline-stages.sh +440 -59
- package/scripts/lib/pipeline-state.sh +54 -4
- package/scripts/lib/policy.sh +0 -0
- package/scripts/lib/test-helpers.sh +247 -0
- package/scripts/postinstall.mjs +78 -12
- package/scripts/signals/example-collector.sh +36 -0
- package/scripts/sw +17 -7
- 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 +17 -18
- package/scripts/sw-daemon.sh +76 -71
- package/scripts/sw-dashboard.sh +57 -17
- package/scripts/sw-db.sh +524 -26
- package/scripts/sw-decide.sh +685 -0
- 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 +138 -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 +114 -30
- 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 +48 -74
- package/scripts/sw-init.sh +35 -37
- package/scripts/sw-instrument.sh +1 -11
- package/scripts/sw-intelligence.sh +368 -53
- 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 +905 -104
- package/scripts/sw-memory.sh +263 -20
- package/scripts/sw-mission-control.sh +2 -12
- package/scripts/sw-model-router.sh +73 -34
- package/scripts/sw-otel.sh +15 -23
- 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 +550 -122
- package/scripts/sw-pm.sh +2 -12
- package/scripts/sw-pr-lifecycle.sh +33 -28
- 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 +85 -14
- 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 +1 -1
- package/scripts/sw-scale.sh +174 -41
- package/scripts/sw-security-audit.sh +12 -22
- package/scripts/sw-self-optimize.sh +239 -23
- package/scripts/sw-session.sh +5 -15
- 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 +29 -39
- 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 +73 -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.1.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,10 @@ 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 '. + {status: "disabled", error: "intelligence_disabled"}' 2>/dev/null || echo '{"status":"disabled","error":"intelligence_disabled","stages":[]}'
|
|
556
|
+
[[ -z "${_INTEL_DISABLED_LOGGED:-}" ]] && { info "Intelligence disabled — using data-driven fallbacks"; _INTEL_DISABLED_LOGGED=1; }
|
|
370
557
|
return 0
|
|
371
558
|
fi
|
|
372
559
|
|
|
@@ -402,8 +589,8 @@ Return ONLY a JSON object (no markdown):
|
|
|
402
589
|
has_stages=$(echo "$result" | jq 'has("stages") and (.stages | type == "array")' 2>/dev/null || echo "false")
|
|
403
590
|
|
|
404
591
|
if [[ "$has_stages" != "true" ]]; then
|
|
405
|
-
warn "Pipeline composition missing stages array, using fallback"
|
|
406
|
-
result
|
|
592
|
+
warn "Pipeline composition missing stages array, using data-driven fallback"
|
|
593
|
+
result=$(_intelligence_fallback_compose "$issue_analysis")
|
|
407
594
|
fi
|
|
408
595
|
|
|
409
596
|
emit_event "intelligence.compose" \
|
|
@@ -413,7 +600,9 @@ Return ONLY a JSON object (no markdown):
|
|
|
413
600
|
echo "$result"
|
|
414
601
|
return 0
|
|
415
602
|
else
|
|
416
|
-
|
|
603
|
+
local fallback
|
|
604
|
+
fallback=$(_intelligence_fallback_compose "$issue_analysis")
|
|
605
|
+
echo "$fallback" | jq -c '. + {error: "composition_failed"}' 2>/dev/null || echo '{"error":"composition_failed","stages":[]}'
|
|
417
606
|
return 0
|
|
418
607
|
fi
|
|
419
608
|
}
|
|
@@ -425,7 +614,10 @@ intelligence_predict_cost() {
|
|
|
425
614
|
local historical_data="${2:-"{}"}"
|
|
426
615
|
|
|
427
616
|
if ! _intelligence_enabled; then
|
|
428
|
-
|
|
617
|
+
local fallback
|
|
618
|
+
fallback=$(_intelligence_fallback_cost)
|
|
619
|
+
echo "$fallback" | jq -c '. + {status: "disabled", error: "intelligence_disabled", estimated_iterations: 0, likely_failure_stage: "unknown"}' 2>/dev/null || echo '{"status":"disabled","error":"intelligence_disabled","estimated_cost_usd":0,"estimated_iterations":0,"likely_failure_stage":"unknown"}'
|
|
620
|
+
[[ -z "${_INTEL_DISABLED_LOGGED:-}" ]] && { info "Intelligence disabled — using data-driven fallbacks"; _INTEL_DISABLED_LOGGED=1; }
|
|
429
621
|
return 0
|
|
430
622
|
fi
|
|
431
623
|
|
|
@@ -456,8 +648,8 @@ Based on similar complexity (${complexity}/10) issues, estimate:
|
|
|
456
648
|
valid=$(echo "$result" | jq 'has("estimated_cost_usd") and has("estimated_iterations")' 2>/dev/null || echo "false")
|
|
457
649
|
|
|
458
650
|
if [[ "$valid" != "true" ]]; then
|
|
459
|
-
warn "Cost prediction missing required fields, using fallback"
|
|
460
|
-
result
|
|
651
|
+
warn "Cost prediction missing required fields, using data-driven fallback"
|
|
652
|
+
result=$(_intelligence_fallback_cost | jq -c '. + {estimated_iterations: 3, estimated_tokens: 500000, likely_failure_stage: "test", confidence: 30}')
|
|
461
653
|
fi
|
|
462
654
|
|
|
463
655
|
emit_event "intelligence.prediction" \
|
|
@@ -468,7 +660,9 @@ Based on similar complexity (${complexity}/10) issues, estimate:
|
|
|
468
660
|
echo "$result"
|
|
469
661
|
return 0
|
|
470
662
|
else
|
|
471
|
-
|
|
663
|
+
local fallback
|
|
664
|
+
fallback=$(_intelligence_fallback_cost)
|
|
665
|
+
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
666
|
return 0
|
|
473
667
|
fi
|
|
474
668
|
}
|
|
@@ -479,7 +673,8 @@ intelligence_synthesize_findings() {
|
|
|
479
673
|
local findings_json="${1:-"[]"}"
|
|
480
674
|
|
|
481
675
|
if ! _intelligence_enabled; then
|
|
482
|
-
echo '{"error":"intelligence_disabled","priority_fixes":[],"root_causes":[],"recommended_approach":""}'
|
|
676
|
+
echo '{"status":"disabled","error":"intelligence_disabled","priority_fixes":[],"root_causes":[],"recommended_approach":""}'
|
|
677
|
+
[[ -z "${_INTEL_DISABLED_LOGGED:-}" ]] && { info "Intelligence disabled — using data-driven fallbacks"; _INTEL_DISABLED_LOGGED=1; }
|
|
483
678
|
return 0
|
|
484
679
|
fi
|
|
485
680
|
|
|
@@ -528,7 +723,8 @@ intelligence_search_memory() {
|
|
|
528
723
|
local top_n="${3:-5}"
|
|
529
724
|
|
|
530
725
|
if ! _intelligence_enabled; then
|
|
531
|
-
echo '{"error":"intelligence_disabled","results":[]}'
|
|
726
|
+
echo '{"status":"disabled","error":"intelligence_disabled","results":[]}'
|
|
727
|
+
[[ -z "${_INTEL_DISABLED_LOGGED:-}" ]] && { info "Intelligence disabled — using data-driven fallbacks"; _INTEL_DISABLED_LOGGED=1; }
|
|
532
728
|
return 0
|
|
533
729
|
fi
|
|
534
730
|
|
|
@@ -712,11 +908,24 @@ intelligence_recommend_model() {
|
|
|
712
908
|
local model="sonnet"
|
|
713
909
|
local reason="default balanced choice"
|
|
714
910
|
|
|
911
|
+
# Budget-constrained: always use haiku regardless of routing (highest priority)
|
|
912
|
+
if [[ "$budget_remaining" != "" ]] && [[ "$(awk -v b="$budget_remaining" 'BEGIN{print (b+0<5)?1:0}')" == "1" ]]; then
|
|
913
|
+
model="haiku"
|
|
914
|
+
reason="budget constrained (< \$5 remaining)"
|
|
915
|
+
local result
|
|
916
|
+
result=$(jq -n --arg model "$model" --arg reason "$reason" --arg stage "$stage" --argjson complexity "$complexity" \
|
|
917
|
+
'{"model": $model, "reason": $reason, "stage": $stage, "complexity": $complexity, "source": "heuristic"}')
|
|
918
|
+
emit_event "intelligence.model" "stage=$stage" "complexity=$complexity" "model=$model" "source=heuristic"
|
|
919
|
+
echo "$result"
|
|
920
|
+
return 0
|
|
921
|
+
fi
|
|
922
|
+
|
|
715
923
|
# Strategy 1: Check historical model routing data
|
|
716
924
|
local routing_file="${HOME}/.shipwright/optimization/model-routing.json"
|
|
717
925
|
if [[ -f "$routing_file" ]]; then
|
|
926
|
+
# Support both formats: .routes.stage (self-optimize) and .stage (legacy)
|
|
718
927
|
local stage_data
|
|
719
|
-
stage_data=$(jq -r --arg s "$stage" '.[$s] // empty' "$routing_file" 2>/dev/null || true)
|
|
928
|
+
stage_data=$(jq -r --arg s "$stage" '.routes[$s] // .[$s] // empty' "$routing_file" 2>/dev/null || true)
|
|
720
929
|
|
|
721
930
|
if [[ -n "$stage_data" && "$stage_data" != "null" ]]; then
|
|
722
931
|
local recommended sonnet_rate sonnet_samples opus_rate opus_samples
|
|
@@ -769,7 +978,7 @@ intelligence_recommend_model() {
|
|
|
769
978
|
fi
|
|
770
979
|
|
|
771
980
|
if [[ "$use_sonnet" == "true" ]]; then
|
|
772
|
-
if [[ "$budget_remaining" != "" ]] && [[ "$(
|
|
981
|
+
if [[ "$budget_remaining" != "" ]] && [[ "$(awk -v b="$budget_remaining" 'BEGIN{print (b+0<5)?1:0}')" == "1" ]]; then
|
|
773
982
|
model="haiku"
|
|
774
983
|
reason="sonnet viable (${sonnet_rate}% success) but budget constrained"
|
|
775
984
|
else
|
|
@@ -786,7 +995,7 @@ intelligence_recommend_model() {
|
|
|
786
995
|
case "$stage" in
|
|
787
996
|
plan|design|review|compound_quality)
|
|
788
997
|
if [[ "$model" != "opus" ]]; then
|
|
789
|
-
if [[ "$budget_remaining" == "" ]] || [[ "$(
|
|
998
|
+
if [[ "$budget_remaining" == "" ]] || [[ "$(awk -v b="$budget_remaining" 'BEGIN{print (b+0>=10)?1:0}')" == "1" ]]; then
|
|
790
999
|
model="opus"
|
|
791
1000
|
reason="high complexity (${complexity}/10) overrides historical for critical stage (${stage})"
|
|
792
1001
|
fi
|
|
@@ -812,7 +1021,7 @@ intelligence_recommend_model() {
|
|
|
812
1021
|
|
|
813
1022
|
# Strategy 2: Heuristic fallback (no historical data available)
|
|
814
1023
|
# Budget-constrained: use haiku
|
|
815
|
-
if [[ "$budget_remaining" != "" ]] && [[ "$(
|
|
1024
|
+
if [[ "$budget_remaining" != "" ]] && [[ "$(awk -v b="$budget_remaining" 'BEGIN{print (b+0<5)?1:0}')" == "1" ]]; then
|
|
816
1025
|
model="haiku"
|
|
817
1026
|
reason="budget constrained (< \$5 remaining)"
|
|
818
1027
|
# High complexity + critical stages: use opus
|
|
@@ -865,13 +1074,46 @@ intelligence_recommend_model() {
|
|
|
865
1074
|
|
|
866
1075
|
# ─── Prediction Validation ─────────────────────────────────────────────────
|
|
867
1076
|
|
|
868
|
-
# intelligence_validate_prediction <issue_id> <
|
|
869
|
-
# Compares predicted
|
|
1077
|
+
# intelligence_validate_prediction [<metric>|<issue_id>] <predicted> <actual> [<actual_success>]
|
|
1078
|
+
# Compares predicted vs actual for feedback learning.
|
|
1079
|
+
# Form 1 (complexity): intelligence_validate_prediction <issue_id> <predicted_complexity> <actual_iterations> <actual_success>
|
|
1080
|
+
# Form 2 (iterations): intelligence_validate_prediction "iterations" <predicted> <actual>
|
|
1081
|
+
# Form 3 (cost): intelligence_validate_prediction "cost" <predicted> <actual>
|
|
870
1082
|
intelligence_validate_prediction() {
|
|
871
|
-
local
|
|
872
|
-
local
|
|
873
|
-
local
|
|
874
|
-
local
|
|
1083
|
+
local arg1="${1:-}"
|
|
1084
|
+
local arg2="${2:-0}"
|
|
1085
|
+
local arg3="${3:-0}"
|
|
1086
|
+
local arg4="${4:-false}"
|
|
1087
|
+
|
|
1088
|
+
# Form 2 & 3: metric-based validation (iterations, cost)
|
|
1089
|
+
if [[ "$arg1" == "iterations" || "$arg1" == "cost" ]]; then
|
|
1090
|
+
local metric="$arg1"
|
|
1091
|
+
local predicted="$arg2"
|
|
1092
|
+
local actual="$arg3"
|
|
1093
|
+
[[ -z "$predicted" || "$predicted" == "null" ]] && return 0
|
|
1094
|
+
local validation_file="${HOME}/.shipwright/optimization/prediction-validation.jsonl"
|
|
1095
|
+
mkdir -p "${HOME}/.shipwright/optimization"
|
|
1096
|
+
local delta
|
|
1097
|
+
delta=$(awk -v p="$predicted" -v a="$actual" 'BEGIN { printf "%.2f", p - a }' 2>/dev/null || echo "0")
|
|
1098
|
+
local record
|
|
1099
|
+
record=$(jq -c -n \
|
|
1100
|
+
--arg ts "$(now_iso)" \
|
|
1101
|
+
--arg issue "${ISSUE_NUMBER:-unknown}" \
|
|
1102
|
+
--arg m "$metric" \
|
|
1103
|
+
--arg pred "$predicted" \
|
|
1104
|
+
--arg act "$actual" \
|
|
1105
|
+
--arg d "$delta" \
|
|
1106
|
+
'{ts: $ts, issue: $issue, metric: $m, predicted: ($pred | tonumber? // 0), actual: ($act | tonumber? // 0), delta: ($d | tonumber? // 0)}')
|
|
1107
|
+
echo "$record" >> "$validation_file"
|
|
1108
|
+
emit_event "intelligence.prediction_validated" "metric=$metric" "predicted=$predicted" "actual=$actual" "delta=$delta"
|
|
1109
|
+
return 0
|
|
1110
|
+
fi
|
|
1111
|
+
|
|
1112
|
+
# Form 1: complexity validation (original)
|
|
1113
|
+
local issue_id="$arg1"
|
|
1114
|
+
local predicted_complexity="$arg2"
|
|
1115
|
+
local actual_iterations="$arg3"
|
|
1116
|
+
local actual_success="$arg4"
|
|
875
1117
|
|
|
876
1118
|
if [[ -z "$issue_id" ]]; then
|
|
877
1119
|
error "Usage: intelligence_validate_prediction <issue_id> <predicted> <actual_iterations> <actual_success>"
|
|
@@ -968,7 +1210,7 @@ intelligence_github_enrich() {
|
|
|
968
1210
|
local analysis_json="$1"
|
|
969
1211
|
|
|
970
1212
|
# Skip if GraphQL not available
|
|
971
|
-
type _gh_detect_repo
|
|
1213
|
+
type _gh_detect_repo >/dev/null 2>&1 || { echo "$analysis_json"; return 0; }
|
|
972
1214
|
_gh_detect_repo 2>/dev/null || { echo "$analysis_json"; return 0; }
|
|
973
1215
|
|
|
974
1216
|
local owner="${GH_OWNER:-}" repo="${GH_REPO:-}"
|
|
@@ -976,13 +1218,13 @@ intelligence_github_enrich() {
|
|
|
976
1218
|
|
|
977
1219
|
# Get repo context
|
|
978
1220
|
local repo_context="{}"
|
|
979
|
-
if type gh_repo_context
|
|
1221
|
+
if type gh_repo_context >/dev/null 2>&1; then
|
|
980
1222
|
repo_context=$(gh_repo_context "$owner" "$repo" 2>/dev/null || echo "{}")
|
|
981
1223
|
fi
|
|
982
1224
|
|
|
983
1225
|
# Get security alerts count
|
|
984
1226
|
local security_count=0
|
|
985
|
-
if type gh_security_alerts
|
|
1227
|
+
if type gh_security_alerts >/dev/null 2>&1; then
|
|
986
1228
|
local alerts
|
|
987
1229
|
alerts=$(gh_security_alerts "$owner" "$repo" 2>/dev/null || echo "[]")
|
|
988
1230
|
security_count=$(echo "$alerts" | jq 'length' 2>/dev/null || echo "0")
|
|
@@ -990,7 +1232,7 @@ intelligence_github_enrich() {
|
|
|
990
1232
|
|
|
991
1233
|
# Get dependabot alerts count
|
|
992
1234
|
local dependabot_count=0
|
|
993
|
-
if type gh_dependabot_alerts
|
|
1235
|
+
if type gh_dependabot_alerts >/dev/null 2>&1; then
|
|
994
1236
|
local deps
|
|
995
1237
|
deps=$(gh_dependabot_alerts "$owner" "$repo" 2>/dev/null || echo "[]")
|
|
996
1238
|
dependabot_count=$(echo "$deps" | jq 'length' 2>/dev/null || echo "0")
|
|
@@ -1011,7 +1253,7 @@ intelligence_file_risk_score() {
|
|
|
1011
1253
|
local file_path="$1"
|
|
1012
1254
|
local risk_score=0
|
|
1013
1255
|
|
|
1014
|
-
type _gh_detect_repo
|
|
1256
|
+
type _gh_detect_repo >/dev/null 2>&1 || { echo "0"; return 0; }
|
|
1015
1257
|
_gh_detect_repo 2>/dev/null || { echo "0"; return 0; }
|
|
1016
1258
|
|
|
1017
1259
|
local owner="${GH_OWNER:-}" repo="${GH_REPO:-}"
|
|
@@ -1019,7 +1261,7 @@ intelligence_file_risk_score() {
|
|
|
1019
1261
|
|
|
1020
1262
|
# Factor 1: File churn (high change frequency = higher risk)
|
|
1021
1263
|
local changes=0
|
|
1022
|
-
if type gh_file_change_frequency
|
|
1264
|
+
if type gh_file_change_frequency >/dev/null 2>&1; then
|
|
1023
1265
|
changes=$(gh_file_change_frequency "$owner" "$repo" "$file_path" 30 2>/dev/null || echo "0")
|
|
1024
1266
|
fi
|
|
1025
1267
|
if [[ "${changes:-0}" -gt 20 ]]; then
|
|
@@ -1031,7 +1273,7 @@ intelligence_file_risk_score() {
|
|
|
1031
1273
|
fi
|
|
1032
1274
|
|
|
1033
1275
|
# Factor 2: Security alerts on this file
|
|
1034
|
-
if type gh_security_alerts
|
|
1276
|
+
if type gh_security_alerts >/dev/null 2>&1; then
|
|
1035
1277
|
local file_alerts
|
|
1036
1278
|
file_alerts=$(gh_security_alerts "$owner" "$repo" 2>/dev/null | \
|
|
1037
1279
|
jq --arg path "$file_path" '[.[] | select(.most_recent_instance.location.path == $path)] | length' 2>/dev/null || echo "0")
|
|
@@ -1039,7 +1281,7 @@ intelligence_file_risk_score() {
|
|
|
1039
1281
|
fi
|
|
1040
1282
|
|
|
1041
1283
|
# Factor 3: Many contributors = higher coordination risk
|
|
1042
|
-
if type gh_blame_data
|
|
1284
|
+
if type gh_blame_data >/dev/null 2>&1; then
|
|
1043
1285
|
local author_count
|
|
1044
1286
|
author_count=$(gh_blame_data "$owner" "$repo" "$file_path" 2>/dev/null | jq 'length' 2>/dev/null || echo "0")
|
|
1045
1287
|
[[ "${author_count:-0}" -gt 5 ]] && risk_score=$((risk_score + 10))
|
|
@@ -1053,13 +1295,13 @@ intelligence_file_risk_score() {
|
|
|
1053
1295
|
intelligence_contributor_expertise() {
|
|
1054
1296
|
local file_path="$1"
|
|
1055
1297
|
|
|
1056
|
-
type _gh_detect_repo
|
|
1298
|
+
type _gh_detect_repo >/dev/null 2>&1 || { echo "[]"; return 0; }
|
|
1057
1299
|
_gh_detect_repo 2>/dev/null || { echo "[]"; return 0; }
|
|
1058
1300
|
|
|
1059
1301
|
local owner="${GH_OWNER:-}" repo="${GH_REPO:-}"
|
|
1060
1302
|
[[ -z "$owner" || -z "$repo" ]] && { echo "[]"; return 0; }
|
|
1061
1303
|
|
|
1062
|
-
if type gh_blame_data
|
|
1304
|
+
if type gh_blame_data >/dev/null 2>&1; then
|
|
1063
1305
|
gh_blame_data "$owner" "$repo" "$file_path" 2>/dev/null || echo "[]"
|
|
1064
1306
|
else
|
|
1065
1307
|
echo "[]"
|
|
@@ -1085,6 +1327,7 @@ show_help() {
|
|
|
1085
1327
|
echo -e " ${CYAN}search-memory${RESET} <context> [dir] Search memory by relevance"
|
|
1086
1328
|
echo -e " ${CYAN}estimate-iterations${RESET} <analysis> Estimate build iterations"
|
|
1087
1329
|
echo -e " ${CYAN}recommend-model${RESET} <stage> [cplx] Recommend model for stage"
|
|
1330
|
+
echo -e " ${CYAN}status${RESET} Show intelligence status and config"
|
|
1088
1331
|
echo -e " ${CYAN}cache-stats${RESET} Show cache statistics"
|
|
1089
1332
|
echo -e " ${CYAN}validate-prediction${RESET} <id> <pred> <iters> <success> Validate prediction accuracy"
|
|
1090
1333
|
echo -e " ${CYAN}cache-clear${RESET} Clear intelligence cache"
|
|
@@ -1097,6 +1340,75 @@ show_help() {
|
|
|
1097
1340
|
echo -e "${DIM}Version ${VERSION}${RESET}"
|
|
1098
1341
|
}
|
|
1099
1342
|
|
|
1343
|
+
cmd_status() {
|
|
1344
|
+
# Find daemon-config (project root, cwd, or shipwright install)
|
|
1345
|
+
local config=""
|
|
1346
|
+
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
|
|
1347
|
+
[[ -n "$cfg" && -f "$cfg" ]] && config="$cfg" && break
|
|
1348
|
+
done
|
|
1349
|
+
local intel_enabled="auto"
|
|
1350
|
+
if [[ -n "$config" && -f "$config" ]]; then
|
|
1351
|
+
intel_enabled=$(jq -r '.intelligence.enabled // "auto"' "$config" 2>/dev/null || echo "auto")
|
|
1352
|
+
fi
|
|
1353
|
+
|
|
1354
|
+
# Resolved: is intelligence actually enabled?
|
|
1355
|
+
local resolved="disabled"
|
|
1356
|
+
if [[ "$intel_enabled" == "true" ]]; then
|
|
1357
|
+
resolved="enabled"
|
|
1358
|
+
elif [[ "$intel_enabled" == "auto" ]]; then
|
|
1359
|
+
if command -v claude &>/dev/null; then
|
|
1360
|
+
resolved="enabled (auto)"
|
|
1361
|
+
else
|
|
1362
|
+
resolved="disabled (auto)"
|
|
1363
|
+
fi
|
|
1364
|
+
fi
|
|
1365
|
+
|
|
1366
|
+
# Cached analyses count (use same .claude as config when found)
|
|
1367
|
+
local cache_file="$INTELLIGENCE_CACHE"
|
|
1368
|
+
local cached_count=0
|
|
1369
|
+
if [[ -n "$config" ]]; then
|
|
1370
|
+
cache_file="$(dirname "$config")/intelligence-cache.json"
|
|
1371
|
+
fi
|
|
1372
|
+
if [[ -f "$cache_file" ]]; then
|
|
1373
|
+
cached_count=$(jq '.entries | length' "$cache_file" 2>/dev/null || echo "0")
|
|
1374
|
+
fi
|
|
1375
|
+
|
|
1376
|
+
# Memory entries count
|
|
1377
|
+
local memory_dir="${HOME}/.shipwright/memory"
|
|
1378
|
+
local memory_count=0
|
|
1379
|
+
if [[ -d "$memory_dir" ]]; then
|
|
1380
|
+
memory_count=$(find "$memory_dir" -name "*.json" -o -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
|
|
1381
|
+
fi
|
|
1382
|
+
|
|
1383
|
+
# Claude authenticated
|
|
1384
|
+
local claude_ok="no"
|
|
1385
|
+
if command -v claude &>/dev/null; then
|
|
1386
|
+
if claude --version &>/dev/null; then
|
|
1387
|
+
claude_ok="yes"
|
|
1388
|
+
fi
|
|
1389
|
+
fi
|
|
1390
|
+
|
|
1391
|
+
# Last intelligence call timestamp
|
|
1392
|
+
local last_ts="never"
|
|
1393
|
+
local events_file="${HOME}/.shipwright/events.jsonl"
|
|
1394
|
+
if [[ -f "$events_file" ]]; then
|
|
1395
|
+
local last_event
|
|
1396
|
+
last_event=$(grep -E '"type":"intelligence\.' "$events_file" 2>/dev/null | tail -1)
|
|
1397
|
+
if [[ -n "$last_event" ]]; then
|
|
1398
|
+
last_ts=$(echo "$last_event" | jq -r '.ts // .ts_epoch // "unknown"' 2>/dev/null || echo "unknown")
|
|
1399
|
+
fi
|
|
1400
|
+
fi
|
|
1401
|
+
|
|
1402
|
+
echo ""
|
|
1403
|
+
echo -e "${BOLD}Intelligence Status${RESET}"
|
|
1404
|
+
echo -e " Config: ${CYAN}${intel_enabled}${RESET} — resolved: ${resolved}"
|
|
1405
|
+
echo -e " Cached analyses: ${CYAN}${cached_count}${RESET}"
|
|
1406
|
+
echo -e " Memory entries: ${CYAN}${memory_count}${RESET}"
|
|
1407
|
+
echo -e " Claude auth: ${claude_ok}"
|
|
1408
|
+
echo -e " Last call: ${DIM}${last_ts}${RESET}"
|
|
1409
|
+
echo ""
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1100
1412
|
cmd_cache_stats() {
|
|
1101
1413
|
_intelligence_cache_init
|
|
1102
1414
|
|
|
@@ -1172,6 +1484,9 @@ main() {
|
|
|
1172
1484
|
validate-prediction)
|
|
1173
1485
|
intelligence_validate_prediction "$@"
|
|
1174
1486
|
;;
|
|
1487
|
+
status)
|
|
1488
|
+
cmd_status
|
|
1489
|
+
;;
|
|
1175
1490
|
cache-stats)
|
|
1176
1491
|
cmd_cache_stats
|
|
1177
1492
|
;;
|