shipwright-cli 2.0.0 → 2.1.1

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 (112) hide show
  1. package/README.md +160 -72
  2. package/completions/_shipwright +59 -7
  3. package/completions/shipwright.bash +24 -4
  4. package/completions/shipwright.fish +80 -2
  5. package/dashboard/server.ts +208 -0
  6. package/docs/tmux-research/TMUX-ARCHITECTURE.md +567 -0
  7. package/docs/tmux-research/TMUX-AUDIT.md +925 -0
  8. package/docs/tmux-research/TMUX-BEST-PRACTICES-2025-2026.md +829 -0
  9. package/docs/tmux-research/TMUX-QUICK-REFERENCE.md +543 -0
  10. package/docs/tmux-research/TMUX-RESEARCH-INDEX.md +438 -0
  11. package/package.json +2 -2
  12. package/scripts/lib/helpers.sh +7 -0
  13. package/scripts/sw +116 -2
  14. package/scripts/sw-activity.sh +1 -1
  15. package/scripts/sw-adaptive.sh +1 -1
  16. package/scripts/sw-adversarial.sh +1 -1
  17. package/scripts/sw-architecture-enforcer.sh +1 -1
  18. package/scripts/sw-auth.sh +1 -1
  19. package/scripts/sw-autonomous.sh +128 -38
  20. package/scripts/sw-changelog.sh +1 -1
  21. package/scripts/sw-checkpoint.sh +1 -1
  22. package/scripts/sw-ci.sh +1 -1
  23. package/scripts/sw-cleanup.sh +1 -1
  24. package/scripts/sw-code-review.sh +62 -1
  25. package/scripts/sw-connect.sh +1 -1
  26. package/scripts/sw-context.sh +1 -1
  27. package/scripts/sw-cost.sh +44 -3
  28. package/scripts/sw-daemon.sh +155 -27
  29. package/scripts/sw-dashboard.sh +1 -1
  30. package/scripts/sw-db.sh +958 -118
  31. package/scripts/sw-decompose.sh +1 -1
  32. package/scripts/sw-deps.sh +1 -1
  33. package/scripts/sw-developer-simulation.sh +1 -1
  34. package/scripts/sw-discovery.sh +1 -1
  35. package/scripts/sw-docs-agent.sh +1 -1
  36. package/scripts/sw-docs.sh +1 -1
  37. package/scripts/sw-doctor.sh +49 -1
  38. package/scripts/sw-dora.sh +1 -1
  39. package/scripts/sw-durable.sh +1 -1
  40. package/scripts/sw-e2e-orchestrator.sh +1 -1
  41. package/scripts/sw-eventbus.sh +1 -1
  42. package/scripts/sw-feedback.sh +23 -15
  43. package/scripts/sw-fix.sh +1 -1
  44. package/scripts/sw-fleet-discover.sh +1 -1
  45. package/scripts/sw-fleet-viz.sh +1 -1
  46. package/scripts/sw-fleet.sh +1 -1
  47. package/scripts/sw-github-app.sh +1 -1
  48. package/scripts/sw-github-checks.sh +4 -4
  49. package/scripts/sw-github-deploy.sh +1 -1
  50. package/scripts/sw-github-graphql.sh +1 -1
  51. package/scripts/sw-guild.sh +1 -1
  52. package/scripts/sw-heartbeat.sh +1 -1
  53. package/scripts/sw-hygiene.sh +1 -1
  54. package/scripts/sw-incident.sh +45 -6
  55. package/scripts/sw-init.sh +150 -24
  56. package/scripts/sw-instrument.sh +1 -1
  57. package/scripts/sw-intelligence.sh +1 -1
  58. package/scripts/sw-jira.sh +1 -1
  59. package/scripts/sw-launchd.sh +1 -1
  60. package/scripts/sw-linear.sh +1 -1
  61. package/scripts/sw-logs.sh +1 -1
  62. package/scripts/sw-loop.sh +204 -19
  63. package/scripts/sw-memory.sh +18 -1
  64. package/scripts/sw-mission-control.sh +1 -1
  65. package/scripts/sw-model-router.sh +1 -1
  66. package/scripts/sw-otel.sh +1 -1
  67. package/scripts/sw-oversight.sh +76 -1
  68. package/scripts/sw-pipeline-composer.sh +1 -1
  69. package/scripts/sw-pipeline-vitals.sh +1 -1
  70. package/scripts/sw-pipeline.sh +302 -18
  71. package/scripts/sw-pm.sh +70 -5
  72. package/scripts/sw-pr-lifecycle.sh +1 -1
  73. package/scripts/sw-predictive.sh +8 -1
  74. package/scripts/sw-prep.sh +1 -1
  75. package/scripts/sw-ps.sh +1 -1
  76. package/scripts/sw-public-dashboard.sh +1 -1
  77. package/scripts/sw-quality.sh +1 -1
  78. package/scripts/sw-reaper.sh +1 -1
  79. package/scripts/sw-recruit.sh +1853 -178
  80. package/scripts/sw-regression.sh +1 -1
  81. package/scripts/sw-release-manager.sh +1 -1
  82. package/scripts/sw-release.sh +1 -1
  83. package/scripts/sw-remote.sh +1 -1
  84. package/scripts/sw-replay.sh +1 -1
  85. package/scripts/sw-retro.sh +1 -1
  86. package/scripts/sw-scale.sh +1 -1
  87. package/scripts/sw-security-audit.sh +1 -1
  88. package/scripts/sw-self-optimize.sh +1 -1
  89. package/scripts/sw-session.sh +1 -1
  90. package/scripts/sw-setup.sh +263 -127
  91. package/scripts/sw-standup.sh +1 -1
  92. package/scripts/sw-status.sh +44 -2
  93. package/scripts/sw-strategic.sh +189 -41
  94. package/scripts/sw-stream.sh +1 -1
  95. package/scripts/sw-swarm.sh +42 -5
  96. package/scripts/sw-team-stages.sh +1 -1
  97. package/scripts/sw-templates.sh +4 -4
  98. package/scripts/sw-testgen.sh +66 -15
  99. package/scripts/sw-tmux-pipeline.sh +1 -1
  100. package/scripts/sw-tmux-role-color.sh +58 -0
  101. package/scripts/sw-tmux-status.sh +128 -0
  102. package/scripts/sw-tmux.sh +1 -1
  103. package/scripts/sw-trace.sh +1 -1
  104. package/scripts/sw-tracker.sh +1 -1
  105. package/scripts/sw-triage.sh +61 -37
  106. package/scripts/sw-upgrade.sh +1 -1
  107. package/scripts/sw-ux.sh +30 -2
  108. package/scripts/sw-webhook.sh +1 -1
  109. package/scripts/sw-widgets.sh +1 -1
  110. package/scripts/sw-worktree.sh +1 -1
  111. package/tmux/shipwright-overlay.conf +35 -17
  112. package/tmux/tmux.conf +26 -21
@@ -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.0.0"
12
+ VERSION="2.1.1"
13
13
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
14
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
15
15
 
@@ -40,6 +40,10 @@ RESET='\033[0m'
40
40
  # shellcheck source=sw-pipeline-vitals.sh
41
41
  [[ -f "$SCRIPT_DIR/sw-pipeline-vitals.sh" ]] && source "$SCRIPT_DIR/sw-pipeline-vitals.sh"
42
42
 
43
+ # ─── SQLite Persistence (optional) ──────────────────────────────────────────
44
+ # shellcheck source=sw-db.sh
45
+ [[ -f "$SCRIPT_DIR/sw-db.sh" ]] && source "$SCRIPT_DIR/sw-db.sh"
46
+
43
47
  # ─── GitHub API Modules (optional) ────────────────────────────────────────
44
48
  # shellcheck source=sw-github-graphql.sh
45
49
  [[ -f "$SCRIPT_DIR/sw-github-graphql.sh" ]] && source "$SCRIPT_DIR/sw-github-graphql.sh"
@@ -507,11 +511,13 @@ load_config() {
507
511
 
508
512
  setup_dirs() {
509
513
  mkdir -p "$DAEMON_DIR"
514
+ mkdir -p "$HOME/.shipwright"
510
515
 
511
516
  STATE_FILE="$DAEMON_DIR/daemon-state.json"
512
517
  LOG_FILE="$DAEMON_DIR/daemon.log"
513
518
  LOG_DIR="$DAEMON_DIR/logs"
514
519
  WORKTREE_DIR=".worktrees"
520
+ PAUSE_FLAG="${HOME}/.shipwright/daemon-pause.flag"
515
521
 
516
522
  mkdir -p "$LOG_DIR"
517
523
  mkdir -p "$HOME/.shipwright/progress"
@@ -1467,6 +1473,7 @@ init_state() {
1467
1473
  queued: [],
1468
1474
  completed: [],
1469
1475
  retry_counts: {},
1476
+ failure_history: [],
1470
1477
  priority_lane_active: [],
1471
1478
  titles: {}
1472
1479
  }')
@@ -1887,6 +1894,14 @@ _Progress updates will appear below as the pipeline advances through each stage.
1887
1894
 
1888
1895
  daemon_track_job() {
1889
1896
  local issue_num="$1" pid="$2" worktree="$3" title="${4:-}" repo="${5:-}" goal="${6:-}"
1897
+
1898
+ # Write to SQLite (non-blocking, best-effort)
1899
+ if type db_save_job &>/dev/null; then
1900
+ local job_id="daemon-${issue_num}-$(now_epoch)"
1901
+ db_save_job "$job_id" "$issue_num" "$title" "$pid" "$worktree" "" "${PIPELINE_TEMPLATE:-autonomous}" "$goal" 2>/dev/null || true
1902
+ fi
1903
+
1904
+ # Always write to JSON state file (primary for now)
1890
1905
  locked_state_update \
1891
1906
  --argjson num "$issue_num" \
1892
1907
  --argjson pid "$pid" \
@@ -1975,6 +1990,16 @@ daemon_reap_completed() {
1975
1990
  [[ "$start_epoch" -gt 0 ]] && dur_s=$((end_epoch - start_epoch))
1976
1991
  emit_event "daemon.reap" "issue=$issue_num" "result=$result_str" "duration_s=$dur_s"
1977
1992
 
1993
+ # Update SQLite (mark job complete/failed)
1994
+ if type db_complete_job &>/dev/null && type db_fail_job &>/dev/null; then
1995
+ local _db_job_id="daemon-${issue_num}-${start_epoch}"
1996
+ if [[ "$exit_code" -eq 0 ]]; then
1997
+ db_complete_job "$_db_job_id" "$result_str" 2>/dev/null || true
1998
+ else
1999
+ db_fail_job "$_db_job_id" "$result_str" 2>/dev/null || true
2000
+ fi
2001
+ fi
2002
+
1978
2003
  if [[ "$exit_code" -eq 0 ]]; then
1979
2004
  daemon_on_success "$issue_num" "$duration_str"
1980
2005
  else
@@ -2135,6 +2160,11 @@ Check the associated PR for the implementation." 2>/dev/null || true
2135
2160
  notify "Pipeline Complete — Issue #${issue_num}" \
2136
2161
  "Duration: ${duration:-unknown}" "success"
2137
2162
  "$SCRIPT_DIR/sw-tracker.sh" notify "completed" "$issue_num" 2>/dev/null || true
2163
+
2164
+ # PM agent: record success for learning
2165
+ if [[ -x "$SCRIPT_DIR/sw-pm.sh" ]]; then
2166
+ bash "$SCRIPT_DIR/sw-pm.sh" learn "$issue_num" success 2>/dev/null || true
2167
+ fi
2138
2168
  }
2139
2169
 
2140
2170
  # ─── Failure Classification ─────────────────────────────────────────────────
@@ -2190,13 +2220,27 @@ classify_failure() {
2190
2220
  echo "unknown"
2191
2221
  }
2192
2222
 
2193
- # ─── Consecutive Failure Tracking ──────────────────────────────────────────
2223
+ # ─── Consecutive Failure Tracking (persisted + adaptive) ─────────────────────
2194
2224
 
2195
2225
  DAEMON_CONSECUTIVE_FAILURE_CLASS=""
2196
2226
  DAEMON_CONSECUTIVE_FAILURE_COUNT=0
2197
2227
 
2228
+ # Max retries per failure class (adaptive retry strategy)
2229
+ get_max_retries_for_class() {
2230
+ local class="${1:-unknown}"
2231
+ case "$class" in
2232
+ auth_error|invalid_issue) echo 0 ;;
2233
+ api_error) echo "${MAX_RETRIES_API_ERROR:-4}" ;;
2234
+ context_exhaustion) echo "${MAX_RETRIES_CONTEXT_EXHAUSTION:-2}" ;;
2235
+ build_failure) echo "${MAX_RETRIES_BUILD:-2}" ;;
2236
+ *) echo "${MAX_RETRIES:-2}" ;;
2237
+ esac
2238
+ }
2239
+
2240
+ # Append failure to persisted history and compute consecutive count; smart pause with exponential backoff
2198
2241
  record_failure_class() {
2199
2242
  local failure_class="$1"
2243
+ # In-memory consecutive (for backward compat)
2200
2244
  if [[ "$failure_class" == "$DAEMON_CONSECUTIVE_FAILURE_CLASS" ]]; then
2201
2245
  DAEMON_CONSECUTIVE_FAILURE_COUNT=$((DAEMON_CONSECUTIVE_FAILURE_COUNT + 1))
2202
2246
  else
@@ -2204,16 +2248,55 @@ record_failure_class() {
2204
2248
  DAEMON_CONSECUTIVE_FAILURE_COUNT=1
2205
2249
  fi
2206
2250
 
2207
- if [[ "$DAEMON_CONSECUTIVE_FAILURE_COUNT" -ge 3 ]]; then
2208
- daemon_log ERROR "3 consecutive failures (class: ${failure_class}) auto-pausing daemon"
2251
+ # Persist failure to state (failure_history) for pattern tracking
2252
+ if [[ -f "${STATE_FILE:-}" ]]; then
2253
+ local entry
2254
+ entry=$(jq -n --arg ts "$(now_iso)" --arg class "$failure_class" '{ts: $ts, class: $class}')
2255
+ locked_state_update --argjson entry "$entry" \
2256
+ '.failure_history = ((.failure_history // []) + [$entry] | .[-100:])' 2>/dev/null || true
2257
+ fi
2258
+
2259
+ # Consecutive count from persisted tail: count only the unbroken run of $failure_class
2260
+ # from the newest entry backwards (not total occurrences)
2261
+ local consecutive="$DAEMON_CONSECUTIVE_FAILURE_COUNT"
2262
+ if [[ -f "${STATE_FILE:-}" ]]; then
2263
+ local from_state
2264
+ from_state=$(jq -r --arg c "$failure_class" '
2265
+ (.failure_history // []) | [.[].class] | reverse |
2266
+ if length == 0 then 0
2267
+ elif .[0] != $c then 0
2268
+ else
2269
+ reduce .[] as $x (
2270
+ {count: 0, done: false};
2271
+ if .done then . elif $x == $c then .count += 1 else .done = true end
2272
+ ) | .count
2273
+ end
2274
+ ' "$STATE_FILE" 2>/dev/null || echo "1")
2275
+ consecutive="${from_state:-1}"
2276
+ [[ "$consecutive" -eq 0 ]] && consecutive="$DAEMON_CONSECUTIVE_FAILURE_COUNT"
2277
+ DAEMON_CONSECUTIVE_FAILURE_COUNT="$consecutive"
2278
+ fi
2279
+
2280
+ # Smart pause: exponential backoff instead of hard stop (resume_after so daemon can auto-resume)
2281
+ if [[ "$consecutive" -ge 3 ]]; then
2282
+ local pause_mins=$((5 * (1 << (consecutive - 3))))
2283
+ [[ "$pause_mins" -gt 480 ]] && pause_mins=480
2284
+ local resume_ts resume_after
2285
+ resume_ts=$(($(date +%s) + pause_mins * 60))
2286
+ resume_after=$(epoch_to_iso "$resume_ts")
2287
+ daemon_log ERROR "${consecutive} consecutive failures (class: ${failure_class}) — auto-pausing until ${resume_after} (${pause_mins}m backoff)"
2209
2288
  local pause_json
2210
- pause_json=$(jq -n --arg reason "consecutive_${failure_class}" --arg ts "$(now_iso)" \
2211
- '{reason: $reason, timestamp: $ts}')
2289
+ pause_json=$(jq -n \
2290
+ --arg reason "consecutive_${failure_class}" \
2291
+ --arg ts "$(now_iso)" \
2292
+ --arg resume "$resume_after" \
2293
+ --argjson count "$consecutive" \
2294
+ '{reason: $reason, timestamp: $ts, resume_after: $resume, consecutive_count: $count}')
2212
2295
  local _tmp_pause
2213
2296
  _tmp_pause=$(mktemp "${TMPDIR:-/tmp}/sw-pause.XXXXXX")
2214
2297
  echo "$pause_json" > "$_tmp_pause"
2215
2298
  mv "$_tmp_pause" "$PAUSE_FLAG"
2216
- emit_event "daemon.auto_pause" "reason=consecutive_failures" "class=$failure_class" "count=$DAEMON_CONSECUTIVE_FAILURE_COUNT"
2299
+ emit_event "daemon.auto_pause" "reason=consecutive_failures" "class=$failure_class" "count=$consecutive" "resume_after=$resume_after"
2217
2300
  fi
2218
2301
  }
2219
2302
 
@@ -2288,8 +2371,10 @@ daemon_on_failure() {
2288
2371
  fi
2289
2372
  ;;
2290
2373
  *)
2291
- # Retryable failures — proceed with escalation
2292
- if [[ "$retry_count" -lt "${MAX_RETRIES:-2}" ]]; then
2374
+ # Retryable failures — per-class max retries and escalation
2375
+ local effective_max
2376
+ effective_max=$(get_max_retries_for_class "$failure_class")
2377
+ if [[ "$retry_count" -lt "$effective_max" ]]; then
2293
2378
  retry_count=$((retry_count + 1))
2294
2379
 
2295
2380
  # Update retry count in state (locked to prevent race)
@@ -2297,8 +2382,8 @@ daemon_on_failure() {
2297
2382
  --arg num "$issue_num" --argjson count "$retry_count" \
2298
2383
  '.retry_counts[$num] = $count'
2299
2384
 
2300
- daemon_log WARN "Auto-retry #${retry_count}/${MAX_RETRIES:-2} for issue #${issue_num} (class: ${failure_class})"
2301
- emit_event "daemon.retry" "issue=$issue_num" "retry=$retry_count" "max=${MAX_RETRIES:-2}" "class=$failure_class"
2385
+ daemon_log WARN "Auto-retry #${retry_count}/${effective_max} for issue #${issue_num} (class: ${failure_class})"
2386
+ emit_event "daemon.retry" "issue=$issue_num" "retry=$retry_count" "max=$effective_max" "class=$failure_class"
2302
2387
 
2303
2388
  # Check for checkpoint to enable resume-from-checkpoint
2304
2389
  local checkpoint_args=()
@@ -2343,13 +2428,12 @@ daemon_on_failure() {
2343
2428
  daemon_log INFO "Boosting max-restarts to $boosted_restarts (context exhaustion)"
2344
2429
  fi
2345
2430
 
2346
- # API errors get extended backoff
2347
- local api_backoff=300
2348
- local backoff_secs=$((30 * retry_count))
2349
- if [[ "$failure_class" == "api_error" ]]; then
2350
- backoff_secs=$((api_backoff * retry_count))
2351
- daemon_log INFO "API error — extended backoff ${backoff_secs}s"
2352
- fi
2431
+ # Exponential backoff (per-class base); cap at 1h
2432
+ local base_secs=30
2433
+ [[ "$failure_class" == "api_error" ]] && base_secs=300
2434
+ local backoff_secs=$((base_secs * (1 << (retry_count - 1))))
2435
+ [[ "$backoff_secs" -gt 3600 ]] && backoff_secs=3600
2436
+ [[ "$failure_class" == "api_error" ]] && daemon_log INFO "API error — exponential backoff ${backoff_secs}s"
2353
2437
 
2354
2438
  if [[ "$NO_GITHUB" != "true" ]]; then
2355
2439
  gh issue comment "$issue_num" --body "## 🔄 Auto-Retry #${retry_count}
@@ -2391,13 +2475,18 @@ _Escalation: $(if [[ "$retry_count" -eq 1 ]]; then echo "upgraded model + increa
2391
2475
  return
2392
2476
  fi
2393
2477
 
2394
- daemon_log WARN "Max retries (${MAX_RETRIES:-2}) exhausted for issue #${issue_num}"
2478
+ daemon_log WARN "Max retries (${effective_max}) exhausted for issue #${issue_num}"
2395
2479
  emit_event "daemon.retry_exhausted" "issue=$issue_num" "retries=$retry_count"
2396
2480
  ;;
2397
2481
  esac
2398
2482
  fi
2399
2483
 
2400
2484
  # ── No retry — report final failure ──
2485
+ # PM agent: record failure for learning (only when we're done with this issue)
2486
+ if [[ -x "$SCRIPT_DIR/sw-pm.sh" ]]; then
2487
+ bash "$SCRIPT_DIR/sw-pm.sh" learn "$issue_num" failure 2>/dev/null || true
2488
+ fi
2489
+
2401
2490
  if [[ "$NO_GITHUB" != "true" ]]; then
2402
2491
  # Add failure label and remove watch label (prevent re-processing)
2403
2492
  gh issue edit "$issue_num" \
@@ -2422,10 +2511,11 @@ _Escalation: $(if [[ "$retry_count" -eq 1 ]]; then echo "upgraded model + increa
2422
2511
 
2423
2512
  local retry_info=""
2424
2513
  if [[ "${RETRY_ESCALATION:-true}" == "true" ]]; then
2425
- local final_count
2514
+ local final_count final_max
2426
2515
  final_count=$(jq -r --arg num "$issue_num" \
2427
2516
  '.retry_counts[$num] // 0' "$STATE_FILE" 2>/dev/null || echo "0")
2428
- retry_info="| Retries | ${final_count} / ${MAX_RETRIES:-2} (exhausted) |"
2517
+ final_max=$(get_max_retries_for_class "$failure_class")
2518
+ retry_info="| Retries | ${final_count} / ${final_max} (exhausted) |"
2429
2519
  fi
2430
2520
 
2431
2521
  gh issue comment "$issue_num" --body "## ❌ Pipeline Failed
@@ -4033,10 +4123,27 @@ daemon_poll_issues() {
4033
4123
  return
4034
4124
  fi
4035
4125
 
4036
- # Check for pause flag (set by dashboard or disk_low alert)
4037
- if [[ -f "$HOME/.shipwright/daemon-pause.flag" ]]; then
4038
- daemon_log INFO "Daemon paused — skipping poll"
4039
- return
4126
+ # Check for pause flag (set by dashboard, disk_low, or consecutive-failure backoff)
4127
+ local pause_file="${PAUSE_FLAG:-$HOME/.shipwright/daemon-pause.flag}"
4128
+ if [[ -f "$pause_file" ]]; then
4129
+ local resume_after
4130
+ resume_after=$(jq -r '.resume_after // empty' "$pause_file" 2>/dev/null || true)
4131
+ if [[ -n "$resume_after" ]]; then
4132
+ local now_epoch resume_epoch
4133
+ now_epoch=$(date +%s)
4134
+ resume_epoch=$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$resume_after" +%s 2>/dev/null || \
4135
+ date -d "$resume_after" +%s 2>/dev/null || echo 0)
4136
+ if [[ "$resume_epoch" -gt 0 ]] && [[ "$now_epoch" -ge "$resume_epoch" ]]; then
4137
+ rm -f "$pause_file"
4138
+ daemon_log INFO "Auto-resuming after backoff (resume_after passed)"
4139
+ else
4140
+ daemon_log INFO "Daemon paused until ${resume_after} — skipping poll"
4141
+ return
4142
+ fi
4143
+ else
4144
+ daemon_log INFO "Daemon paused — skipping poll"
4145
+ return
4146
+ fi
4040
4147
  fi
4041
4148
 
4042
4149
  # Circuit breaker: skip poll if in backoff window
@@ -4274,9 +4381,25 @@ daemon_poll_issues() {
4274
4381
  continue
4275
4382
  fi
4276
4383
 
4277
- # Auto-select pipeline template based on labels + triage score
4384
+ # Auto-select pipeline template: PM recommendation (if available) else labels + triage score
4278
4385
  local template
4279
- template=$(select_pipeline_template "$labels_csv" "$score" 2>/dev/null | tail -1)
4386
+ if [[ "$NO_GITHUB" != "true" ]] && [[ -x "$SCRIPT_DIR/sw-pm.sh" ]]; then
4387
+ local pm_rec
4388
+ pm_rec=$(bash "$SCRIPT_DIR/sw-pm.sh" recommend --json "$issue_num" 2>/dev/null) || true
4389
+ if [[ -n "$pm_rec" ]]; then
4390
+ template=$(echo "$pm_rec" | jq -r '.team_composition.template // empty' 2>/dev/null) || true
4391
+ # Capability self-assessment: low confidence → upgrade to full template
4392
+ local confidence
4393
+ confidence=$(echo "$pm_rec" | jq -r '.team_composition.confidence_percent // 100' 2>/dev/null) || true
4394
+ if [[ -n "$confidence" && "$confidence" != "null" && "$confidence" -lt 60 ]]; then
4395
+ daemon_log INFO "Low PM confidence (${confidence}%) — upgrading to full template"
4396
+ template="full"
4397
+ fi
4398
+ fi
4399
+ fi
4400
+ if [[ -z "$template" ]]; then
4401
+ template=$(select_pipeline_template "$labels_csv" "$score" 2>/dev/null | tail -1)
4402
+ fi
4280
4403
  template=$(printf '%s' "$template" | sed $'s/\x1b\\[[0-9;]*m//g' | tr -cd '[:alnum:]-_')
4281
4404
  [[ -z "$template" ]] && template="$PIPELINE_TEMPLATE"
4282
4405
  daemon_log INFO "Triage: issue #${issue_num} scored ${score}, template=${template}"
@@ -5253,6 +5376,11 @@ daemon_start() {
5253
5376
  # Remove stale shutdown flag
5254
5377
  rm -f "$SHUTDOWN_FLAG"
5255
5378
 
5379
+ # Initialize SQLite database (if available)
5380
+ if type init_schema &>/dev/null; then
5381
+ init_schema 2>/dev/null || true
5382
+ fi
5383
+
5256
5384
  # Initialize state
5257
5385
  init_state
5258
5386
 
@@ -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.0.0"
9
+ VERSION="2.1.1"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────