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
@@ -11,7 +11,7 @@ unset CLAUDECODE 2>/dev/null || true
11
11
  # Ignore SIGHUP so tmux attach/detach doesn't kill long-running plan/design/review stages
12
12
  trap '' HUP
13
13
 
14
- VERSION="2.0.0"
14
+ VERSION="2.1.1"
15
15
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
16
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
17
17
 
@@ -202,7 +202,9 @@ PIPELINE_CONFIG=""
202
202
  TEST_CMD=""
203
203
  MODEL=""
204
204
  AGENTS=""
205
+ PIPELINE_AGENT_ID="${PIPELINE_AGENT_ID:-pipeline-$$}"
205
206
  SKIP_GATES=false
207
+ HEADLESS=false
206
208
  GIT_BRANCH=""
207
209
  GITHUB_ISSUE=""
208
210
  TASK_TYPE=""
@@ -223,7 +225,9 @@ AUTO_WORKTREE=false
223
225
  WORKTREE_NAME=""
224
226
  CLEANUP_WORKTREE=false
225
227
  ORIGINAL_REPO_DIR=""
228
+ REPO_OVERRIDE=""
226
229
  _cleanup_done=""
230
+ PIPELINE_EXIT_CODE=1 # assume failure until run_pipeline succeeds
227
231
 
228
232
  # GitHub metadata (populated during intake)
229
233
  ISSUE_LABELS=""
@@ -264,11 +268,14 @@ show_help() {
264
268
  echo -e "${BOLD}START OPTIONS${RESET}"
265
269
  echo -e " ${DIM}--goal \"description\"${RESET} What to build (required unless --issue)"
266
270
  echo -e " ${DIM}--issue <number>${RESET} Fetch goal from GitHub issue"
271
+ echo -e " ${DIM}--repo <path>${RESET} Change to directory before running (must be a git repo)"
272
+ echo -e " ${DIM}--local${RESET} Alias for --no-github --no-github-label (local-only mode)"
267
273
  echo -e " ${DIM}--pipeline <name>${RESET} Pipeline template (default: standard)"
268
274
  echo -e " ${DIM}--test-cmd \"command\"${RESET} Override test command (auto-detected if omitted)"
269
275
  echo -e " ${DIM}--model <model>${RESET} Override AI model (opus, sonnet, haiku)"
270
276
  echo -e " ${DIM}--agents <n>${RESET} Override agent count"
271
277
  echo -e " ${DIM}--skip-gates${RESET} Auto-approve all gates (fully autonomous)"
278
+ echo -e " ${DIM}--headless${RESET} Full headless mode (skip gates, no prompts)"
272
279
  echo -e " ${DIM}--base <branch>${RESET} Base branch for PR (default: main)"
273
280
  echo -e " ${DIM}--reviewers \"a,b\"${RESET} Request PR reviewers (auto-detected if omitted)"
274
281
  echo -e " ${DIM}--labels \"a,b\"${RESET} Add labels to PR (inherited from issue if omitted)"
@@ -346,11 +353,14 @@ parse_args() {
346
353
  case "$1" in
347
354
  --goal) GOAL="$2"; shift 2 ;;
348
355
  --issue) ISSUE_NUMBER="$2"; shift 2 ;;
356
+ --repo) REPO_OVERRIDE="$2"; shift 2 ;;
357
+ --local) NO_GITHUB=true; NO_GITHUB_LABEL=true; shift ;;
349
358
  --pipeline|--template) PIPELINE_NAME="$2"; shift 2 ;;
350
359
  --test-cmd) TEST_CMD="$2"; shift 2 ;;
351
360
  --model) MODEL="$2"; shift 2 ;;
352
361
  --agents) AGENTS="$2"; shift 2 ;;
353
362
  --skip-gates) SKIP_GATES=true; shift ;;
363
+ --headless) HEADLESS=true; SKIP_GATES=true; shift ;;
354
364
  --base) BASE_BRANCH="$2"; shift 2 ;;
355
365
  --reviewers) REVIEWERS="$2"; shift 2 ;;
356
366
  --labels) LABELS="$2"; shift 2 ;;
@@ -387,6 +397,20 @@ parse_args() {
387
397
  PIPELINE_NAME_ARG=""
388
398
  parse_args "$@"
389
399
 
400
+ # ─── Non-Interactive Detection ──────────────────────────────────────────────
401
+ # When stdin is not a terminal (background, pipe, nohup, tmux send-keys),
402
+ # auto-enable headless mode to prevent read prompts from killing the script.
403
+ if [[ ! -t 0 ]]; then
404
+ HEADLESS=true
405
+ if [[ "$SKIP_GATES" != "true" ]]; then
406
+ SKIP_GATES=true
407
+ fi
408
+ fi
409
+ # --worktree implies headless when stdin is not a terminal
410
+ if [[ "$AUTO_WORKTREE" == "true" && "$SKIP_GATES" != "true" && ! -t 0 ]]; then
411
+ SKIP_GATES=true
412
+ fi
413
+
390
414
  # ─── Directory Setup ────────────────────────────────────────────────────────
391
415
 
392
416
  setup_dirs() {
@@ -396,6 +420,7 @@ setup_dirs() {
396
420
  ARTIFACTS_DIR="$STATE_DIR/pipeline-artifacts"
397
421
  TASKS_FILE="$STATE_DIR/pipeline-tasks.md"
398
422
  mkdir -p "$STATE_DIR" "$ARTIFACTS_DIR"
423
+ export SHIPWRIGHT_PIPELINE_ID="pipeline-$$-${ISSUE_NUMBER:-0}"
399
424
  }
400
425
 
401
426
  # ─── Pipeline Config Loading ───────────────────────────────────────────────
@@ -404,10 +429,11 @@ find_pipeline_config() {
404
429
  local name="$1"
405
430
  local locations=(
406
431
  "$REPO_DIR/templates/pipelines/${name}.json"
432
+ "${PROJECT_ROOT:-}/templates/pipelines/${name}.json"
407
433
  "$HOME/.shipwright/pipelines/${name}.json"
408
434
  )
409
435
  for loc in "${locations[@]}"; do
410
- if [[ -f "$loc" ]]; then
436
+ if [[ -n "$loc" && -f "$loc" ]]; then
411
437
  echo "$loc"
412
438
  return 0
413
439
  fi
@@ -1155,6 +1181,21 @@ get_stage_timing() {
1155
1181
  fi
1156
1182
  }
1157
1183
 
1184
+ # Raw seconds for a stage (for memory baseline updates)
1185
+ get_stage_timing_seconds() {
1186
+ local stage_id="$1"
1187
+ local start_e end_e
1188
+ start_e=$(echo "$STAGE_TIMINGS" | grep "^${stage_id}_start:" | cut -d: -f2 | tail -1 || true)
1189
+ end_e=$(echo "$STAGE_TIMINGS" | grep "^${stage_id}_end:" | cut -d: -f2 | tail -1 || true)
1190
+ if [[ -n "$start_e" && -n "$end_e" ]]; then
1191
+ echo $(( end_e - start_e ))
1192
+ elif [[ -n "$start_e" ]]; then
1193
+ echo $(( $(now_epoch) - start_e ))
1194
+ else
1195
+ echo "0"
1196
+ fi
1197
+ }
1198
+
1158
1199
  get_stage_description() {
1159
1200
  local stage_id="$1"
1160
1201
 
@@ -1250,6 +1291,22 @@ mark_stage_complete() {
1250
1291
  log_stage "$stage_id" "complete (${timing})"
1251
1292
  write_state
1252
1293
 
1294
+ record_stage_effectiveness "$stage_id" "complete"
1295
+ # Update memory baselines and predictive baselines for stage durations
1296
+ if [[ "$stage_id" == "test" || "$stage_id" == "build" ]]; then
1297
+ local secs
1298
+ secs=$(get_stage_timing_seconds "$stage_id")
1299
+ if [[ -n "$secs" && "$secs" != "0" ]]; then
1300
+ [[ -x "$SCRIPT_DIR/sw-memory.sh" ]] && bash "$SCRIPT_DIR/sw-memory.sh" metric "${stage_id}_duration_s" "$secs" 2>/dev/null || true
1301
+ if [[ -x "$SCRIPT_DIR/sw-predictive.sh" ]]; then
1302
+ local anomaly_sev
1303
+ anomaly_sev=$(bash "$SCRIPT_DIR/sw-predictive.sh" anomaly "$stage_id" "duration_s" "$secs" 2>/dev/null || echo "normal")
1304
+ [[ "$anomaly_sev" == "critical" || "$anomaly_sev" == "warning" ]] && emit_event "pipeline.anomaly" "stage=$stage_id" "metric=duration_s" "value=$secs" "severity=$anomaly_sev" 2>/dev/null || true
1305
+ bash "$SCRIPT_DIR/sw-predictive.sh" baseline "$stage_id" "duration_s" "$secs" 2>/dev/null || true
1306
+ fi
1307
+ fi
1308
+ fi
1309
+
1253
1310
  # Update GitHub progress comment
1254
1311
  if [[ -n "$ISSUE_NUMBER" ]]; then
1255
1312
  local body
@@ -1346,9 +1403,40 @@ verify_stage_artifacts() {
1346
1403
  return "$missing"
1347
1404
  }
1348
1405
 
1406
+ # Self-aware pipeline: record stage effectiveness for meta-cognition
1407
+ STAGE_EFFECTIVENESS_FILE="${HOME}/.shipwright/stage-effectiveness.jsonl"
1408
+ record_stage_effectiveness() {
1409
+ local stage_id="$1" outcome="${2:-failed}"
1410
+ mkdir -p "${HOME}/.shipwright"
1411
+ echo "{\"stage\":\"$stage_id\",\"outcome\":\"$outcome\",\"ts\":\"$(now_iso)\"}" >> "${STAGE_EFFECTIVENESS_FILE}"
1412
+ # Keep last 100 entries
1413
+ tail -100 "${STAGE_EFFECTIVENESS_FILE}" > "${STAGE_EFFECTIVENESS_FILE}.tmp" 2>/dev/null && mv "${STAGE_EFFECTIVENESS_FILE}.tmp" "${STAGE_EFFECTIVENESS_FILE}" 2>/dev/null || true
1414
+ }
1415
+ get_stage_self_awareness_hint() {
1416
+ local stage_id="$1"
1417
+ [[ ! -f "$STAGE_EFFECTIVENESS_FILE" ]] && return 0
1418
+ local recent
1419
+ recent=$(grep "\"stage\":\"$stage_id\"" "$STAGE_EFFECTIVENESS_FILE" 2>/dev/null | tail -10 || true)
1420
+ [[ -z "$recent" ]] && return 0
1421
+ local failures=0 total=0
1422
+ while IFS= read -r line; do
1423
+ [[ -z "$line" ]] && continue
1424
+ total=$((total + 1))
1425
+ echo "$line" | grep -q '"outcome":"failed"' && failures=$((failures + 1)) || true
1426
+ done <<< "$recent"
1427
+ if [[ "$total" -ge 3 ]] && [[ $((failures * 100 / total)) -ge 50 ]]; then
1428
+ case "$stage_id" in
1429
+ plan) echo "Recent plan stage failures: consider adding more context or breaking the goal into smaller steps." ;;
1430
+ build) echo "Recent build stage failures: consider adding test expectations or simplifying the change." ;;
1431
+ *) echo "Recent $stage_id failures: review past logs and adjust approach." ;;
1432
+ esac
1433
+ fi
1434
+ }
1435
+
1349
1436
  mark_stage_failed() {
1350
1437
  local stage_id="$1"
1351
1438
  record_stage_end "$stage_id"
1439
+ record_stage_effectiveness "$stage_id" "failed"
1352
1440
  set_stage_status "$stage_id" "failed"
1353
1441
  local timing
1354
1442
  timing=$(get_stage_timing "$stage_id")
@@ -1779,6 +1867,28 @@ ${memory_summary}
1779
1867
  fi
1780
1868
  fi
1781
1869
 
1870
+ # Self-aware pipeline: inject hint when plan stage has been failing recently
1871
+ local plan_hint
1872
+ plan_hint=$(get_stage_self_awareness_hint "plan" 2>/dev/null || true)
1873
+ if [[ -n "$plan_hint" ]]; then
1874
+ plan_prompt="${plan_prompt}
1875
+ ## Self-Assessment (recent plan stage performance)
1876
+ ${plan_hint}
1877
+ "
1878
+ fi
1879
+
1880
+ # Inject cross-pipeline discoveries (from other concurrent/similar pipelines)
1881
+ if [[ -x "$SCRIPT_DIR/sw-discovery.sh" ]]; then
1882
+ local plan_discoveries
1883
+ plan_discoveries=$("$SCRIPT_DIR/sw-discovery.sh" inject "*.md,*.json" 2>/dev/null | head -20 || true)
1884
+ if [[ -n "$plan_discoveries" ]]; then
1885
+ plan_prompt="${plan_prompt}
1886
+ ## Discoveries from Other Pipelines
1887
+ ${plan_discoveries}
1888
+ "
1889
+ fi
1890
+ fi
1891
+
1782
1892
  # Inject architecture patterns from intelligence layer
1783
1893
  local repo_hash_plan
1784
1894
  repo_hash_plan=$(echo -n "$PROJECT_ROOT" | shasum -a 256 2>/dev/null | cut -c1-12 || echo "unknown")
@@ -2151,6 +2261,12 @@ stage_design() {
2151
2261
  memory_context=$(bash "$SCRIPT_DIR/sw-memory.sh" inject "design" 2>/dev/null) || true
2152
2262
  fi
2153
2263
 
2264
+ # Inject cross-pipeline discoveries for design stage
2265
+ local design_discoveries=""
2266
+ if [[ -x "$SCRIPT_DIR/sw-discovery.sh" ]]; then
2267
+ design_discoveries=$("$SCRIPT_DIR/sw-discovery.sh" inject "*.md,*.ts,*.tsx,*.js" 2>/dev/null | head -20 || true)
2268
+ fi
2269
+
2154
2270
  # Inject architecture model patterns if available
2155
2271
  local arch_context=""
2156
2272
  local repo_hash
@@ -2210,7 +2326,10 @@ ${memory_context}
2210
2326
  }${arch_context:+
2211
2327
  ## Architecture Model (from previous designs)
2212
2328
  ${arch_context}
2213
- }${design_antipatterns}
2329
+ }${design_antipatterns}${design_discoveries:+
2330
+ ## Discoveries from Other Pipelines
2331
+ ${design_discoveries}
2332
+ }
2214
2333
  ## Required Output — Architecture Decision Record
2215
2334
 
2216
2335
  Produce this EXACT format:
@@ -2334,6 +2453,18 @@ Historical context (lessons from previous pipelines):
2334
2453
  ${memory_context}"
2335
2454
  fi
2336
2455
 
2456
+ # Inject cross-pipeline discoveries for build stage
2457
+ if [[ -x "$SCRIPT_DIR/sw-discovery.sh" ]]; then
2458
+ local build_discoveries
2459
+ build_discoveries=$("$SCRIPT_DIR/sw-discovery.sh" inject "src/*,*.ts,*.tsx,*.js" 2>/dev/null | head -20 || true)
2460
+ if [[ -n "$build_discoveries" ]]; then
2461
+ enriched_goal="${enriched_goal}
2462
+
2463
+ Discoveries from other pipelines:
2464
+ ${build_discoveries}"
2465
+ fi
2466
+ fi
2467
+
2337
2468
  # Add task list context
2338
2469
  if [[ -s "$TASKS_FILE" ]]; then
2339
2470
  enriched_goal="${enriched_goal}
@@ -2380,6 +2511,19 @@ Coverage baseline: ${coverage_baseline}% — do not decrease coverage."
2380
2511
  fi
2381
2512
  fi
2382
2513
 
2514
+ # Predictive: inject prevention hints when risk/memory patterns suggest build-stage failures
2515
+ if [[ -x "$SCRIPT_DIR/sw-predictive.sh" ]]; then
2516
+ local issue_json_build="{}"
2517
+ [[ -n "${ISSUE_NUMBER:-}" ]] && issue_json_build=$(jq -n --arg title "${GOAL:-}" --arg num "${ISSUE_NUMBER:-}" '{title: $title, number: $num}')
2518
+ local prevention_text
2519
+ prevention_text=$(bash "$SCRIPT_DIR/sw-predictive.sh" inject-prevention "build" "$issue_json_build" 2>/dev/null || true)
2520
+ if [[ -n "$prevention_text" ]]; then
2521
+ enriched_goal="${enriched_goal}
2522
+
2523
+ ${prevention_text}"
2524
+ fi
2525
+ fi
2526
+
2383
2527
  loop_args+=("$enriched_goal")
2384
2528
 
2385
2529
  # Build loop args from pipeline config + CLI overrides
@@ -2432,6 +2576,23 @@ Coverage baseline: ${coverage_baseline}% — do not decrease coverage."
2432
2576
  build_model="$CLAUDE_MODEL"
2433
2577
  fi
2434
2578
 
2579
+ # Recruit-powered model selection (when no explicit override)
2580
+ if [[ -z "$MODEL" ]] && [[ -x "$SCRIPT_DIR/sw-recruit.sh" ]]; then
2581
+ local _recruit_goal="${GOAL:-}"
2582
+ if [[ -n "$_recruit_goal" ]]; then
2583
+ local _recruit_match
2584
+ _recruit_match=$(bash "$SCRIPT_DIR/sw-recruit.sh" match --json "$_recruit_goal" 2>/dev/null) || true
2585
+ if [[ -n "$_recruit_match" ]]; then
2586
+ local _recruit_model
2587
+ _recruit_model=$(echo "$_recruit_match" | jq -r '.model // ""' 2>/dev/null) || true
2588
+ if [[ -n "$_recruit_model" && "$_recruit_model" != "null" && "$_recruit_model" != "" ]]; then
2589
+ info "Recruit recommends model: ${CYAN}${_recruit_model}${RESET} for this task"
2590
+ build_model="$_recruit_model"
2591
+ fi
2592
+ fi
2593
+ fi
2594
+ fi
2595
+
2435
2596
  [[ -n "$test_cmd" && "$test_cmd" != "null" ]] && loop_args+=(--test-cmd "$test_cmd")
2436
2597
  loop_args+=(--max-iterations "$max_iter")
2437
2598
  loop_args+=(--model "$build_model")
@@ -2488,6 +2649,23 @@ Coverage baseline: ${coverage_baseline}% — do not decrease coverage."
2488
2649
  }
2489
2650
  parse_claude_tokens "$_token_log"
2490
2651
 
2652
+ # Read accumulated token counts from build loop (written by sw-loop.sh)
2653
+ local _loop_token_file="${PROJECT_ROOT}/.claude/loop-logs/loop-tokens.json"
2654
+ if [[ -f "$_loop_token_file" ]] && command -v jq &>/dev/null; then
2655
+ local _loop_in _loop_out _loop_cost
2656
+ _loop_in=$(jq -r '.input_tokens // 0' "$_loop_token_file" 2>/dev/null || echo "0")
2657
+ _loop_out=$(jq -r '.output_tokens // 0' "$_loop_token_file" 2>/dev/null || echo "0")
2658
+ _loop_cost=$(jq -r '.cost_usd // 0' "$_loop_token_file" 2>/dev/null || echo "0")
2659
+ TOTAL_INPUT_TOKENS=$(( TOTAL_INPUT_TOKENS + ${_loop_in:-0} ))
2660
+ TOTAL_OUTPUT_TOKENS=$(( TOTAL_OUTPUT_TOKENS + ${_loop_out:-0} ))
2661
+ if [[ -n "$_loop_cost" && "$_loop_cost" != "0" && "$_loop_cost" != "null" ]]; then
2662
+ TOTAL_COST_USD="${_loop_cost}"
2663
+ fi
2664
+ if [[ "${_loop_in:-0}" -gt 0 || "${_loop_out:-0}" -gt 0 ]]; then
2665
+ info "Build loop tokens: in=${_loop_in} out=${_loop_out} cost=\$${_loop_cost:-0}"
2666
+ fi
2667
+ fi
2668
+
2491
2669
  # Count commits made during build
2492
2670
  local commit_count
2493
2671
  commit_count=$(git log --oneline "${BASE_BRANCH}..HEAD" 2>/dev/null | wc -l | xargs)
@@ -2792,6 +2970,22 @@ $(cat "$diff_file")"
2792
2970
  success "Review clean"
2793
2971
  fi
2794
2972
 
2973
+ # ── Oversight gate: pipeline review/quality stages block on verdict ──
2974
+ if [[ -x "$SCRIPT_DIR/sw-oversight.sh" ]] && [[ "${SKIP_GATES:-false}" != "true" ]]; then
2975
+ local reject_reason=""
2976
+ local _sec_count
2977
+ _sec_count=$(grep -ciE '\*\*\[?Security\]?\*\*' "$review_file" 2>/dev/null || true)
2978
+ _sec_count="${_sec_count:-0}"
2979
+ local _blocking=$((critical_count + _sec_count))
2980
+ [[ "$_blocking" -gt 0 ]] && reject_reason="Review found ${_blocking} critical/security issue(s)"
2981
+ if ! bash "$SCRIPT_DIR/sw-oversight.sh" gate --diff "$diff_file" --description "${GOAL:-Pipeline review}" --reject-if "$reject_reason" >/dev/null 2>&1; then
2982
+ error "Oversight gate rejected — blocking pipeline"
2983
+ emit_event "review.oversight_blocked" "issue=${ISSUE_NUMBER:-0}"
2984
+ log_stage "review" "BLOCKED: oversight gate rejected"
2985
+ return 1
2986
+ fi
2987
+ fi
2988
+
2795
2989
  # ── Review Blocking Gate ──
2796
2990
  # Block pipeline on critical/security issues unless compound_quality handles them
2797
2991
  local security_count
@@ -3771,6 +3965,8 @@ stage_monitor() {
3771
3965
  fi
3772
3966
 
3773
3967
  local report_file="$ARTIFACTS_DIR/monitor-report.md"
3968
+ local deploy_log_file="$ARTIFACTS_DIR/deploy-logs.txt"
3969
+ : > "$deploy_log_file"
3774
3970
  local total_errors=0
3775
3971
  local poll_interval=30 # seconds between polls
3776
3972
  local total_polls=$(( (duration_minutes * 60) / poll_interval ))
@@ -3818,10 +4014,11 @@ stage_monitor() {
3818
4014
  fi
3819
4015
  fi
3820
4016
 
3821
- # Log command check
4017
+ # Log command check (accumulate deploy logs for feedback collect)
3822
4018
  if [[ -n "$log_cmd" ]]; then
3823
4019
  local log_output
3824
4020
  log_output=$(bash -c "$log_cmd" 2>/dev/null || true)
4021
+ [[ -n "$log_output" ]] && echo "$log_output" >> "$deploy_log_file"
3825
4022
  local error_count=0
3826
4023
  if [[ -n "$log_output" ]]; then
3827
4024
  error_count=$(echo "$log_output" | grep -cE "$log_pattern" 2>/dev/null || true)
@@ -3856,13 +4053,24 @@ stage_monitor() {
3856
4053
  "total_errors=$total_errors" \
3857
4054
  "threshold=$error_threshold"
3858
4055
 
3859
- # Auto-rollback if configured
3860
- if [[ "$auto_rollback" == "true" && -n "$rollback_cmd" ]]; then
4056
+ # Feedback loop: collect deploy logs and optionally create issue
4057
+ if [[ -f "$deploy_log_file" ]] && [[ -s "$deploy_log_file" ]] && [[ -x "$SCRIPT_DIR/sw-feedback.sh" ]]; then
4058
+ (cd "$PROJECT_ROOT" && ARTIFACTS_DIR="$ARTIFACTS_DIR" bash "$SCRIPT_DIR/sw-feedback.sh" collect "$deploy_log_file" 2>/dev/null) || true
4059
+ (cd "$PROJECT_ROOT" && ARTIFACTS_DIR="$ARTIFACTS_DIR" bash "$SCRIPT_DIR/sw-feedback.sh" create-issue 2>/dev/null) || true
4060
+ fi
4061
+
4062
+ # Auto-rollback: feedback rollback (GitHub Deployments API) and/or config rollback_cmd
4063
+ if [[ "$auto_rollback" == "true" ]]; then
3861
4064
  warn "Auto-rolling back..."
3862
4065
  echo "" >> "$report_file"
3863
4066
  echo "## Rollback" >> "$report_file"
3864
4067
 
3865
- if bash -c "$rollback_cmd" >> "$report_file" 2>&1; then
4068
+ # Trigger feedback rollback (calls sw-github-deploy.sh rollback)
4069
+ if [[ -x "$SCRIPT_DIR/sw-feedback.sh" ]]; then
4070
+ (cd "$PROJECT_ROOT" && ARTIFACTS_DIR="$ARTIFACTS_DIR" bash "$SCRIPT_DIR/sw-feedback.sh" rollback production "Monitor threshold exceeded (${total_errors} errors)" >> "$report_file" 2>&1) || true
4071
+ fi
4072
+
4073
+ if [[ -n "$rollback_cmd" ]] && bash -c "$rollback_cmd" >> "$report_file" 2>&1; then
3866
4074
  success "Rollback executed"
3867
4075
  echo "Rollback: ✅ success" >> "$report_file"
3868
4076
 
@@ -7076,6 +7284,12 @@ Focus on fixing the failing tests while keeping all passing tests working."
7076
7284
  _snap_error="${_snap_error:-}"
7077
7285
  pipeline_emit_progress_snapshot "${ISSUE_NUMBER}" "${CURRENT_STAGE_ID:-test}" "${cycle:-0}" "${_diff_count:-0}" "${_snap_files}" "${_snap_error}" 2>/dev/null || true
7078
7286
  fi
7287
+ # Record fix outcome when tests pass after a retry with memory injection (pipeline path)
7288
+ if [[ "$cycle" -gt 1 && -n "${last_test_error:-}" ]] && [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
7289
+ local _sig
7290
+ _sig=$(echo "$last_test_error" | head -3 | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
7291
+ [[ -n "$_sig" ]] && bash "$SCRIPT_DIR/sw-memory.sh" fix-outcome "$_sig" "true" "true" 2>/dev/null || true
7292
+ fi
7079
7293
  return 0 # Tests passed!
7080
7294
  fi
7081
7295
 
@@ -7285,7 +7499,9 @@ run_pipeline() {
7285
7499
  if [[ "$build_gate" == "approve" && "$SKIP_GATES" != "true" ]]; then
7286
7500
  show_stage_preview "build"
7287
7501
  local answer=""
7288
- read -rp " Proceed with build+test (self-healing)? [Y/n] " answer
7502
+ if [[ -t 0 ]]; then
7503
+ read -rp " Proceed with build+test (self-healing)? [Y/n] " answer || true
7504
+ fi
7289
7505
  if [[ "$answer" =~ ^[Nn] ]]; then
7290
7506
  update_status "paused" "build"
7291
7507
  info "Pipeline paused. Resume with: ${DIM}shipwright pipeline resume${RESET}"
@@ -7323,7 +7539,12 @@ run_pipeline() {
7323
7539
  if [[ "$gate" == "approve" && "$SKIP_GATES" != "true" ]]; then
7324
7540
  show_stage_preview "$id"
7325
7541
  local answer=""
7326
- read -rp " Proceed with ${id}? [Y/n] " answer
7542
+ if [[ -t 0 ]]; then
7543
+ read -rp " Proceed with ${id}? [Y/n] " answer || true
7544
+ else
7545
+ # Non-interactive: auto-approve (shouldn't reach here if headless detection works)
7546
+ info "Non-interactive mode — auto-approving ${id}"
7547
+ fi
7327
7548
  if [[ "$answer" =~ ^[Nn] ]]; then
7328
7549
  update_status "paused" "$id"
7329
7550
  info "Pipeline paused at ${BOLD}$id${RESET}. Resume with: ${DIM}shipwright pipeline resume${RESET}"
@@ -7426,11 +7647,29 @@ run_pipeline() {
7426
7647
  if run_stage_with_retry "$id"; then
7427
7648
  mark_stage_complete "$id"
7428
7649
  completed=$((completed + 1))
7650
+ # Capture project pattern after intake (for memory context in later stages)
7651
+ if [[ "$id" == "intake" ]] && [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
7652
+ (cd "$REPO_DIR" && bash "$SCRIPT_DIR/sw-memory.sh" pattern "project" "{}" 2>/dev/null) || true
7653
+ fi
7429
7654
  local timing stage_dur_s
7430
7655
  timing=$(get_stage_timing "$id")
7431
7656
  stage_dur_s=$(( $(now_epoch) - stage_start_epoch ))
7432
7657
  success "Stage ${BOLD}$id${RESET} complete ${DIM}(${timing})${RESET}"
7433
7658
  emit_event "stage.completed" "issue=${ISSUE_NUMBER:-0}" "stage=$id" "duration_s=$stage_dur_s"
7659
+ # Broadcast discovery for cross-pipeline learning
7660
+ if [[ -x "$SCRIPT_DIR/sw-discovery.sh" ]]; then
7661
+ local _disc_cat _disc_patterns _disc_text
7662
+ _disc_cat="$id"
7663
+ case "$id" in
7664
+ plan) _disc_patterns="*.md"; _disc_text="Plan completed: ${GOAL:-goal}" ;;
7665
+ design) _disc_patterns="*.md,*.ts,*.tsx,*.js"; _disc_text="Design completed for ${GOAL:-goal}" ;;
7666
+ build) _disc_patterns="src/*,*.ts,*.tsx,*.js"; _disc_text="Build completed" ;;
7667
+ test) _disc_patterns="*.test.*,*_test.*"; _disc_text="Tests passed" ;;
7668
+ review) _disc_patterns="*.md,*.ts,*.tsx"; _disc_text="Review completed" ;;
7669
+ *) _disc_patterns="*"; _disc_text="Stage $id completed" ;;
7670
+ esac
7671
+ bash "$SCRIPT_DIR/sw-discovery.sh" broadcast "$_disc_cat" "$_disc_patterns" "$_disc_text" "" 2>/dev/null || true
7672
+ fi
7434
7673
  # Log model used for prediction feedback
7435
7674
  echo "${id}|${stage_model_used}|true" >> "${ARTIFACTS_DIR}/model-routing.log"
7436
7675
  else
@@ -7613,8 +7852,14 @@ pipeline_cleanup_worktree() {
7613
7852
 
7614
7853
  if [[ -n "${ORIGINAL_REPO_DIR:-}" && "$worktree_path" != "$ORIGINAL_REPO_DIR" ]]; then
7615
7854
  cd "$ORIGINAL_REPO_DIR" 2>/dev/null || cd /
7616
- info "Cleaning up worktree: ${DIM}${worktree_path}${RESET}"
7617
- git worktree remove --force "$worktree_path" 2>/dev/null || true
7855
+ # Only clean up worktree on success — preserve on failure for inspection
7856
+ if [[ "${PIPELINE_EXIT_CODE:-1}" -eq 0 ]]; then
7857
+ info "Cleaning up worktree: ${DIM}${worktree_path}${RESET}"
7858
+ git worktree remove --force "$worktree_path" 2>/dev/null || true
7859
+ else
7860
+ warn "Pipeline failed — worktree preserved for inspection: ${DIM}${worktree_path}${RESET}"
7861
+ warn "Clean up manually: ${DIM}git worktree remove --force ${worktree_path}${RESET}"
7862
+ fi
7618
7863
  fi
7619
7864
  }
7620
7865
 
@@ -7787,6 +8032,24 @@ run_dry_run() {
7787
8032
  # ─── Subcommands ────────────────────────────────────────────────────────────
7788
8033
 
7789
8034
  pipeline_start() {
8035
+ # Handle --repo flag: change to directory before running
8036
+ if [[ -n "$REPO_OVERRIDE" ]]; then
8037
+ if [[ ! -d "$REPO_OVERRIDE" ]]; then
8038
+ error "Directory does not exist: $REPO_OVERRIDE"
8039
+ exit 1
8040
+ fi
8041
+ if ! cd "$REPO_OVERRIDE" 2>/dev/null; then
8042
+ error "Cannot cd to: $REPO_OVERRIDE"
8043
+ exit 1
8044
+ fi
8045
+ if ! git rev-parse --show-toplevel >/dev/null 2>&1; then
8046
+ error "Not a git repository: $REPO_OVERRIDE"
8047
+ exit 1
8048
+ fi
8049
+ ORIGINAL_REPO_DIR="$(pwd)"
8050
+ info "Using repository: $ORIGINAL_REPO_DIR"
8051
+ fi
8052
+
7790
8053
  if [[ -z "$GOAL" && -z "$ISSUE_NUMBER" ]]; then
7791
8054
  error "Must provide --goal or --issue"
7792
8055
  echo -e " Example: ${DIM}shipwright pipeline start --goal \"Add JWT auth\"${RESET}"
@@ -7879,7 +8142,9 @@ pipeline_start() {
7879
8142
 
7880
8143
  local gate_count
7881
8144
  gate_count=$(jq '[.stages[] | select(.gate == "approve" and .enabled == true)] | length' "$PIPELINE_CONFIG")
7882
- if [[ "$SKIP_GATES" == "true" ]]; then
8145
+ if [[ "$HEADLESS" == "true" ]]; then
8146
+ echo -e " ${BOLD}Gates:${RESET} ${YELLOW}all auto (headless — non-interactive stdin detected)${RESET}"
8147
+ elif [[ "$SKIP_GATES" == "true" ]]; then
7883
8148
  echo -e " ${BOLD}Gates:${RESET} ${YELLOW}all auto (--skip-gates)${RESET}"
7884
8149
  else
7885
8150
  echo -e " ${BOLD}Gates:${RESET} ${gate_count} approval gate(s)"
@@ -7931,6 +8196,7 @@ pipeline_start() {
7931
8196
 
7932
8197
  run_pipeline
7933
8198
  local exit_code=$?
8199
+ PIPELINE_EXIT_CODE="$exit_code"
7934
8200
 
7935
8201
  # Send completion notification + event
7936
8202
  local total_dur_s=""
@@ -7946,9 +8212,15 @@ pipeline_start() {
7946
8212
  "result=success" \
7947
8213
  "duration_s=${total_dur_s:-0}" \
7948
8214
  "pr_url=${pr_url:-}" \
8215
+ "agent_id=${PIPELINE_AGENT_ID}" \
7949
8216
  "input_tokens=$TOTAL_INPUT_TOKENS" \
7950
8217
  "output_tokens=$TOTAL_OUTPUT_TOKENS" \
7951
8218
  "self_heal_count=$SELF_HEAL_COUNT"
8219
+
8220
+ # Auto-ingest pipeline outcome into recruit profiles
8221
+ if [[ -x "$SCRIPT_DIR/sw-recruit.sh" ]]; then
8222
+ bash "$SCRIPT_DIR/sw-recruit.sh" ingest-pipeline 1 2>/dev/null || true
8223
+ fi
7952
8224
  else
7953
8225
  notify "Pipeline Failed" "Goal: ${GOAL}\nFailed at: ${CURRENT_STAGE_ID:-unknown}" "error"
7954
8226
  emit_event "pipeline.completed" \
@@ -7956,10 +8228,16 @@ pipeline_start() {
7956
8228
  "result=failure" \
7957
8229
  "duration_s=${total_dur_s:-0}" \
7958
8230
  "failed_stage=${CURRENT_STAGE_ID:-unknown}" \
8231
+ "agent_id=${PIPELINE_AGENT_ID}" \
7959
8232
  "input_tokens=$TOTAL_INPUT_TOKENS" \
7960
8233
  "output_tokens=$TOTAL_OUTPUT_TOKENS" \
7961
8234
  "self_heal_count=$SELF_HEAL_COUNT"
7962
8235
 
8236
+ # Auto-ingest pipeline outcome into recruit profiles
8237
+ if [[ -x "$SCRIPT_DIR/sw-recruit.sh" ]]; then
8238
+ bash "$SCRIPT_DIR/sw-recruit.sh" ingest-pipeline 1 2>/dev/null || true
8239
+ fi
8240
+
7963
8241
  # Capture failure learnings to memory
7964
8242
  if [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
7965
8243
  bash "$SCRIPT_DIR/sw-memory.sh" capture "$STATE_FILE" "$ARTIFACTS_DIR" 2>/dev/null || true
@@ -8015,18 +8293,24 @@ pipeline_start() {
8015
8293
  memory_finalize_pipeline "$STATE_FILE" "$ARTIFACTS_DIR" 2>/dev/null || true
8016
8294
  fi
8017
8295
 
8018
- # Emit cost event
8296
+ # Emit cost event — prefer actual cost from Claude CLI when available
8019
8297
  local model_key="${MODEL:-sonnet}"
8020
- local input_cost output_cost total_cost
8021
- input_cost=$(awk -v tokens="$TOTAL_INPUT_TOKENS" -v rate="$(echo "$COST_MODEL_RATES" | jq -r ".${model_key}.input // 3")" 'BEGIN{printf "%.4f", (tokens / 1000000) * rate}')
8022
- output_cost=$(awk -v tokens="$TOTAL_OUTPUT_TOKENS" -v rate="$(echo "$COST_MODEL_RATES" | jq -r ".${model_key}.output // 15")" 'BEGIN{printf "%.4f", (tokens / 1000000) * rate}')
8023
- total_cost=$(awk -v i="$input_cost" -v o="$output_cost" 'BEGIN{printf "%.4f", i + o}')
8298
+ local total_cost
8299
+ if [[ -n "${TOTAL_COST_USD:-}" && "${TOTAL_COST_USD}" != "0" && "${TOTAL_COST_USD}" != "null" ]]; then
8300
+ total_cost="${TOTAL_COST_USD}"
8301
+ else
8302
+ # Fallback: estimate from token counts and model rates
8303
+ local input_cost output_cost
8304
+ input_cost=$(awk -v tokens="$TOTAL_INPUT_TOKENS" -v rate="$(echo "$COST_MODEL_RATES" | jq -r ".${model_key}.input // 3")" 'BEGIN{printf "%.4f", (tokens / 1000000) * rate}')
8305
+ output_cost=$(awk -v tokens="$TOTAL_OUTPUT_TOKENS" -v rate="$(echo "$COST_MODEL_RATES" | jq -r ".${model_key}.output // 15")" 'BEGIN{printf "%.4f", (tokens / 1000000) * rate}')
8306
+ total_cost=$(awk -v i="$input_cost" -v o="$output_cost" 'BEGIN{printf "%.4f", i + o}')
8307
+ fi
8024
8308
 
8025
8309
  emit_event "pipeline.cost" \
8026
8310
  "input_tokens=$TOTAL_INPUT_TOKENS" \
8027
8311
  "output_tokens=$TOTAL_OUTPUT_TOKENS" \
8028
8312
  "model=$model_key" \
8029
- "estimated_cost_usd=$total_cost"
8313
+ "cost_usd=$total_cost"
8030
8314
 
8031
8315
  return $exit_code
8032
8316
  }