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="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -21,24 +21,6 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
21
21
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
22
22
  [[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
23
23
  [[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
24
- if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
25
- emit_event() {
26
- local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
27
- local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
28
- while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
29
- echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
30
- }
31
- fi
32
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
33
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
34
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
35
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
36
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
37
- RED="${RED:-\033[38;2;248;113;113m}"
38
- DIM="${DIM:-\033[2m}"
39
- BOLD="${BOLD:-\033[1m}"
40
- RESET="${RESET:-\033[0m}"
41
-
42
24
  # ─── Configuration ───────────────────────────────────────────────────────
43
25
  REVIEW_CONFIG="${REPO_DIR}/.claude/code-review.json"
44
26
  QUALITY_METRICS_FILE="${REPO_DIR}/.claude/pipeline-artifacts/quality-metrics.json"
@@ -335,17 +317,16 @@ auto_fix() {
335
317
  local backup="${target_file}.review-backup"
336
318
  cp "$target_file" "$backup"
337
319
 
338
- # Fix 1: Run shellcheck and capture warnings
339
- if command -v shellcheck &>/dev/null; then
340
- local shellcheck_fixes=0
320
+ # Report shellcheck issues (informational auto-fix is limited to whitespace)
321
+ if command -v shellcheck >/dev/null 2>&1; then
341
322
  local warnings_file
342
323
  warnings_file=$(mktemp)
343
324
  shellcheck -f json "$target_file" > "$warnings_file" 2>/dev/null || true
344
325
 
345
326
  if [[ -s "$warnings_file" ]]; then
346
- shellcheck_fixes=$(jq 'length' "$warnings_file" 2>/dev/null || echo "0")
347
- info "shellcheck found $shellcheck_fixes issues in $target_file"
348
- fixed=$((fixed + shellcheck_fixes))
327
+ local shellcheck_count
328
+ shellcheck_count=$(jq 'length' "$warnings_file" 2>/dev/null || echo "0")
329
+ [[ "$shellcheck_count" -gt 0 ]] && info "shellcheck found $shellcheck_count issues in $target_file (manual review recommended)"
349
330
  fi
350
331
  rm -f "$warnings_file"
351
332
  fi
@@ -381,7 +362,7 @@ run_claude_semantic_review() {
381
362
  local diff_content="$1"
382
363
  local requirements="${2:-}"
383
364
  [[ -z "$diff_content" ]] && return 0
384
- if ! command -v claude &>/dev/null; then
365
+ if ! command -v claude >/dev/null 2>&1; then
385
366
  return 0
386
367
  fi
387
368
 
@@ -437,13 +418,12 @@ review_changes() {
437
418
  local review_output="{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"scope\":\"$review_scope\",\"findings\":{}}"
438
419
  local total_issues=0
439
420
 
440
- # Get changed files
421
+ # Get changed files (Bash 3.2 compatible — no mapfile)
441
422
  local changed_files=()
442
423
  if [[ "$review_scope" == "staged" ]]; then
443
- mapfile -t changed_files < <(cd "$REPO_DIR" && git diff --cached --name-only 2>/dev/null || true)
424
+ while IFS= read -r _f; do [[ -n "$_f" ]] && changed_files+=("$_f"); done < <(cd "$REPO_DIR" && git diff --cached --name-only 2>/dev/null || true)
444
425
  else
445
- # For PR: get diff against main
446
- mapfile -t changed_files < <(cd "$REPO_DIR" && git diff main...HEAD --name-only 2>/dev/null || true)
426
+ while IFS= read -r _f; do [[ -n "$_f" ]] && changed_files+=("$_f"); done < <(cd "$REPO_DIR" && git diff main...HEAD --name-only 2>/dev/null || true)
447
427
  fi
448
428
 
449
429
  [[ ${#changed_files[@]} -eq 0 ]] && { success "No changes to review"; return 0; }
@@ -456,9 +436,9 @@ review_changes() {
456
436
  diff_content=$(cd "$REPO_DIR" && git diff main...HEAD 2>/dev/null || true)
457
437
  fi
458
438
  local semantic_issues=()
459
- if [[ -n "$diff_content" ]] && command -v claude &>/dev/null; then
439
+ if [[ -n "$diff_content" ]] && command -v claude >/dev/null 2>&1; then
460
440
  info "Running Claude semantic review (logic, race conditions, API usage)..."
461
- mapfile -t semantic_issues < <(run_claude_semantic_review "$diff_content" "${REVIEW_REQUIREMENTS:-}" || true)
441
+ while IFS= read -r _si; do [[ -n "$_si" ]] && semantic_issues+=("$_si"); done < <(run_claude_semantic_review "$diff_content" "${REVIEW_REQUIREMENTS:-}" || true)
462
442
  if [[ ${#semantic_issues[@]} -gt 0 ]]; then
463
443
  total_issues=$((total_issues + ${#semantic_issues[@]}))
464
444
  review_output=$(echo "$review_output" | jq --argjson arr "$(printf '%s\n' "${semantic_issues[@]}" | jq -R . | jq -s .)" '.semantic_findings = $arr' 2>/dev/null || echo "$review_output")
@@ -476,10 +456,10 @@ review_changes() {
476
456
  local arch_issues=()
477
457
  local style_issues=()
478
458
 
479
- mapfile -t smells < <(detect_code_smells "$file_path")
480
- mapfile -t solids < <(check_solid_principles "$file_path")
481
- mapfile -t arch_issues < <(check_architecture_boundaries "$file_path")
482
- mapfile -t style_issues < <(check_style_consistency "$file_path")
459
+ while IFS= read -r _s; do [[ -n "$_s" ]] && smells+=("$_s"); done < <(detect_code_smells "$file_path")
460
+ while IFS= read -r _s; do [[ -n "$_s" ]] && solids+=("$_s"); done < <(check_solid_principles "$file_path")
461
+ while IFS= read -r _s; do [[ -n "$_s" ]] && arch_issues+=("$_s"); done < <(check_architecture_boundaries "$file_path")
462
+ while IFS= read -r _s; do [[ -n "$_s" ]] && style_issues+=("$_s"); done < <(check_style_consistency "$file_path")
483
463
 
484
464
  local file_issues=$((${#smells[@]} + ${#solids[@]} + ${#arch_issues[@]} + ${#style_issues[@]}))
485
465
  total_issues=$((total_issues + file_issues))
@@ -8,7 +8,7 @@
8
8
  set -euo pipefail
9
9
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
 
11
- VERSION="2.4.0"
11
+ VERSION="3.0.0"
12
12
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
13
 
14
14
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -18,8 +18,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
18
  # Canonical helpers (colors, output, events)
19
19
  # shellcheck source=lib/helpers.sh
20
20
  [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
21
+ [[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
21
22
  # Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
22
23
  [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
24
+ # Color fallbacks when helpers not loaded
25
+ : "${CYAN:=}" "${BOLD:=}" "${RESET:=}" "${DIM:=}" "${GREEN:=}" "${RED:=}" "${YELLOW:=}" "${PURPLE:=}" "${WHITE:=}" "${BLUE:=}"
23
26
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
24
27
  [[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
25
28
  [[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
@@ -35,16 +38,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
35
38
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
36
39
  }
37
40
  fi
38
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
39
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
40
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
41
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
42
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
43
- RED="${RED:-\033[38;2;248;113;113m}"
44
- DIM="${DIM:-\033[2m}"
45
- BOLD="${BOLD:-\033[1m}"
46
- RESET="${RESET:-\033[0m}"
47
-
48
41
  # ─── Constants ──────────────────────────────────────────────────────────────
49
42
  SHIPWRIGHT_DIR="$HOME/.shipwright"
50
43
  PID_FILE="$SHIPWRIGHT_DIR/connect.pid"
@@ -53,7 +46,7 @@ DAEMON_PID_FILE="$SHIPWRIGHT_DIR/daemon.pid"
53
46
  DAEMON_STATE_FILE="$SHIPWRIGHT_DIR/daemon-state.json"
54
47
  EVENTS_FILE="$SHIPWRIGHT_DIR/events.jsonl"
55
48
  CONNECT_LOG="$SHIPWRIGHT_DIR/connect.log"
56
- DEFAULT_URL="http://localhost:8767"
49
+ DEFAULT_URL="http://localhost:$(_config_get_int "dashboard.port" 8767)"
57
50
  HEARTBEAT_INTERVAL=10
58
51
 
59
52
  ensure_dir() {
@@ -6,9 +6,9 @@
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
- REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
11
+ REPO_DIR="${SHIPWRIGHT_REPO_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
12
12
 
13
13
  # ─── Cross-platform compatibility ──────────────────────────────────────────
14
14
  # shellcheck source=lib/compat.sh
@@ -26,24 +26,6 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
26
26
  now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
27
27
  now_epoch() { date +%s; }
28
28
  fi
29
- if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
30
- emit_event() {
31
- local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
32
- local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
33
- while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
34
- echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
35
- }
36
- 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}"
46
-
47
29
  # ─── Paths ────────────────────────────────────────────────────────────────
48
30
  ARTIFACTS_DIR="${REPO_DIR}/.claude/pipeline-artifacts"
49
31
  CONTEXT_BUNDLE="${ARTIFACTS_DIR}/context-bundle.md"
@@ -124,7 +106,7 @@ extract_recent_prs() {
124
106
  return
125
107
  fi
126
108
 
127
- if ! command -v gh &>/dev/null; then
109
+ if ! command -v gh >/dev/null 2>&1; then
128
110
  echo "(gh CLI not available, using git log)"
129
111
  echo ""
130
112
  local recent_commits
@@ -405,6 +387,7 @@ gather_context() {
405
387
 
406
388
  local tmp_file
407
389
  tmp_file=$(mktemp "${TMPDIR:-/tmp}/sw-context-bundle.XXXXXX")
390
+ trap "rm -f '$tmp_file'" RETURN
408
391
 
409
392
  # Write bundle header
410
393
  {
@@ -484,9 +467,9 @@ gather_context() {
484
467
  # ─── Show current bundle ───────────────────────────────────────────────────
485
468
  show_context() {
486
469
  if [[ ! -f "$CONTEXT_BUNDLE" ]]; then
487
- warn "No context bundle found at ${CONTEXT_BUNDLE}"
488
- echo "Run '${CYAN}shipwright context gather --goal \"...\" --stage plan${RESET}' first"
489
- return 1
470
+ echo "Pipeline Context — No bundle generated yet"
471
+ echo "Run '${CYAN}shipwright context gather --goal \"...\" --stage plan${RESET}' to create one"
472
+ return 0
490
473
  fi
491
474
 
492
475
  cat "$CONTEXT_BUNDLE"
@@ -498,7 +481,7 @@ clear_context() {
498
481
  rm -f "$CONTEXT_BUNDLE"
499
482
  success "Context bundle cleared"
500
483
  else
501
- warn "No context bundle to clear"
484
+ info "No context bundle to clear — already cleared"
502
485
  fi
503
486
  }
504
487
 
@@ -584,7 +567,7 @@ main() {
584
567
 
585
568
  # If issue provided, fetch from GitHub
586
569
  if [[ -n "$issue" ]]; then
587
- if [[ "${NO_GITHUB:-}" == "true" ]] || ! command -v gh &>/dev/null; then
570
+ if [[ "${NO_GITHUB:-}" == "true" ]] || ! command -v gh >/dev/null 2>&1; then
588
571
  goal="GitHub issue #$issue (fetch unavailable)"
589
572
  else
590
573
  goal=$(gh issue view "$issue" --json title,body --template '{{.title}}: {{.body}}' 2>/dev/null || echo "GitHub issue #$issue")
@@ -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="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -34,16 +34,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
34
34
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
35
35
  }
36
36
  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}"
46
-
47
37
  format_duration() {
48
38
  local secs="$1"
49
39
  if [[ "$secs" -ge 3600 ]]; then
@@ -153,13 +143,13 @@ cost_record() {
153
143
  cost_usd=$(cost_calculate "$input_tokens" "$output_tokens" "$model")
154
144
 
155
145
  # Try SQLite first
156
- if type db_record_cost &>/dev/null; then
146
+ if type db_record_cost >/dev/null 2>&1; then
157
147
  db_record_cost "$input_tokens" "$output_tokens" "$model" "$stage" "$cost_usd" "$issue" 2>/dev/null || true
158
148
  fi
159
149
 
160
150
  # Always write to JSON (dual-write period)
161
151
  (
162
- if command -v flock &>/dev/null; then
152
+ if command -v flock >/dev/null 2>&1; then
163
153
  flock -w 10 200 2>/dev/null || { warn "Cost lock timeout"; }
164
154
  fi
165
155
  local tmp_file
@@ -202,7 +192,7 @@ cost_check_budget() {
202
192
 
203
193
  # Try DB for budget info
204
194
  local budget_enabled budget_usd
205
- if type db_get_budget &>/dev/null && type db_available &>/dev/null && db_available 2>/dev/null; then
195
+ if type db_get_budget >/dev/null 2>&1 && type db_available >/dev/null 2>&1 && db_available 2>/dev/null; then
206
196
  local db_budget
207
197
  db_budget=$(db_get_budget 2>/dev/null || true)
208
198
  if [[ -n "$db_budget" ]]; then
@@ -259,7 +249,7 @@ cost_remaining_budget() {
259
249
  ensure_cost_dir
260
250
 
261
251
  # Try DB for remaining budget (single query)
262
- if type db_remaining_budget &>/dev/null && type db_available &>/dev/null && db_available 2>/dev/null; then
252
+ if type db_remaining_budget >/dev/null 2>&1 && type db_available >/dev/null 2>&1 && db_available 2>/dev/null; then
263
253
  local db_result
264
254
  db_result=$(db_remaining_budget 2>/dev/null || true)
265
255
  if [[ -n "$db_result" ]]; then
@@ -818,7 +808,7 @@ budget_set() {
818
808
  ensure_cost_dir
819
809
 
820
810
  # Write to DB if available
821
- if type db_set_budget &>/dev/null; then
811
+ if type db_set_budget >/dev/null 2>&1; then
822
812
  db_set_budget "$amount" 2>/dev/null || true
823
813
  fi
824
814
 
@@ -9,7 +9,7 @@ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
9
9
  # Allow spawning Claude CLI from within a Claude Code session (daemon, fleet, etc.)
10
10
  unset CLAUDECODE 2>/dev/null || true
11
11
 
12
- VERSION="2.4.0"
12
+ VERSION="3.0.0"
13
13
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
14
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
15
15
 
@@ -20,6 +20,7 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
20
20
  # Canonical helpers (colors, output, events)
21
21
  # shellcheck source=lib/helpers.sh
22
22
  [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
23
+ [[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
23
24
  # Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
24
25
  [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
25
26
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
@@ -29,23 +30,6 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
29
30
  now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
30
31
  now_epoch() { date +%s; }
31
32
  fi
32
- if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
33
- emit_event() {
34
- local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
35
- local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
36
- while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
37
- echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
38
- }
39
- fi
40
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
41
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
42
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
43
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
44
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
45
- RED="${RED:-\033[38;2;248;113;113m}"
46
- DIM="${DIM:-\033[2m}"
47
- BOLD="${BOLD:-\033[1m}"
48
- RESET="${RESET:-\033[0m}"
49
33
 
50
34
  # Policy (config/policy.json) — daemon defaults when daemon-config.json missing or silent
51
35
  [[ -f "$SCRIPT_DIR/lib/policy.sh" ]] && source "$SCRIPT_DIR/lib/policy.sh"
@@ -109,28 +93,6 @@ format_duration() {
109
93
  fi
110
94
  }
111
95
 
112
- # ─── Structured Event Log ──────────────────────────────────────────────────
113
- EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
114
-
115
- emit_event() {
116
- local event_type="$1"
117
- shift
118
- local json_fields=""
119
- for kv in "$@"; do
120
- local key="${kv%%=*}"
121
- local val="${kv#*=}"
122
- if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
123
- json_fields="${json_fields},\"${key}\":${val}"
124
- else
125
- local escaped_val
126
- escaped_val=$(printf '%s' "$val" | jq -Rs '.' 2>/dev/null || printf '"%s"' "${val//\"/\\\"}")
127
- json_fields="${json_fields},\"${key}\":${escaped_val}"
128
- fi
129
- done
130
- mkdir -p "${HOME}/.shipwright"
131
- echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
132
- }
133
-
134
96
  # ─── Event Log Rotation ─────────────────────────────────────────────────────
135
97
  rotate_event_log() {
136
98
  local max_size=$((50 * 1024 * 1024)) # 50MB
@@ -167,8 +129,8 @@ rotate_event_log() {
167
129
  daemon_github_context() {
168
130
  # Skip if no GitHub
169
131
  [[ "${NO_GITHUB:-false}" == "true" ]] && return 0
170
- type gh_repo_context &>/dev/null 2>&1 || return 0
171
- type _gh_detect_repo &>/dev/null 2>&1 || return 0
132
+ type gh_repo_context >/dev/null 2>&1 || return 0
133
+ type _gh_detect_repo >/dev/null 2>&1 || return 0
172
134
 
173
135
  _gh_detect_repo 2>/dev/null || return 0
174
136
  local owner="${GH_OWNER:-}" repo="${GH_REPO:-}"
@@ -192,8 +154,8 @@ gh_retry() {
192
154
 
193
155
  while [[ $attempt -lt $max_retries ]]; do
194
156
  attempt=$((attempt + 1))
195
- # Run the gh command; capture exit code
196
- if output=$("$@" 2>&1); then
157
+ # Run the gh command with per-call timeout; capture exit code
158
+ if output=$(_timeout "$(_config_get_int "network.gh_timeout" 30 2>/dev/null || echo 30)" "$@" 2>&1); then
197
159
  echo "$output"
198
160
  return 0
199
161
  fi
@@ -227,17 +189,18 @@ LOG_DIR=""
227
189
  WORKTREE_DIR=""
228
190
 
229
191
  # Config defaults (overridden by daemon-config.json; policy overrides when present)
230
- WATCH_LABEL="ready-to-build"
231
- POLL_INTERVAL=60
232
- if type policy_get &>/dev/null 2>&1; then
192
+ WATCH_LABEL="$(_config_get "labels.watch" "shipwright" 2>/dev/null || echo "shipwright")"
193
+ POLL_INTERVAL=$(_config_get_int "daemon.poll_interval" 60 2>/dev/null || echo 60)
194
+ if type policy_get >/dev/null 2>&1; then
233
195
  POLL_INTERVAL=$(policy_get ".daemon.poll_interval_seconds" "60")
234
196
  fi
235
- MAX_PARALLEL=2
197
+ MAX_PARALLEL=$(_config_get_int "daemon.max_parallel" 4 2>/dev/null || echo 4)
198
+ ISSUE_LIMIT=$(_config_get_int "daemon.issue_limit" 100 2>/dev/null || echo 100)
236
199
  PIPELINE_TEMPLATE="autonomous"
237
200
  SKIP_GATES=true
238
201
  MODEL="opus"
239
202
  BASE_BRANCH="main"
240
- ON_SUCCESS_REMOVE_LABEL="ready-to-build"
203
+ ON_SUCCESS_REMOVE_LABEL="shipwright"
241
204
  ON_SUCCESS_ADD_LABEL="pipeline/complete"
242
205
  ON_SUCCESS_CLOSE_ISSUE=false
243
206
  ON_FAILURE_ADD_LABEL="pipeline/failed"
@@ -257,7 +220,7 @@ REPO_FILTER=""
257
220
  # Auto-scaling defaults (policy overrides when present)
258
221
  AUTO_SCALE=false
259
222
  AUTO_SCALE_INTERVAL=5
260
- if type policy_get &>/dev/null 2>&1; then
223
+ if type policy_get >/dev/null 2>&1; then
261
224
  AUTO_SCALE_INTERVAL=$(policy_get ".daemon.auto_scale_interval_cycles" "5")
262
225
  fi
263
226
  MAX_WORKERS=8
@@ -280,7 +243,7 @@ PATROL_RETRY_THRESHOLD=2
280
243
  LAST_PATROL_EPOCH=0
281
244
 
282
245
  # Team dashboard coordination
283
- DASHBOARD_URL="${DASHBOARD_URL:-http://localhost:8767}"
246
+ DASHBOARD_URL="${DASHBOARD_URL:-$(_config_get "dashboard.url" "http://localhost:8767" 2>/dev/null || echo "http://localhost:8767")}"
284
247
 
285
248
  # Runtime
286
249
  NO_GITHUB=false
@@ -370,7 +333,7 @@ show_help() {
370
333
  echo -e " ${DIM}shipwright daemon patrol --once${RESET} # Run patrol once and exit"
371
334
  echo ""
372
335
  echo -e "${BOLD}CONFIG FILE${RESET} ${DIM}(.claude/daemon-config.json)${RESET}"
373
- echo -e " ${DIM}watch_label${RESET} GitHub label to watch for ${DIM}(default: ready-to-build)${RESET}"
336
+ echo -e " ${DIM}watch_label${RESET} GitHub label to watch for ${DIM}(default: shipwright)${RESET}"
374
337
  echo -e " ${DIM}poll_interval${RESET} Seconds between polls ${DIM}(default: 60)${RESET}"
375
338
  echo -e " ${DIM}max_parallel${RESET} Max concurrent pipeline jobs ${DIM}(default: 2)${RESET}"
376
339
  echo -e " ${DIM}pipeline_template${RESET} Pipeline template to use ${DIM}(default: autonomous)${RESET}"
@@ -411,16 +374,17 @@ load_config() {
411
374
 
412
375
  info "Loading config: ${DIM}${config_file}${RESET}"
413
376
 
414
- WATCH_LABEL=$(jq -r '.watch_label // "ready-to-build"' "$config_file")
415
- POLL_INTERVAL=$(jq -r '.poll_interval // '"$(type policy_get &>/dev/null 2>&1 && policy_get ".daemon.poll_interval_seconds" "60" || echo "60")"'' "$config_file")
377
+ WATCH_LABEL=$(jq -r '.watch_label // "shipwright"' "$config_file")
378
+ POLL_INTERVAL=$(jq -r '.poll_interval // '"$(type policy_get >/dev/null 2>&1 && policy_get ".daemon.poll_interval_seconds" "60" || echo "60")"'' "$config_file")
416
379
  MAX_PARALLEL=$(jq -r '.max_parallel // 2' "$config_file")
380
+ ISSUE_LIMIT=$(jq -r '.issue_limit // 100' "$config_file")
417
381
  PIPELINE_TEMPLATE=$(jq -r '.pipeline_template // "autonomous"' "$config_file")
418
382
  SKIP_GATES=$(jq -r '.skip_gates // true' "$config_file")
419
383
  MODEL=$(jq -r '.model // "opus"' "$config_file")
420
384
  BASE_BRANCH=$(jq -r '.base_branch // "main"' "$config_file")
421
385
 
422
386
  # on_success settings
423
- ON_SUCCESS_REMOVE_LABEL=$(jq -r '.on_success.remove_label // "ready-to-build"' "$config_file")
387
+ ON_SUCCESS_REMOVE_LABEL=$(jq -r '.on_success.remove_label // "shipwright"' "$config_file")
424
388
  ON_SUCCESS_ADD_LABEL=$(jq -r '.on_success.add_label // "pipeline/complete"' "$config_file")
425
389
  ON_SUCCESS_CLOSE_ISSUE=$(jq -r '.on_success.close_issue // false' "$config_file")
426
390
 
@@ -472,12 +436,41 @@ load_config() {
472
436
 
473
437
  # self-optimization
474
438
  SELF_OPTIMIZE=$(jq -r '.self_optimize // false' "$config_file")
475
- OPTIMIZE_INTERVAL=$(jq -r '.optimize_interval // '"$(type policy_get &>/dev/null 2>&1 && policy_get ".daemon.optimize_interval_cycles" "10" || echo "10")"'' "$config_file")
439
+ OPTIMIZE_INTERVAL=$(jq -r '.optimize_interval // '"$(type policy_get >/dev/null 2>&1 && policy_get ".daemon.optimize_interval_cycles" "10" || echo "10")"'' "$config_file")
476
440
 
477
- # intelligence engine settings
478
- INTELLIGENCE_ENABLED=$(jq -r '.intelligence.enabled // false' "$config_file")
441
+ # intelligence engine settings (default "auto" = enable when Claude CLI available)
442
+ INTELLIGENCE_ENABLED=$(jq -r '.intelligence.enabled // "auto"' "$config_file")
479
443
  INTELLIGENCE_CACHE_TTL=$(jq -r '.intelligence.cache_ttl_seconds // 3600' "$config_file")
480
- COMPOSER_ENABLED=$(jq -r '.intelligence.composer_enabled // false' "$config_file")
444
+ COMPOSER_ENABLED=$(jq -r '.intelligence.composer_enabled // "auto"' "$config_file")
445
+
446
+ # Auto-enable intelligence when Claude is available (unless explicitly disabled)
447
+ if [[ "${INTELLIGENCE_ENABLED}" == "false" && "${INTELLIGENCE_EXPLICIT_DISABLE:-false}" != "true" ]]; then
448
+ if command -v claude &>/dev/null; then
449
+ INTELLIGENCE_ENABLED=true
450
+ type daemon_log &>/dev/null && daemon_log INFO "Intelligence auto-enabled (Claude CLI detected). Disable with intelligence.enabled=false in daemon-config.json"
451
+ fi
452
+ elif [[ "${INTELLIGENCE_ENABLED}" == "auto" ]]; then
453
+ if command -v claude &>/dev/null; then
454
+ INTELLIGENCE_ENABLED=true
455
+ type daemon_log &>/dev/null && daemon_log INFO "Intelligence enabled (auto: Claude CLI detected)"
456
+ else
457
+ INTELLIGENCE_ENABLED=false
458
+ fi
459
+ fi
460
+
461
+ # Auto-enable composer when Claude is available (unless explicitly disabled)
462
+ if [[ "${COMPOSER_ENABLED}" == "false" && "${COMPOSER_EXPLICIT_DISABLE:-false}" != "true" ]]; then
463
+ if command -v claude &>/dev/null; then
464
+ COMPOSER_ENABLED=true
465
+ type daemon_log &>/dev/null && daemon_log INFO "Composer auto-enabled (Claude CLI detected). Disable with intelligence.composer_enabled=false in daemon-config.json"
466
+ fi
467
+ elif [[ "${COMPOSER_ENABLED}" == "auto" ]]; then
468
+ if command -v claude &>/dev/null; then
469
+ COMPOSER_ENABLED=true
470
+ else
471
+ COMPOSER_ENABLED=false
472
+ fi
473
+ fi
481
474
  OPTIMIZATION_ENABLED=$(jq -r '.intelligence.optimization_enabled // false' "$config_file")
482
475
  PREDICTION_ENABLED=$(jq -r '.intelligence.prediction_enabled // false' "$config_file")
483
476
  ANOMALY_THRESHOLD=$(jq -r '.intelligence.anomaly_threshold // 3.0' "$config_file")
@@ -491,7 +484,7 @@ load_config() {
491
484
 
492
485
  # stale state reaper: clean old worktrees, artifacts, state entries
493
486
  STALE_REAPER_ENABLED=$(jq -r '.stale_reaper // true' "$config_file")
494
- STALE_REAPER_INTERVAL=$(jq -r '.stale_reaper_interval // '"$(type policy_get &>/dev/null 2>&1 && policy_get ".daemon.stale_reaper_interval_cycles" "10" || echo "10")"'' "$config_file")
487
+ STALE_REAPER_INTERVAL=$(jq -r '.stale_reaper_interval // '"$(type policy_get >/dev/null 2>&1 && policy_get ".daemon.stale_reaper_interval_cycles" "10" || echo "10")"'' "$config_file")
495
488
  STALE_REAPER_AGE_DAYS=$(jq -r '.stale_reaper_age_days // 7' "$config_file")
496
489
 
497
490
  # priority lane settings
@@ -508,14 +501,14 @@ load_config() {
508
501
 
509
502
  # auto-scaling
510
503
  AUTO_SCALE=$(jq -r '.auto_scale // false' "$config_file")
511
- AUTO_SCALE_INTERVAL=$(jq -r '.auto_scale_interval // '"$(type policy_get &>/dev/null 2>&1 && policy_get ".daemon.auto_scale_interval_cycles" "5" || echo "5")"'' "$config_file")
504
+ AUTO_SCALE_INTERVAL=$(jq -r '.auto_scale_interval // '"$(type policy_get >/dev/null 2>&1 && policy_get ".daemon.auto_scale_interval_cycles" "5" || echo "5")"'' "$config_file")
512
505
  MAX_WORKERS=$(jq -r '.max_workers // 8' "$config_file")
513
506
  MIN_WORKERS=$(jq -r '.min_workers // 1' "$config_file")
514
507
  WORKER_MEM_GB=$(jq -r '.worker_mem_gb // 4' "$config_file")
515
508
  EST_COST_PER_JOB=$(jq -r '.estimated_cost_per_job_usd // 5.0' "$config_file")
516
509
 
517
510
  # heartbeat + checkpoint recovery (policy fallback when config silent)
518
- HEALTH_HEARTBEAT_TIMEOUT=$(jq -r '.health.heartbeat_timeout_s // '"$(type policy_get &>/dev/null 2>&1 && policy_get ".daemon.health_heartbeat_timeout" "120" || echo "120")"'' "$config_file")
511
+ HEALTH_HEARTBEAT_TIMEOUT=$(jq -r '.health.heartbeat_timeout_s // '"$(type policy_get >/dev/null 2>&1 && policy_get ".daemon.health_heartbeat_timeout" "120" || echo "120")"'' "$config_file")
519
512
  CHECKPOINT_ENABLED=$(jq -r '.health.checkpoint_enabled // true' "$config_file")
520
513
 
521
514
  # progress-based health monitoring (replaces static timeouts)
@@ -603,6 +596,18 @@ cleanup_on_exit() {
603
596
  done <<< "$child_pids"
604
597
  fi
605
598
  fi
599
+
600
+ # Release claims on active issues before exit
601
+ local active_issues
602
+ active_issues=$(jq -r '.active_jobs[].issue // empty' "$STATE_FILE" 2>/dev/null || true)
603
+ if [[ -n "$active_issues" ]]; then
604
+ local machine_name
605
+ machine_name=$(jq -r '.machines[] | select(.role == "primary") | .name' "$HOME/.shipwright/machines.json" 2>/dev/null || hostname -s)
606
+ for issue_num in $active_issues; do
607
+ [[ -z "$issue_num" ]] && continue
608
+ release_claim "$issue_num" "$machine_name" 2>/dev/null || true
609
+ done
610
+ fi
606
611
  fi
607
612
 
608
613
  rm -f "$PID_FILE" "$SHUTDOWN_FLAG"
@@ -647,7 +652,7 @@ daemon_start() {
647
652
 
648
653
  # Detach mode: re-exec in a tmux session
649
654
  if [[ "$DETACH" == "true" ]]; then
650
- if ! command -v tmux &>/dev/null; then
655
+ if ! command -v tmux >/dev/null 2>&1; then
651
656
  error "tmux required for --detach mode"
652
657
  exit 1
653
658
  fi
@@ -689,7 +694,7 @@ daemon_start() {
689
694
  rm -f "$SHUTDOWN_FLAG"
690
695
 
691
696
  # Initialize SQLite database (if available)
692
- if type init_schema &>/dev/null; then
697
+ if type init_schema >/dev/null 2>&1; then
693
698
  init_schema 2>/dev/null || true
694
699
  fi
695
700
 
@@ -761,7 +766,7 @@ daemon_start() {
761
766
  if [[ -f "$STATE_FILE" ]]; then
762
767
  if ! jq '.' "$STATE_FILE" >/dev/null 2>&1; then
763
768
  daemon_log WARN "Watchdog: state file corrupt — recovering from backup"
764
- type validate_json &>/dev/null 2>&1 && validate_json "$STATE_FILE" || true
769
+ type validate_json >/dev/null 2>&1 && validate_json "$STATE_FILE" || true
765
770
  fi
766
771
  fi
767
772
  done
@@ -931,7 +936,7 @@ daemon_init() {
931
936
 
932
937
  cat > "$config_file" << 'CONFIGEOF'
933
938
  {
934
- "watch_label": "ready-to-build",
939
+ "watch_label": "shipwright",
935
940
  "poll_interval": 60,
936
941
  "max_parallel": 2,
937
942
  "pipeline_template": "autonomous",
@@ -939,7 +944,7 @@ daemon_init() {
939
944
  "model": "opus",
940
945
  "base_branch": "main",
941
946
  "on_success": {
942
- "remove_label": "ready-to-build",
947
+ "remove_label": "shipwright",
943
948
  "add_label": "pipeline/complete",
944
949
  "close_issue": false
945
950
  },
@@ -993,9 +998,9 @@ daemon_init() {
993
998
  "worker_mem_gb": 4,
994
999
  "estimated_cost_per_job_usd": 5.0,
995
1000
  "intelligence": {
996
- "enabled": true,
1001
+ "enabled": "auto",
997
1002
  "cache_ttl_seconds": 3600,
998
- "composer_enabled": true,
1003
+ "composer_enabled": "auto",
999
1004
  "optimization_enabled": true,
1000
1005
  "prediction_enabled": true,
1001
1006
  "adversarial_enabled": false,
@@ -1052,7 +1057,7 @@ daemon_metrics() {
1052
1057
  exit 1
1053
1058
  fi
1054
1059
 
1055
- if ! command -v jq &>/dev/null; then
1060
+ if ! command -v jq >/dev/null 2>&1; then
1056
1061
  error "jq is required for metrics. Install: brew install jq"
1057
1062
  exit 1
1058
1063
  fi