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.
- package/README.md +160 -72
- package/completions/_shipwright +59 -7
- package/completions/shipwright.bash +24 -4
- package/completions/shipwright.fish +80 -2
- package/dashboard/server.ts +208 -0
- package/docs/tmux-research/TMUX-ARCHITECTURE.md +567 -0
- package/docs/tmux-research/TMUX-AUDIT.md +925 -0
- package/docs/tmux-research/TMUX-BEST-PRACTICES-2025-2026.md +829 -0
- package/docs/tmux-research/TMUX-QUICK-REFERENCE.md +543 -0
- package/docs/tmux-research/TMUX-RESEARCH-INDEX.md +438 -0
- package/package.json +2 -2
- package/scripts/lib/helpers.sh +7 -0
- package/scripts/sw +116 -2
- package/scripts/sw-activity.sh +1 -1
- package/scripts/sw-adaptive.sh +1 -1
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +1 -1
- package/scripts/sw-autonomous.sh +128 -38
- package/scripts/sw-changelog.sh +1 -1
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +1 -1
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +62 -1
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +1 -1
- package/scripts/sw-cost.sh +44 -3
- package/scripts/sw-daemon.sh +155 -27
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +958 -118
- package/scripts/sw-decompose.sh +1 -1
- package/scripts/sw-deps.sh +1 -1
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +1 -1
- package/scripts/sw-docs-agent.sh +1 -1
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +49 -1
- package/scripts/sw-dora.sh +1 -1
- package/scripts/sw-durable.sh +1 -1
- package/scripts/sw-e2e-orchestrator.sh +1 -1
- package/scripts/sw-eventbus.sh +1 -1
- package/scripts/sw-feedback.sh +23 -15
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +1 -1
- package/scripts/sw-fleet-viz.sh +1 -1
- package/scripts/sw-fleet.sh +1 -1
- package/scripts/sw-github-app.sh +1 -1
- package/scripts/sw-github-checks.sh +4 -4
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +1 -1
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +1 -1
- package/scripts/sw-incident.sh +45 -6
- package/scripts/sw-init.sh +150 -24
- package/scripts/sw-instrument.sh +1 -1
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +1 -1
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +204 -19
- package/scripts/sw-memory.sh +18 -1
- package/scripts/sw-mission-control.sh +1 -1
- package/scripts/sw-model-router.sh +1 -1
- package/scripts/sw-otel.sh +1 -1
- package/scripts/sw-oversight.sh +76 -1
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +261 -12
- package/scripts/sw-pm.sh +70 -5
- package/scripts/sw-pr-lifecycle.sh +1 -1
- package/scripts/sw-predictive.sh +8 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +1 -1
- package/scripts/sw-quality.sh +1 -1
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +1853 -178
- package/scripts/sw-regression.sh +1 -1
- package/scripts/sw-release-manager.sh +1 -1
- package/scripts/sw-release.sh +1 -1
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +1 -1
- package/scripts/sw-retro.sh +1 -1
- package/scripts/sw-scale.sh +1 -1
- package/scripts/sw-security-audit.sh +1 -1
- package/scripts/sw-self-optimize.sh +1 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +263 -127
- package/scripts/sw-standup.sh +1 -1
- package/scripts/sw-status.sh +44 -2
- package/scripts/sw-strategic.sh +189 -41
- package/scripts/sw-stream.sh +1 -1
- package/scripts/sw-swarm.sh +42 -5
- package/scripts/sw-team-stages.sh +1 -1
- package/scripts/sw-templates.sh +4 -4
- package/scripts/sw-testgen.sh +66 -15
- package/scripts/sw-tmux-pipeline.sh +1 -1
- package/scripts/sw-tmux-role-color.sh +58 -0
- package/scripts/sw-tmux-status.sh +128 -0
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +1 -1
- package/scripts/sw-tracker.sh +1 -1
- package/scripts/sw-triage.sh +61 -37
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +1 -1
- package/scripts/sw-webhook.sh +1 -1
- package/scripts/sw-widgets.sh +1 -1
- package/scripts/sw-worktree.sh +1 -1
- package/templates/pipelines/autonomous.json +2 -2
- package/tmux/shipwright-overlay.conf +35 -17
- package/tmux/tmux.conf +23 -21
package/scripts/sw-daemon.sh
CHANGED
|
@@ -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.
|
|
12
|
+
VERSION="2.1.0"
|
|
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
|
-
|
|
2208
|
-
|
|
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
|
|
2211
|
-
|
|
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=$
|
|
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 —
|
|
2292
|
-
|
|
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}/${
|
|
2301
|
-
emit_event "daemon.retry" "issue=$issue_num" "retry=$retry_count" "max=$
|
|
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
|
-
#
|
|
2347
|
-
local
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
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 (${
|
|
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
|
-
|
|
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
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
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
|
|
4384
|
+
# Auto-select pipeline template: PM recommendation (if available) else labels + triage score
|
|
4278
4385
|
local template
|
|
4279
|
-
|
|
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
|
|
package/scripts/sw-dashboard.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.
|
|
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) ─────────────────────────────────────
|