shipwright-cli 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) 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 +261 -12
  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 +1 -1
  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/templates/pipelines/autonomous.json +2 -2
  112. package/tmux/shipwright-overlay.conf +35 -17
  113. package/tmux/tmux.conf +23 -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.0"
15
15
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
16
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
17
17
 
@@ -202,6 +202,7 @@ 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
206
207
  GIT_BRANCH=""
207
208
  GITHUB_ISSUE=""
@@ -223,6 +224,7 @@ AUTO_WORKTREE=false
223
224
  WORKTREE_NAME=""
224
225
  CLEANUP_WORKTREE=false
225
226
  ORIGINAL_REPO_DIR=""
227
+ REPO_OVERRIDE=""
226
228
  _cleanup_done=""
227
229
 
228
230
  # GitHub metadata (populated during intake)
@@ -264,6 +266,8 @@ show_help() {
264
266
  echo -e "${BOLD}START OPTIONS${RESET}"
265
267
  echo -e " ${DIM}--goal \"description\"${RESET} What to build (required unless --issue)"
266
268
  echo -e " ${DIM}--issue <number>${RESET} Fetch goal from GitHub issue"
269
+ echo -e " ${DIM}--repo <path>${RESET} Change to directory before running (must be a git repo)"
270
+ echo -e " ${DIM}--local${RESET} Alias for --no-github --no-github-label (local-only mode)"
267
271
  echo -e " ${DIM}--pipeline <name>${RESET} Pipeline template (default: standard)"
268
272
  echo -e " ${DIM}--test-cmd \"command\"${RESET} Override test command (auto-detected if omitted)"
269
273
  echo -e " ${DIM}--model <model>${RESET} Override AI model (opus, sonnet, haiku)"
@@ -346,6 +350,8 @@ parse_args() {
346
350
  case "$1" in
347
351
  --goal) GOAL="$2"; shift 2 ;;
348
352
  --issue) ISSUE_NUMBER="$2"; shift 2 ;;
353
+ --repo) REPO_OVERRIDE="$2"; shift 2 ;;
354
+ --local) NO_GITHUB=true; NO_GITHUB_LABEL=true; shift ;;
349
355
  --pipeline|--template) PIPELINE_NAME="$2"; shift 2 ;;
350
356
  --test-cmd) TEST_CMD="$2"; shift 2 ;;
351
357
  --model) MODEL="$2"; shift 2 ;;
@@ -396,6 +402,7 @@ setup_dirs() {
396
402
  ARTIFACTS_DIR="$STATE_DIR/pipeline-artifacts"
397
403
  TASKS_FILE="$STATE_DIR/pipeline-tasks.md"
398
404
  mkdir -p "$STATE_DIR" "$ARTIFACTS_DIR"
405
+ export SHIPWRIGHT_PIPELINE_ID="pipeline-$$-${ISSUE_NUMBER:-0}"
399
406
  }
400
407
 
401
408
  # ─── Pipeline Config Loading ───────────────────────────────────────────────
@@ -1155,6 +1162,21 @@ get_stage_timing() {
1155
1162
  fi
1156
1163
  }
1157
1164
 
1165
+ # Raw seconds for a stage (for memory baseline updates)
1166
+ get_stage_timing_seconds() {
1167
+ local stage_id="$1"
1168
+ local start_e end_e
1169
+ start_e=$(echo "$STAGE_TIMINGS" | grep "^${stage_id}_start:" | cut -d: -f2 | tail -1 || true)
1170
+ end_e=$(echo "$STAGE_TIMINGS" | grep "^${stage_id}_end:" | cut -d: -f2 | tail -1 || true)
1171
+ if [[ -n "$start_e" && -n "$end_e" ]]; then
1172
+ echo $(( end_e - start_e ))
1173
+ elif [[ -n "$start_e" ]]; then
1174
+ echo $(( $(now_epoch) - start_e ))
1175
+ else
1176
+ echo "0"
1177
+ fi
1178
+ }
1179
+
1158
1180
  get_stage_description() {
1159
1181
  local stage_id="$1"
1160
1182
 
@@ -1250,6 +1272,22 @@ mark_stage_complete() {
1250
1272
  log_stage "$stage_id" "complete (${timing})"
1251
1273
  write_state
1252
1274
 
1275
+ record_stage_effectiveness "$stage_id" "complete"
1276
+ # Update memory baselines and predictive baselines for stage durations
1277
+ if [[ "$stage_id" == "test" || "$stage_id" == "build" ]]; then
1278
+ local secs
1279
+ secs=$(get_stage_timing_seconds "$stage_id")
1280
+ if [[ -n "$secs" && "$secs" != "0" ]]; then
1281
+ [[ -x "$SCRIPT_DIR/sw-memory.sh" ]] && bash "$SCRIPT_DIR/sw-memory.sh" metric "${stage_id}_duration_s" "$secs" 2>/dev/null || true
1282
+ if [[ -x "$SCRIPT_DIR/sw-predictive.sh" ]]; then
1283
+ local anomaly_sev
1284
+ anomaly_sev=$(bash "$SCRIPT_DIR/sw-predictive.sh" anomaly "$stage_id" "duration_s" "$secs" 2>/dev/null || echo "normal")
1285
+ [[ "$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
1286
+ bash "$SCRIPT_DIR/sw-predictive.sh" baseline "$stage_id" "duration_s" "$secs" 2>/dev/null || true
1287
+ fi
1288
+ fi
1289
+ fi
1290
+
1253
1291
  # Update GitHub progress comment
1254
1292
  if [[ -n "$ISSUE_NUMBER" ]]; then
1255
1293
  local body
@@ -1346,9 +1384,40 @@ verify_stage_artifacts() {
1346
1384
  return "$missing"
1347
1385
  }
1348
1386
 
1387
+ # Self-aware pipeline: record stage effectiveness for meta-cognition
1388
+ STAGE_EFFECTIVENESS_FILE="${HOME}/.shipwright/stage-effectiveness.jsonl"
1389
+ record_stage_effectiveness() {
1390
+ local stage_id="$1" outcome="${2:-failed}"
1391
+ mkdir -p "${HOME}/.shipwright"
1392
+ echo "{\"stage\":\"$stage_id\",\"outcome\":\"$outcome\",\"ts\":\"$(now_iso)\"}" >> "${STAGE_EFFECTIVENESS_FILE}"
1393
+ # Keep last 100 entries
1394
+ tail -100 "${STAGE_EFFECTIVENESS_FILE}" > "${STAGE_EFFECTIVENESS_FILE}.tmp" 2>/dev/null && mv "${STAGE_EFFECTIVENESS_FILE}.tmp" "${STAGE_EFFECTIVENESS_FILE}" 2>/dev/null || true
1395
+ }
1396
+ get_stage_self_awareness_hint() {
1397
+ local stage_id="$1"
1398
+ [[ ! -f "$STAGE_EFFECTIVENESS_FILE" ]] && return 0
1399
+ local recent
1400
+ recent=$(grep "\"stage\":\"$stage_id\"" "$STAGE_EFFECTIVENESS_FILE" 2>/dev/null | tail -10 || true)
1401
+ [[ -z "$recent" ]] && return 0
1402
+ local failures=0 total=0
1403
+ while IFS= read -r line; do
1404
+ [[ -z "$line" ]] && continue
1405
+ total=$((total + 1))
1406
+ echo "$line" | grep -q '"outcome":"failed"' && failures=$((failures + 1)) || true
1407
+ done <<< "$recent"
1408
+ if [[ "$total" -ge 3 ]] && [[ $((failures * 100 / total)) -ge 50 ]]; then
1409
+ case "$stage_id" in
1410
+ plan) echo "Recent plan stage failures: consider adding more context or breaking the goal into smaller steps." ;;
1411
+ build) echo "Recent build stage failures: consider adding test expectations or simplifying the change." ;;
1412
+ *) echo "Recent $stage_id failures: review past logs and adjust approach." ;;
1413
+ esac
1414
+ fi
1415
+ }
1416
+
1349
1417
  mark_stage_failed() {
1350
1418
  local stage_id="$1"
1351
1419
  record_stage_end "$stage_id"
1420
+ record_stage_effectiveness "$stage_id" "failed"
1352
1421
  set_stage_status "$stage_id" "failed"
1353
1422
  local timing
1354
1423
  timing=$(get_stage_timing "$stage_id")
@@ -1779,6 +1848,28 @@ ${memory_summary}
1779
1848
  fi
1780
1849
  fi
1781
1850
 
1851
+ # Self-aware pipeline: inject hint when plan stage has been failing recently
1852
+ local plan_hint
1853
+ plan_hint=$(get_stage_self_awareness_hint "plan" 2>/dev/null || true)
1854
+ if [[ -n "$plan_hint" ]]; then
1855
+ plan_prompt="${plan_prompt}
1856
+ ## Self-Assessment (recent plan stage performance)
1857
+ ${plan_hint}
1858
+ "
1859
+ fi
1860
+
1861
+ # Inject cross-pipeline discoveries (from other concurrent/similar pipelines)
1862
+ if [[ -x "$SCRIPT_DIR/sw-discovery.sh" ]]; then
1863
+ local plan_discoveries
1864
+ plan_discoveries=$("$SCRIPT_DIR/sw-discovery.sh" inject "*.md,*.json" 2>/dev/null | head -20 || true)
1865
+ if [[ -n "$plan_discoveries" ]]; then
1866
+ plan_prompt="${plan_prompt}
1867
+ ## Discoveries from Other Pipelines
1868
+ ${plan_discoveries}
1869
+ "
1870
+ fi
1871
+ fi
1872
+
1782
1873
  # Inject architecture patterns from intelligence layer
1783
1874
  local repo_hash_plan
1784
1875
  repo_hash_plan=$(echo -n "$PROJECT_ROOT" | shasum -a 256 2>/dev/null | cut -c1-12 || echo "unknown")
@@ -2151,6 +2242,12 @@ stage_design() {
2151
2242
  memory_context=$(bash "$SCRIPT_DIR/sw-memory.sh" inject "design" 2>/dev/null) || true
2152
2243
  fi
2153
2244
 
2245
+ # Inject cross-pipeline discoveries for design stage
2246
+ local design_discoveries=""
2247
+ if [[ -x "$SCRIPT_DIR/sw-discovery.sh" ]]; then
2248
+ design_discoveries=$("$SCRIPT_DIR/sw-discovery.sh" inject "*.md,*.ts,*.tsx,*.js" 2>/dev/null | head -20 || true)
2249
+ fi
2250
+
2154
2251
  # Inject architecture model patterns if available
2155
2252
  local arch_context=""
2156
2253
  local repo_hash
@@ -2210,7 +2307,10 @@ ${memory_context}
2210
2307
  }${arch_context:+
2211
2308
  ## Architecture Model (from previous designs)
2212
2309
  ${arch_context}
2213
- }${design_antipatterns}
2310
+ }${design_antipatterns}${design_discoveries:+
2311
+ ## Discoveries from Other Pipelines
2312
+ ${design_discoveries}
2313
+ }
2214
2314
  ## Required Output — Architecture Decision Record
2215
2315
 
2216
2316
  Produce this EXACT format:
@@ -2334,6 +2434,18 @@ Historical context (lessons from previous pipelines):
2334
2434
  ${memory_context}"
2335
2435
  fi
2336
2436
 
2437
+ # Inject cross-pipeline discoveries for build stage
2438
+ if [[ -x "$SCRIPT_DIR/sw-discovery.sh" ]]; then
2439
+ local build_discoveries
2440
+ build_discoveries=$("$SCRIPT_DIR/sw-discovery.sh" inject "src/*,*.ts,*.tsx,*.js" 2>/dev/null | head -20 || true)
2441
+ if [[ -n "$build_discoveries" ]]; then
2442
+ enriched_goal="${enriched_goal}
2443
+
2444
+ Discoveries from other pipelines:
2445
+ ${build_discoveries}"
2446
+ fi
2447
+ fi
2448
+
2337
2449
  # Add task list context
2338
2450
  if [[ -s "$TASKS_FILE" ]]; then
2339
2451
  enriched_goal="${enriched_goal}
@@ -2380,6 +2492,19 @@ Coverage baseline: ${coverage_baseline}% — do not decrease coverage."
2380
2492
  fi
2381
2493
  fi
2382
2494
 
2495
+ # Predictive: inject prevention hints when risk/memory patterns suggest build-stage failures
2496
+ if [[ -x "$SCRIPT_DIR/sw-predictive.sh" ]]; then
2497
+ local issue_json_build="{}"
2498
+ [[ -n "${ISSUE_NUMBER:-}" ]] && issue_json_build=$(jq -n --arg title "${GOAL:-}" --arg num "${ISSUE_NUMBER:-}" '{title: $title, number: $num}')
2499
+ local prevention_text
2500
+ prevention_text=$(bash "$SCRIPT_DIR/sw-predictive.sh" inject-prevention "build" "$issue_json_build" 2>/dev/null || true)
2501
+ if [[ -n "$prevention_text" ]]; then
2502
+ enriched_goal="${enriched_goal}
2503
+
2504
+ ${prevention_text}"
2505
+ fi
2506
+ fi
2507
+
2383
2508
  loop_args+=("$enriched_goal")
2384
2509
 
2385
2510
  # Build loop args from pipeline config + CLI overrides
@@ -2432,6 +2557,23 @@ Coverage baseline: ${coverage_baseline}% — do not decrease coverage."
2432
2557
  build_model="$CLAUDE_MODEL"
2433
2558
  fi
2434
2559
 
2560
+ # Recruit-powered model selection (when no explicit override)
2561
+ if [[ -z "$MODEL" ]] && [[ -x "$SCRIPT_DIR/sw-recruit.sh" ]]; then
2562
+ local _recruit_goal="${GOAL:-}"
2563
+ if [[ -n "$_recruit_goal" ]]; then
2564
+ local _recruit_match
2565
+ _recruit_match=$(bash "$SCRIPT_DIR/sw-recruit.sh" match --json "$_recruit_goal" 2>/dev/null) || true
2566
+ if [[ -n "$_recruit_match" ]]; then
2567
+ local _recruit_model
2568
+ _recruit_model=$(echo "$_recruit_match" | jq -r '.model // ""' 2>/dev/null) || true
2569
+ if [[ -n "$_recruit_model" && "$_recruit_model" != "null" && "$_recruit_model" != "" ]]; then
2570
+ info "Recruit recommends model: ${CYAN}${_recruit_model}${RESET} for this task"
2571
+ build_model="$_recruit_model"
2572
+ fi
2573
+ fi
2574
+ fi
2575
+ fi
2576
+
2435
2577
  [[ -n "$test_cmd" && "$test_cmd" != "null" ]] && loop_args+=(--test-cmd "$test_cmd")
2436
2578
  loop_args+=(--max-iterations "$max_iter")
2437
2579
  loop_args+=(--model "$build_model")
@@ -2488,6 +2630,23 @@ Coverage baseline: ${coverage_baseline}% — do not decrease coverage."
2488
2630
  }
2489
2631
  parse_claude_tokens "$_token_log"
2490
2632
 
2633
+ # Read accumulated token counts from build loop (written by sw-loop.sh)
2634
+ local _loop_token_file="${PROJECT_ROOT}/.claude/loop-logs/loop-tokens.json"
2635
+ if [[ -f "$_loop_token_file" ]] && command -v jq &>/dev/null; then
2636
+ local _loop_in _loop_out _loop_cost
2637
+ _loop_in=$(jq -r '.input_tokens // 0' "$_loop_token_file" 2>/dev/null || echo "0")
2638
+ _loop_out=$(jq -r '.output_tokens // 0' "$_loop_token_file" 2>/dev/null || echo "0")
2639
+ _loop_cost=$(jq -r '.cost_usd // 0' "$_loop_token_file" 2>/dev/null || echo "0")
2640
+ TOTAL_INPUT_TOKENS=$(( TOTAL_INPUT_TOKENS + ${_loop_in:-0} ))
2641
+ TOTAL_OUTPUT_TOKENS=$(( TOTAL_OUTPUT_TOKENS + ${_loop_out:-0} ))
2642
+ if [[ -n "$_loop_cost" && "$_loop_cost" != "0" && "$_loop_cost" != "null" ]]; then
2643
+ TOTAL_COST_USD="${_loop_cost}"
2644
+ fi
2645
+ if [[ "${_loop_in:-0}" -gt 0 || "${_loop_out:-0}" -gt 0 ]]; then
2646
+ info "Build loop tokens: in=${_loop_in} out=${_loop_out} cost=\$${_loop_cost:-0}"
2647
+ fi
2648
+ fi
2649
+
2491
2650
  # Count commits made during build
2492
2651
  local commit_count
2493
2652
  commit_count=$(git log --oneline "${BASE_BRANCH}..HEAD" 2>/dev/null | wc -l | xargs)
@@ -2792,6 +2951,22 @@ $(cat "$diff_file")"
2792
2951
  success "Review clean"
2793
2952
  fi
2794
2953
 
2954
+ # ── Oversight gate: pipeline review/quality stages block on verdict ──
2955
+ if [[ -x "$SCRIPT_DIR/sw-oversight.sh" ]] && [[ "${SKIP_GATES:-false}" != "true" ]]; then
2956
+ local reject_reason=""
2957
+ local _sec_count
2958
+ _sec_count=$(grep -ciE '\*\*\[?Security\]?\*\*' "$review_file" 2>/dev/null || true)
2959
+ _sec_count="${_sec_count:-0}"
2960
+ local _blocking=$((critical_count + _sec_count))
2961
+ [[ "$_blocking" -gt 0 ]] && reject_reason="Review found ${_blocking} critical/security issue(s)"
2962
+ if ! bash "$SCRIPT_DIR/sw-oversight.sh" gate --diff "$diff_file" --description "${GOAL:-Pipeline review}" --reject-if "$reject_reason" >/dev/null 2>&1; then
2963
+ error "Oversight gate rejected — blocking pipeline"
2964
+ emit_event "review.oversight_blocked" "issue=${ISSUE_NUMBER:-0}"
2965
+ log_stage "review" "BLOCKED: oversight gate rejected"
2966
+ return 1
2967
+ fi
2968
+ fi
2969
+
2795
2970
  # ── Review Blocking Gate ──
2796
2971
  # Block pipeline on critical/security issues unless compound_quality handles them
2797
2972
  local security_count
@@ -3771,6 +3946,8 @@ stage_monitor() {
3771
3946
  fi
3772
3947
 
3773
3948
  local report_file="$ARTIFACTS_DIR/monitor-report.md"
3949
+ local deploy_log_file="$ARTIFACTS_DIR/deploy-logs.txt"
3950
+ : > "$deploy_log_file"
3774
3951
  local total_errors=0
3775
3952
  local poll_interval=30 # seconds between polls
3776
3953
  local total_polls=$(( (duration_minutes * 60) / poll_interval ))
@@ -3818,10 +3995,11 @@ stage_monitor() {
3818
3995
  fi
3819
3996
  fi
3820
3997
 
3821
- # Log command check
3998
+ # Log command check (accumulate deploy logs for feedback collect)
3822
3999
  if [[ -n "$log_cmd" ]]; then
3823
4000
  local log_output
3824
4001
  log_output=$(bash -c "$log_cmd" 2>/dev/null || true)
4002
+ [[ -n "$log_output" ]] && echo "$log_output" >> "$deploy_log_file"
3825
4003
  local error_count=0
3826
4004
  if [[ -n "$log_output" ]]; then
3827
4005
  error_count=$(echo "$log_output" | grep -cE "$log_pattern" 2>/dev/null || true)
@@ -3856,13 +4034,24 @@ stage_monitor() {
3856
4034
  "total_errors=$total_errors" \
3857
4035
  "threshold=$error_threshold"
3858
4036
 
3859
- # Auto-rollback if configured
3860
- if [[ "$auto_rollback" == "true" && -n "$rollback_cmd" ]]; then
4037
+ # Feedback loop: collect deploy logs and optionally create issue
4038
+ if [[ -f "$deploy_log_file" ]] && [[ -s "$deploy_log_file" ]] && [[ -x "$SCRIPT_DIR/sw-feedback.sh" ]]; then
4039
+ (cd "$PROJECT_ROOT" && ARTIFACTS_DIR="$ARTIFACTS_DIR" bash "$SCRIPT_DIR/sw-feedback.sh" collect "$deploy_log_file" 2>/dev/null) || true
4040
+ (cd "$PROJECT_ROOT" && ARTIFACTS_DIR="$ARTIFACTS_DIR" bash "$SCRIPT_DIR/sw-feedback.sh" create-issue 2>/dev/null) || true
4041
+ fi
4042
+
4043
+ # Auto-rollback: feedback rollback (GitHub Deployments API) and/or config rollback_cmd
4044
+ if [[ "$auto_rollback" == "true" ]]; then
3861
4045
  warn "Auto-rolling back..."
3862
4046
  echo "" >> "$report_file"
3863
4047
  echo "## Rollback" >> "$report_file"
3864
4048
 
3865
- if bash -c "$rollback_cmd" >> "$report_file" 2>&1; then
4049
+ # Trigger feedback rollback (calls sw-github-deploy.sh rollback)
4050
+ if [[ -x "$SCRIPT_DIR/sw-feedback.sh" ]]; then
4051
+ (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
4052
+ fi
4053
+
4054
+ if [[ -n "$rollback_cmd" ]] && bash -c "$rollback_cmd" >> "$report_file" 2>&1; then
3866
4055
  success "Rollback executed"
3867
4056
  echo "Rollback: ✅ success" >> "$report_file"
3868
4057
 
@@ -7076,6 +7265,12 @@ Focus on fixing the failing tests while keeping all passing tests working."
7076
7265
  _snap_error="${_snap_error:-}"
7077
7266
  pipeline_emit_progress_snapshot "${ISSUE_NUMBER}" "${CURRENT_STAGE_ID:-test}" "${cycle:-0}" "${_diff_count:-0}" "${_snap_files}" "${_snap_error}" 2>/dev/null || true
7078
7267
  fi
7268
+ # Record fix outcome when tests pass after a retry with memory injection (pipeline path)
7269
+ if [[ "$cycle" -gt 1 && -n "${last_test_error:-}" ]] && [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
7270
+ local _sig
7271
+ _sig=$(echo "$last_test_error" | head -3 | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
7272
+ [[ -n "$_sig" ]] && bash "$SCRIPT_DIR/sw-memory.sh" fix-outcome "$_sig" "true" "true" 2>/dev/null || true
7273
+ fi
7079
7274
  return 0 # Tests passed!
7080
7275
  fi
7081
7276
 
@@ -7426,11 +7621,29 @@ run_pipeline() {
7426
7621
  if run_stage_with_retry "$id"; then
7427
7622
  mark_stage_complete "$id"
7428
7623
  completed=$((completed + 1))
7624
+ # Capture project pattern after intake (for memory context in later stages)
7625
+ if [[ "$id" == "intake" ]] && [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
7626
+ (cd "$REPO_DIR" && bash "$SCRIPT_DIR/sw-memory.sh" pattern "project" "{}" 2>/dev/null) || true
7627
+ fi
7429
7628
  local timing stage_dur_s
7430
7629
  timing=$(get_stage_timing "$id")
7431
7630
  stage_dur_s=$(( $(now_epoch) - stage_start_epoch ))
7432
7631
  success "Stage ${BOLD}$id${RESET} complete ${DIM}(${timing})${RESET}"
7433
7632
  emit_event "stage.completed" "issue=${ISSUE_NUMBER:-0}" "stage=$id" "duration_s=$stage_dur_s"
7633
+ # Broadcast discovery for cross-pipeline learning
7634
+ if [[ -x "$SCRIPT_DIR/sw-discovery.sh" ]]; then
7635
+ local _disc_cat _disc_patterns _disc_text
7636
+ _disc_cat="$id"
7637
+ case "$id" in
7638
+ plan) _disc_patterns="*.md"; _disc_text="Plan completed: ${GOAL:-goal}" ;;
7639
+ design) _disc_patterns="*.md,*.ts,*.tsx,*.js"; _disc_text="Design completed for ${GOAL:-goal}" ;;
7640
+ build) _disc_patterns="src/*,*.ts,*.tsx,*.js"; _disc_text="Build completed" ;;
7641
+ test) _disc_patterns="*.test.*,*_test.*"; _disc_text="Tests passed" ;;
7642
+ review) _disc_patterns="*.md,*.ts,*.tsx"; _disc_text="Review completed" ;;
7643
+ *) _disc_patterns="*"; _disc_text="Stage $id completed" ;;
7644
+ esac
7645
+ bash "$SCRIPT_DIR/sw-discovery.sh" broadcast "$_disc_cat" "$_disc_patterns" "$_disc_text" "" 2>/dev/null || true
7646
+ fi
7434
7647
  # Log model used for prediction feedback
7435
7648
  echo "${id}|${stage_model_used}|true" >> "${ARTIFACTS_DIR}/model-routing.log"
7436
7649
  else
@@ -7787,6 +8000,24 @@ run_dry_run() {
7787
8000
  # ─── Subcommands ────────────────────────────────────────────────────────────
7788
8001
 
7789
8002
  pipeline_start() {
8003
+ # Handle --repo flag: change to directory before running
8004
+ if [[ -n "$REPO_OVERRIDE" ]]; then
8005
+ if [[ ! -d "$REPO_OVERRIDE" ]]; then
8006
+ error "Directory does not exist: $REPO_OVERRIDE"
8007
+ exit 1
8008
+ fi
8009
+ if ! cd "$REPO_OVERRIDE" 2>/dev/null; then
8010
+ error "Cannot cd to: $REPO_OVERRIDE"
8011
+ exit 1
8012
+ fi
8013
+ if ! git rev-parse --show-toplevel >/dev/null 2>&1; then
8014
+ error "Not a git repository: $REPO_OVERRIDE"
8015
+ exit 1
8016
+ fi
8017
+ ORIGINAL_REPO_DIR="$(pwd)"
8018
+ info "Using repository: $ORIGINAL_REPO_DIR"
8019
+ fi
8020
+
7790
8021
  if [[ -z "$GOAL" && -z "$ISSUE_NUMBER" ]]; then
7791
8022
  error "Must provide --goal or --issue"
7792
8023
  echo -e " Example: ${DIM}shipwright pipeline start --goal \"Add JWT auth\"${RESET}"
@@ -7946,9 +8177,15 @@ pipeline_start() {
7946
8177
  "result=success" \
7947
8178
  "duration_s=${total_dur_s:-0}" \
7948
8179
  "pr_url=${pr_url:-}" \
8180
+ "agent_id=${PIPELINE_AGENT_ID}" \
7949
8181
  "input_tokens=$TOTAL_INPUT_TOKENS" \
7950
8182
  "output_tokens=$TOTAL_OUTPUT_TOKENS" \
7951
8183
  "self_heal_count=$SELF_HEAL_COUNT"
8184
+
8185
+ # Auto-ingest pipeline outcome into recruit profiles
8186
+ if [[ -x "$SCRIPT_DIR/sw-recruit.sh" ]]; then
8187
+ bash "$SCRIPT_DIR/sw-recruit.sh" ingest-pipeline 1 2>/dev/null || true
8188
+ fi
7952
8189
  else
7953
8190
  notify "Pipeline Failed" "Goal: ${GOAL}\nFailed at: ${CURRENT_STAGE_ID:-unknown}" "error"
7954
8191
  emit_event "pipeline.completed" \
@@ -7956,10 +8193,16 @@ pipeline_start() {
7956
8193
  "result=failure" \
7957
8194
  "duration_s=${total_dur_s:-0}" \
7958
8195
  "failed_stage=${CURRENT_STAGE_ID:-unknown}" \
8196
+ "agent_id=${PIPELINE_AGENT_ID}" \
7959
8197
  "input_tokens=$TOTAL_INPUT_TOKENS" \
7960
8198
  "output_tokens=$TOTAL_OUTPUT_TOKENS" \
7961
8199
  "self_heal_count=$SELF_HEAL_COUNT"
7962
8200
 
8201
+ # Auto-ingest pipeline outcome into recruit profiles
8202
+ if [[ -x "$SCRIPT_DIR/sw-recruit.sh" ]]; then
8203
+ bash "$SCRIPT_DIR/sw-recruit.sh" ingest-pipeline 1 2>/dev/null || true
8204
+ fi
8205
+
7963
8206
  # Capture failure learnings to memory
7964
8207
  if [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
7965
8208
  bash "$SCRIPT_DIR/sw-memory.sh" capture "$STATE_FILE" "$ARTIFACTS_DIR" 2>/dev/null || true
@@ -8015,18 +8258,24 @@ pipeline_start() {
8015
8258
  memory_finalize_pipeline "$STATE_FILE" "$ARTIFACTS_DIR" 2>/dev/null || true
8016
8259
  fi
8017
8260
 
8018
- # Emit cost event
8261
+ # Emit cost event — prefer actual cost from Claude CLI when available
8019
8262
  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}')
8263
+ local total_cost
8264
+ if [[ -n "${TOTAL_COST_USD:-}" && "${TOTAL_COST_USD}" != "0" && "${TOTAL_COST_USD}" != "null" ]]; then
8265
+ total_cost="${TOTAL_COST_USD}"
8266
+ else
8267
+ # Fallback: estimate from token counts and model rates
8268
+ local input_cost output_cost
8269
+ 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}')
8270
+ 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}')
8271
+ total_cost=$(awk -v i="$input_cost" -v o="$output_cost" 'BEGIN{printf "%.4f", i + o}')
8272
+ fi
8024
8273
 
8025
8274
  emit_event "pipeline.cost" \
8026
8275
  "input_tokens=$TOTAL_INPUT_TOKENS" \
8027
8276
  "output_tokens=$TOTAL_OUTPUT_TOKENS" \
8028
8277
  "model=$model_key" \
8029
- "estimated_cost_usd=$total_cost"
8278
+ "cost_usd=$total_cost"
8030
8279
 
8031
8280
  return $exit_code
8032
8281
  }
package/scripts/sw-pm.sh CHANGED
@@ -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.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
@@ -223,9 +223,57 @@ analyze_issue() {
223
223
 
224
224
  # ─── recommend_team <analysis_json> ──────────────────────────────────────────
225
225
  # Based on analysis, recommend team composition
226
+ # Tries recruit's AI/heuristic team composition first, falls back to hardcoded rules.
226
227
  recommend_team() {
227
228
  local analysis="$1"
228
229
 
230
+ # ── Try recruit-powered team composition first ──
231
+ if [[ -x "${SCRIPT_DIR:-}/sw-recruit.sh" ]]; then
232
+ local issue_title
233
+ issue_title=$(echo "$analysis" | jq -r '.title // .recommendation // ""' 2>/dev/null || true)
234
+ if [[ -n "$issue_title" ]]; then
235
+ local recruit_result
236
+ recruit_result=$(bash "$SCRIPT_DIR/sw-recruit.sh" team --json "$issue_title" 2>/dev/null) || true
237
+ if [[ -n "$recruit_result" ]] && echo "$recruit_result" | jq -e '.team' &>/dev/null 2>&1; then
238
+ local recruit_roles recruit_model recruit_agents recruit_cost
239
+ recruit_roles=$(echo "$recruit_result" | jq -r '.team | join(",")')
240
+ recruit_model=$(echo "$recruit_result" | jq -r '.model // "sonnet"')
241
+ recruit_agents=$(echo "$recruit_result" | jq -r '.agents // 2')
242
+ recruit_cost=$(echo "$recruit_result" | jq -r '.estimated_cost // 0')
243
+
244
+ # Map recruit roles/model to PM output format
245
+ local max_iterations=5
246
+ local template="standard"
247
+ if [[ "$recruit_agents" -ge 4 ]]; then template="full"; max_iterations=8;
248
+ elif [[ "$recruit_agents" -le 1 ]]; then template="fast"; max_iterations=3;
249
+ fi
250
+
251
+ local team_rec
252
+ team_rec=$(jq -n \
253
+ --arg roles "$recruit_roles" \
254
+ --arg template "$template" \
255
+ --arg model "$recruit_model" \
256
+ --arg max_iter "$max_iterations" \
257
+ --arg agents "$recruit_agents" \
258
+ --arg cost "$recruit_cost" \
259
+ '{
260
+ roles: ($roles | split(",")),
261
+ template: $template,
262
+ model: $model,
263
+ max_iterations: ($max_iter | tonumber),
264
+ estimated_agents: ($agents | tonumber),
265
+ confidence_percent: 80,
266
+ risk_factors: "recruit-powered recommendation",
267
+ mitigation_strategies: "AI-optimized team composition",
268
+ source: "recruit"
269
+ }')
270
+ echo "$team_rec"
271
+ return 0
272
+ fi
273
+ fi
274
+ fi
275
+
276
+ # ── Fallback: hardcoded heuristic team composition ──
229
277
  local complexity risk is_security is_perf file_scope
230
278
  complexity=$(echo "$analysis" | jq -r '.complexity')
231
279
  risk=$(echo "$analysis" | jq -r '.risk')
@@ -442,15 +490,22 @@ cmd_orchestrate() {
442
490
  emit_event "pm.orchestrate" "issue=${issue_num}"
443
491
  }
444
492
 
445
- # ─── cmd_recommend <issue_num> ──────────────────────────────────────────────
493
+ # ─── cmd_recommend <issue_num> [--json] ──────────────────────────────────────
446
494
  cmd_recommend() {
447
- local issue_num="${1:-}"
495
+ local json_mode="false"
496
+ local issue_num=""
497
+ if [[ "${1:-}" == "--json" ]]; then
498
+ json_mode="true"
499
+ issue_num="${2:-}"
500
+ else
501
+ issue_num="${1:-}"
502
+ fi
448
503
  if [[ -z "$issue_num" ]]; then
449
- error "Usage: shipwright pm recommend <issue-num>"
504
+ error "Usage: shipwright pm recommend <issue-num> [--json]"
450
505
  return 1
451
506
  fi
452
507
 
453
- info "Generating full PM recommendation for issue #${issue_num}..."
508
+ [[ "$json_mode" != "true" ]] && info "Generating full PM recommendation for issue #${issue_num}..."
454
509
  local analysis team_rec stages
455
510
 
456
511
  analysis=$(analyze_issue "$issue_num")
@@ -472,6 +527,16 @@ cmd_recommend() {
472
527
  recommendation_timestamp: "'$(now_iso)'"
473
528
  }')
474
529
 
530
+ if [[ "$json_mode" == "true" ]]; then
531
+ echo "$recommendation"
532
+ ensure_pm_history
533
+ local tmp_hist
534
+ tmp_hist=$(mktemp)
535
+ jq --argjson rec "$recommendation" '.decisions += [$rec]' "$PM_HISTORY" > "$tmp_hist" && mv "$tmp_hist" "$PM_HISTORY"
536
+ emit_event "pm.recommend" "issue=${issue_num}"
537
+ return 0
538
+ fi
539
+
475
540
  # Pretty-print
476
541
  echo ""
477
542
  echo -e "${BOLD}PM RECOMMENDATION FOR ISSUE #${issue_num}${RESET}"
@@ -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.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -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.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -810,6 +810,13 @@ main() {
810
810
  confirm-anomaly) predictive_confirm_anomaly "$@" ;;
811
811
  patrol) patrol_ai_analyze "$@" ;;
812
812
  baseline) predict_update_baseline "$@" ;;
813
+ inject-prevention)
814
+ local stage="${1:-build}"
815
+ local issue_json="${2:-{}}"
816
+ local mem_ctx="${3:-}"
817
+ [[ -n "$mem_ctx" && -f "$mem_ctx" ]] && mem_ctx=$(cat "$mem_ctx" 2>/dev/null || true)
818
+ predict_inject_prevention "$stage" "$issue_json" "$mem_ctx" || true
819
+ ;;
813
820
  help|--help|-h) show_help ;;
814
821
  *) error "Unknown command: $cmd"; exit 1 ;;
815
822
  esac
@@ -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.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
 
12
12
  # ─── Handle subcommands ───────────────────────────────────────────────────────
package/scripts/sw-ps.sh CHANGED
@@ -5,7 +5,7 @@
5
5
  # ║ Displays a table of agents running in claude-* tmux windows with ║
6
6
  # ║ PID, status, idle time, and pane references. ║
7
7
  # ╚═══════════════════════════════════════════════════════════════════════════╝
8
- VERSION="2.0.0"
8
+ VERSION="2.1.0"
9
9
  set -euo pipefail
10
10
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
11
 
@@ -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.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -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.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12