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