shipwright-cli 2.4.0 → 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.
Files changed (161) hide show
  1. package/README.md +16 -11
  2. package/completions/_shipwright +1 -1
  3. package/completions/shipwright.bash +3 -8
  4. package/completions/shipwright.fish +1 -1
  5. package/config/defaults.json +111 -0
  6. package/config/event-schema.json +81 -0
  7. package/config/policy.json +13 -18
  8. package/dashboard/coverage/coverage-summary.json +14 -0
  9. package/dashboard/public/index.html +1 -1
  10. package/dashboard/server.ts +306 -17
  11. package/dashboard/src/components/charts/bar.test.ts +79 -0
  12. package/dashboard/src/components/charts/donut.test.ts +68 -0
  13. package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
  14. package/dashboard/src/components/charts/sparkline.test.ts +125 -0
  15. package/dashboard/src/core/api.test.ts +309 -0
  16. package/dashboard/src/core/helpers.test.ts +301 -0
  17. package/dashboard/src/core/router.test.ts +307 -0
  18. package/dashboard/src/core/router.ts +7 -0
  19. package/dashboard/src/core/sse.test.ts +144 -0
  20. package/dashboard/src/views/metrics.test.ts +186 -0
  21. package/dashboard/src/views/overview.test.ts +173 -0
  22. package/dashboard/src/views/pipelines.test.ts +183 -0
  23. package/dashboard/src/views/team.test.ts +253 -0
  24. package/dashboard/vitest.config.ts +14 -5
  25. package/docs/TIPS.md +1 -1
  26. package/docs/patterns/README.md +1 -1
  27. package/package.json +5 -7
  28. package/scripts/adapters/docker-deploy.sh +1 -1
  29. package/scripts/adapters/tmux-adapter.sh +11 -1
  30. package/scripts/adapters/wezterm-adapter.sh +1 -1
  31. package/scripts/check-version-consistency.sh +1 -1
  32. package/scripts/lib/architecture.sh +126 -0
  33. package/scripts/lib/bootstrap.sh +75 -0
  34. package/scripts/lib/compat.sh +89 -6
  35. package/scripts/lib/config.sh +91 -0
  36. package/scripts/lib/daemon-adaptive.sh +3 -3
  37. package/scripts/lib/daemon-dispatch.sh +39 -16
  38. package/scripts/lib/daemon-health.sh +1 -1
  39. package/scripts/lib/daemon-patrol.sh +24 -12
  40. package/scripts/lib/daemon-poll.sh +37 -25
  41. package/scripts/lib/daemon-state.sh +115 -23
  42. package/scripts/lib/daemon-triage.sh +30 -8
  43. package/scripts/lib/fleet-failover.sh +63 -0
  44. package/scripts/lib/helpers.sh +30 -6
  45. package/scripts/lib/pipeline-detection.sh +2 -2
  46. package/scripts/lib/pipeline-github.sh +9 -9
  47. package/scripts/lib/pipeline-intelligence.sh +85 -35
  48. package/scripts/lib/pipeline-quality-checks.sh +16 -16
  49. package/scripts/lib/pipeline-quality.sh +1 -1
  50. package/scripts/lib/pipeline-stages.sh +242 -28
  51. package/scripts/lib/pipeline-state.sh +40 -4
  52. package/scripts/lib/test-helpers.sh +247 -0
  53. package/scripts/postinstall.mjs +3 -11
  54. package/scripts/sw +10 -4
  55. package/scripts/sw-activity.sh +1 -11
  56. package/scripts/sw-adaptive.sh +109 -85
  57. package/scripts/sw-adversarial.sh +4 -14
  58. package/scripts/sw-architecture-enforcer.sh +1 -11
  59. package/scripts/sw-auth.sh +8 -17
  60. package/scripts/sw-autonomous.sh +111 -49
  61. package/scripts/sw-changelog.sh +1 -11
  62. package/scripts/sw-checkpoint.sh +144 -20
  63. package/scripts/sw-ci.sh +2 -12
  64. package/scripts/sw-cleanup.sh +13 -17
  65. package/scripts/sw-code-review.sh +16 -36
  66. package/scripts/sw-connect.sh +5 -12
  67. package/scripts/sw-context.sh +9 -26
  68. package/scripts/sw-cost.sh +6 -16
  69. package/scripts/sw-daemon.sh +75 -70
  70. package/scripts/sw-dashboard.sh +57 -17
  71. package/scripts/sw-db.sh +506 -15
  72. package/scripts/sw-decompose.sh +1 -11
  73. package/scripts/sw-deps.sh +15 -25
  74. package/scripts/sw-developer-simulation.sh +1 -11
  75. package/scripts/sw-discovery.sh +112 -30
  76. package/scripts/sw-doc-fleet.sh +7 -17
  77. package/scripts/sw-docs-agent.sh +6 -16
  78. package/scripts/sw-docs.sh +4 -12
  79. package/scripts/sw-doctor.sh +134 -43
  80. package/scripts/sw-dora.sh +11 -19
  81. package/scripts/sw-durable.sh +35 -52
  82. package/scripts/sw-e2e-orchestrator.sh +11 -27
  83. package/scripts/sw-eventbus.sh +115 -115
  84. package/scripts/sw-evidence.sh +114 -30
  85. package/scripts/sw-feedback.sh +3 -13
  86. package/scripts/sw-fix.sh +2 -20
  87. package/scripts/sw-fleet-discover.sh +1 -11
  88. package/scripts/sw-fleet-viz.sh +10 -18
  89. package/scripts/sw-fleet.sh +13 -17
  90. package/scripts/sw-github-app.sh +6 -16
  91. package/scripts/sw-github-checks.sh +1 -11
  92. package/scripts/sw-github-deploy.sh +1 -11
  93. package/scripts/sw-github-graphql.sh +2 -12
  94. package/scripts/sw-guild.sh +1 -11
  95. package/scripts/sw-heartbeat.sh +49 -12
  96. package/scripts/sw-hygiene.sh +45 -43
  97. package/scripts/sw-incident.sh +48 -74
  98. package/scripts/sw-init.sh +35 -37
  99. package/scripts/sw-instrument.sh +1 -11
  100. package/scripts/sw-intelligence.sh +362 -51
  101. package/scripts/sw-jira.sh +5 -14
  102. package/scripts/sw-launchd.sh +2 -12
  103. package/scripts/sw-linear.sh +8 -17
  104. package/scripts/sw-logs.sh +4 -12
  105. package/scripts/sw-loop.sh +641 -90
  106. package/scripts/sw-memory.sh +243 -17
  107. package/scripts/sw-mission-control.sh +2 -12
  108. package/scripts/sw-model-router.sh +73 -34
  109. package/scripts/sw-otel.sh +11 -21
  110. package/scripts/sw-oversight.sh +1 -11
  111. package/scripts/sw-patrol-meta.sh +5 -11
  112. package/scripts/sw-pipeline-composer.sh +7 -17
  113. package/scripts/sw-pipeline-vitals.sh +1 -11
  114. package/scripts/sw-pipeline.sh +478 -122
  115. package/scripts/sw-pm.sh +2 -12
  116. package/scripts/sw-pr-lifecycle.sh +27 -25
  117. package/scripts/sw-predictive.sh +16 -22
  118. package/scripts/sw-prep.sh +6 -16
  119. package/scripts/sw-ps.sh +1 -11
  120. package/scripts/sw-public-dashboard.sh +2 -12
  121. package/scripts/sw-quality.sh +77 -10
  122. package/scripts/sw-reaper.sh +1 -11
  123. package/scripts/sw-recruit.sh +15 -25
  124. package/scripts/sw-regression.sh +11 -21
  125. package/scripts/sw-release-manager.sh +19 -28
  126. package/scripts/sw-release.sh +8 -16
  127. package/scripts/sw-remote.sh +1 -11
  128. package/scripts/sw-replay.sh +48 -44
  129. package/scripts/sw-retro.sh +70 -92
  130. package/scripts/sw-review-rerun.sh +1 -1
  131. package/scripts/sw-scale.sh +109 -32
  132. package/scripts/sw-security-audit.sh +12 -22
  133. package/scripts/sw-self-optimize.sh +239 -23
  134. package/scripts/sw-session.sh +3 -13
  135. package/scripts/sw-setup.sh +8 -18
  136. package/scripts/sw-standup.sh +5 -15
  137. package/scripts/sw-status.sh +32 -23
  138. package/scripts/sw-strategic.sh +129 -13
  139. package/scripts/sw-stream.sh +1 -11
  140. package/scripts/sw-swarm.sh +76 -36
  141. package/scripts/sw-team-stages.sh +10 -20
  142. package/scripts/sw-templates.sh +4 -14
  143. package/scripts/sw-testgen.sh +3 -13
  144. package/scripts/sw-tmux-pipeline.sh +1 -19
  145. package/scripts/sw-tmux-role-color.sh +0 -10
  146. package/scripts/sw-tmux-status.sh +3 -11
  147. package/scripts/sw-tmux.sh +2 -20
  148. package/scripts/sw-trace.sh +1 -19
  149. package/scripts/sw-tracker-github.sh +0 -10
  150. package/scripts/sw-tracker-jira.sh +1 -11
  151. package/scripts/sw-tracker-linear.sh +1 -11
  152. package/scripts/sw-tracker.sh +7 -24
  153. package/scripts/sw-triage.sh +24 -34
  154. package/scripts/sw-upgrade.sh +5 -23
  155. package/scripts/sw-ux.sh +1 -19
  156. package/scripts/sw-webhook.sh +18 -32
  157. package/scripts/sw-widgets.sh +3 -21
  158. package/scripts/sw-worktree.sh +11 -27
  159. package/scripts/update-homebrew-sha.sh +67 -0
  160. package/templates/pipelines/tdd.json +72 -0
  161. 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="2.4.0"
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
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
38
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
39
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
40
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
41
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
42
- RED="${RED:-\033[38;2;248;113;113m}"
43
- DIM="${DIM:-\033[2m}"
44
- BOLD="${BOLD:-\033[1m}"
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=3600 # 1 hour (fallback)
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 // false' "$config" 2>/dev/null || echo "false")
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 &>/dev/null; then _timeout_cmd="gtimeout 60"
253
- elif command -v timeout &>/dev/null; then _timeout_cmd="timeout 60"
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='{"complexity":5,"risk_level":"medium","success_probability":50,"recommended_template":"standard","key_risks":["analysis_incomplete"],"implementation_hints":[]}'
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
- echo '{"error":"analysis_failed","complexity":5,"risk_level":"medium","success_probability":50,"recommended_template":"standard","key_risks":["analysis_failed"],"implementation_hints":[]}'
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
- echo '{"error":"intelligence_disabled","stages":[]}'
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='{"stages":[{"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":{}}],"rationale":"fallback pipeline"}'
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
- echo '{"error":"composition_failed","stages":[]}'
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
- echo '{"error":"intelligence_disabled","estimated_cost_usd":0,"estimated_iterations":0,"likely_failure_stage":"unknown"}'
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='{"estimated_cost_usd":5.0,"estimated_iterations":3,"estimated_tokens":500000,"likely_failure_stage":"test","confidence":30}'
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
- echo '{"error":"prediction_failed","estimated_cost_usd":0,"estimated_iterations":0,"likely_failure_stage":"unknown"}'
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" != "" ]] && [[ "$(echo "$budget_remaining < 5" | bc 2>/dev/null || echo "0")" == "1" ]]; then
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" == "" ]] || [[ "$(echo "$budget_remaining >= 10" | bc 2>/dev/null || echo "1")" == "1" ]]; then
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" != "" ]] && [[ "$(echo "$budget_remaining < 5" | bc 2>/dev/null || echo "0")" == "1" ]]; then
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> <predicted_complexity> <actual_iterations> <actual_success>
869
- # Compares predicted complexity to actual outcome for feedback learning.
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 issue_id="${1:-}"
872
- local predicted_complexity="${2:-0}"
873
- local actual_iterations="${3:-0}"
874
- local actual_success="${4:-false}"
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 &>/dev/null 2>&1 || { echo "$analysis_json"; return 0; }
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 &>/dev/null 2>&1; then
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 &>/dev/null 2>&1; then
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 &>/dev/null 2>&1; then
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 &>/dev/null 2>&1 || { echo "0"; return 0; }
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 &>/dev/null 2>&1; then
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 &>/dev/null 2>&1; then
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 &>/dev/null 2>&1; then
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 &>/dev/null 2>&1 || { echo "[]"; return 0; }
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 &>/dev/null 2>&1; then
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
  ;;