shipwright-cli 2.3.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -28
- package/completions/_shipwright +1 -1
- package/completions/shipwright.bash +3 -8
- package/completions/shipwright.fish +1 -1
- package/config/defaults.json +111 -0
- package/config/event-schema.json +81 -0
- package/config/policy.json +155 -2
- package/config/policy.schema.json +162 -1
- package/dashboard/coverage/coverage-summary.json +14 -0
- package/dashboard/public/index.html +1 -1
- package/dashboard/server.ts +306 -17
- package/dashboard/src/components/charts/bar.test.ts +79 -0
- package/dashboard/src/components/charts/donut.test.ts +68 -0
- package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
- package/dashboard/src/components/charts/sparkline.test.ts +125 -0
- package/dashboard/src/core/api.test.ts +309 -0
- package/dashboard/src/core/helpers.test.ts +301 -0
- package/dashboard/src/core/router.test.ts +307 -0
- package/dashboard/src/core/router.ts +7 -0
- package/dashboard/src/core/sse.test.ts +144 -0
- package/dashboard/src/views/metrics.test.ts +186 -0
- package/dashboard/src/views/overview.test.ts +173 -0
- package/dashboard/src/views/pipelines.test.ts +183 -0
- package/dashboard/src/views/team.test.ts +253 -0
- package/dashboard/vitest.config.ts +14 -5
- package/docs/TIPS.md +1 -1
- package/docs/patterns/README.md +1 -1
- package/package.json +15 -5
- package/scripts/adapters/docker-deploy.sh +1 -1
- package/scripts/adapters/tmux-adapter.sh +11 -1
- package/scripts/adapters/wezterm-adapter.sh +1 -1
- package/scripts/check-version-consistency.sh +1 -1
- package/scripts/lib/architecture.sh +126 -0
- package/scripts/lib/bootstrap.sh +75 -0
- package/scripts/lib/compat.sh +89 -6
- package/scripts/lib/config.sh +91 -0
- package/scripts/lib/daemon-adaptive.sh +3 -3
- package/scripts/lib/daemon-dispatch.sh +39 -16
- package/scripts/lib/daemon-health.sh +1 -1
- package/scripts/lib/daemon-patrol.sh +24 -12
- package/scripts/lib/daemon-poll.sh +37 -25
- package/scripts/lib/daemon-state.sh +115 -23
- package/scripts/lib/daemon-triage.sh +30 -8
- package/scripts/lib/fleet-failover.sh +63 -0
- package/scripts/lib/helpers.sh +30 -6
- package/scripts/lib/pipeline-detection.sh +2 -2
- package/scripts/lib/pipeline-github.sh +9 -9
- package/scripts/lib/pipeline-intelligence.sh +85 -35
- package/scripts/lib/pipeline-quality-checks.sh +16 -16
- package/scripts/lib/pipeline-quality.sh +1 -1
- package/scripts/lib/pipeline-stages.sh +242 -28
- package/scripts/lib/pipeline-state.sh +40 -4
- package/scripts/lib/test-helpers.sh +247 -0
- package/scripts/postinstall.mjs +3 -11
- package/scripts/sw +10 -4
- package/scripts/sw-activity.sh +1 -11
- package/scripts/sw-adaptive.sh +109 -85
- package/scripts/sw-adversarial.sh +4 -14
- package/scripts/sw-architecture-enforcer.sh +1 -11
- package/scripts/sw-auth.sh +8 -17
- package/scripts/sw-autonomous.sh +111 -49
- package/scripts/sw-changelog.sh +1 -11
- package/scripts/sw-checkpoint.sh +144 -20
- package/scripts/sw-ci.sh +2 -12
- package/scripts/sw-cleanup.sh +13 -17
- package/scripts/sw-code-review.sh +16 -36
- package/scripts/sw-connect.sh +5 -12
- package/scripts/sw-context.sh +9 -26
- package/scripts/sw-cost.sh +6 -16
- package/scripts/sw-daemon.sh +75 -70
- package/scripts/sw-dashboard.sh +57 -17
- package/scripts/sw-db.sh +506 -15
- package/scripts/sw-decompose.sh +1 -11
- package/scripts/sw-deps.sh +15 -25
- package/scripts/sw-developer-simulation.sh +1 -11
- package/scripts/sw-discovery.sh +112 -30
- package/scripts/sw-doc-fleet.sh +7 -17
- package/scripts/sw-docs-agent.sh +6 -16
- package/scripts/sw-docs.sh +4 -12
- package/scripts/sw-doctor.sh +134 -43
- package/scripts/sw-dora.sh +11 -19
- package/scripts/sw-durable.sh +35 -52
- package/scripts/sw-e2e-orchestrator.sh +11 -27
- package/scripts/sw-eventbus.sh +115 -115
- package/scripts/sw-evidence.sh +748 -0
- package/scripts/sw-feedback.sh +3 -13
- package/scripts/sw-fix.sh +2 -20
- package/scripts/sw-fleet-discover.sh +1 -11
- package/scripts/sw-fleet-viz.sh +10 -18
- package/scripts/sw-fleet.sh +13 -17
- package/scripts/sw-github-app.sh +6 -16
- package/scripts/sw-github-checks.sh +1 -11
- package/scripts/sw-github-deploy.sh +1 -11
- package/scripts/sw-github-graphql.sh +2 -12
- package/scripts/sw-guild.sh +1 -11
- package/scripts/sw-heartbeat.sh +49 -12
- package/scripts/sw-hygiene.sh +45 -43
- package/scripts/sw-incident.sh +284 -67
- package/scripts/sw-init.sh +35 -37
- package/scripts/sw-instrument.sh +1 -11
- package/scripts/sw-intelligence.sh +362 -51
- package/scripts/sw-jira.sh +5 -14
- package/scripts/sw-launchd.sh +2 -12
- package/scripts/sw-linear.sh +8 -17
- package/scripts/sw-logs.sh +4 -12
- package/scripts/sw-loop.sh +641 -90
- package/scripts/sw-memory.sh +243 -17
- package/scripts/sw-mission-control.sh +2 -12
- package/scripts/sw-model-router.sh +73 -34
- package/scripts/sw-otel.sh +11 -21
- package/scripts/sw-oversight.sh +1 -11
- package/scripts/sw-patrol-meta.sh +5 -11
- package/scripts/sw-pipeline-composer.sh +7 -17
- package/scripts/sw-pipeline-vitals.sh +1 -11
- package/scripts/sw-pipeline.sh +478 -122
- package/scripts/sw-pm.sh +2 -12
- package/scripts/sw-pr-lifecycle.sh +203 -29
- package/scripts/sw-predictive.sh +16 -22
- package/scripts/sw-prep.sh +6 -16
- package/scripts/sw-ps.sh +1 -11
- package/scripts/sw-public-dashboard.sh +2 -12
- package/scripts/sw-quality.sh +77 -10
- package/scripts/sw-reaper.sh +1 -11
- package/scripts/sw-recruit.sh +15 -25
- package/scripts/sw-regression.sh +11 -21
- package/scripts/sw-release-manager.sh +19 -28
- package/scripts/sw-release.sh +8 -16
- package/scripts/sw-remote.sh +1 -11
- package/scripts/sw-replay.sh +48 -44
- package/scripts/sw-retro.sh +70 -92
- package/scripts/sw-review-rerun.sh +220 -0
- package/scripts/sw-scale.sh +109 -32
- package/scripts/sw-security-audit.sh +12 -22
- package/scripts/sw-self-optimize.sh +239 -23
- package/scripts/sw-session.sh +3 -13
- package/scripts/sw-setup.sh +8 -18
- package/scripts/sw-standup.sh +5 -15
- package/scripts/sw-status.sh +32 -23
- package/scripts/sw-strategic.sh +129 -13
- package/scripts/sw-stream.sh +1 -11
- package/scripts/sw-swarm.sh +76 -36
- package/scripts/sw-team-stages.sh +10 -20
- package/scripts/sw-templates.sh +4 -14
- package/scripts/sw-testgen.sh +3 -13
- package/scripts/sw-tmux-pipeline.sh +1 -19
- package/scripts/sw-tmux-role-color.sh +0 -10
- package/scripts/sw-tmux-status.sh +3 -11
- package/scripts/sw-tmux.sh +2 -20
- package/scripts/sw-trace.sh +1 -19
- package/scripts/sw-tracker-github.sh +0 -10
- package/scripts/sw-tracker-jira.sh +1 -11
- package/scripts/sw-tracker-linear.sh +1 -11
- package/scripts/sw-tracker.sh +7 -24
- package/scripts/sw-triage.sh +24 -34
- package/scripts/sw-upgrade.sh +5 -23
- package/scripts/sw-ux.sh +1 -19
- package/scripts/sw-webhook.sh +18 -32
- package/scripts/sw-widgets.sh +3 -21
- package/scripts/sw-worktree.sh +11 -27
- package/scripts/update-homebrew-sha.sh +67 -0
- package/templates/pipelines/tdd.json +72 -0
- package/scripts/sw-pipeline.sh.mock +0 -7
|
@@ -37,6 +37,22 @@ daemon_spawn_pipeline() {
|
|
|
37
37
|
|
|
38
38
|
daemon_log INFO "Spawning pipeline for issue #${issue_num}: ${issue_title}"
|
|
39
39
|
|
|
40
|
+
# ── Budget gate: hard-stop if daily budget exhausted ──
|
|
41
|
+
if [[ -x "${SCRIPT_DIR}/sw-cost.sh" ]]; then
|
|
42
|
+
local remaining
|
|
43
|
+
remaining=$("${SCRIPT_DIR}/sw-cost.sh" remaining-budget 2>/dev/null || echo "")
|
|
44
|
+
if [[ -n "$remaining" && "$remaining" != "unlimited" ]]; then
|
|
45
|
+
if awk -v r="$remaining" 'BEGIN { exit !(r <= 0) }' 2>/dev/null; then
|
|
46
|
+
daemon_log WARN "Budget exhausted (remaining: \$${remaining}) — skipping issue #${issue_num}"
|
|
47
|
+
emit_event "daemon.budget_exhausted" "remaining=$remaining" "issue=$issue_num"
|
|
48
|
+
return 1
|
|
49
|
+
fi
|
|
50
|
+
if awk -v r="$remaining" 'BEGIN { exit !(r < 1.0) }' 2>/dev/null; then
|
|
51
|
+
daemon_log WARN "Budget low: \$${remaining} remaining"
|
|
52
|
+
fi
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
40
56
|
# ── Issue decomposition (if decomposer available) ──
|
|
41
57
|
local decompose_script="${SCRIPT_DIR}/sw-decompose.sh"
|
|
42
58
|
if [[ -x "$decompose_script" && "$NO_GITHUB" != "true" ]]; then
|
|
@@ -45,7 +61,7 @@ daemon_spawn_pipeline() {
|
|
|
45
61
|
if [[ "$decompose_result" == *"decomposed"* ]]; then
|
|
46
62
|
daemon_log INFO "Issue #${issue_num} decomposed into subtasks — skipping pipeline"
|
|
47
63
|
# Remove the shipwright label so decomposed parent doesn't re-queue
|
|
48
|
-
gh issue edit "$issue_num" --remove-label "shipwright" 2>/dev/null || true
|
|
64
|
+
_timeout 30 gh issue edit "$issue_num" --remove-label "shipwright" 2>/dev/null || true
|
|
49
65
|
return 0
|
|
50
66
|
fi
|
|
51
67
|
fi
|
|
@@ -54,14 +70,14 @@ daemon_spawn_pipeline() {
|
|
|
54
70
|
local issue_goal="$issue_title"
|
|
55
71
|
if [[ "$NO_GITHUB" != "true" ]]; then
|
|
56
72
|
local issue_body_first
|
|
57
|
-
issue_body_first=$(gh issue view "$issue_num" --json body --jq '.body' 2>/dev/null | head -3 | tr '\n' ' ' | cut -c1-200 || true)
|
|
73
|
+
issue_body_first=$(_timeout 30 gh issue view "$issue_num" --json body --jq '.body' 2>/dev/null | head -3 | tr '\n' ' ' | cut -c1-200 || true)
|
|
58
74
|
if [[ -n "$issue_body_first" ]]; then
|
|
59
75
|
issue_goal="${issue_title}: ${issue_body_first}"
|
|
60
76
|
fi
|
|
61
77
|
fi
|
|
62
78
|
|
|
63
79
|
# ── Predictive risk assessment (if enabled) ──
|
|
64
|
-
if [[ "${PREDICTION_ENABLED:-false}" == "true" ]] && type predict_pipeline_risk
|
|
80
|
+
if [[ "${PREDICTION_ENABLED:-false}" == "true" ]] && type predict_pipeline_risk >/dev/null 2>&1; then
|
|
65
81
|
local issue_json_for_pred=""
|
|
66
82
|
if [[ "$NO_GITHUB" != "true" ]]; then
|
|
67
83
|
issue_json_for_pred=$(gh issue view "$issue_num" --json number,title,body,labels 2>/dev/null || echo "")
|
|
@@ -214,7 +230,7 @@ daemon_track_job() {
|
|
|
214
230
|
local issue_num="$1" pid="$2" worktree="$3" title="${4:-}" repo="${5:-}" goal="${6:-}"
|
|
215
231
|
|
|
216
232
|
# Write to SQLite (non-blocking, best-effort)
|
|
217
|
-
if type db_save_job
|
|
233
|
+
if type db_save_job >/dev/null 2>&1; then
|
|
218
234
|
local job_id="daemon-${issue_num}-$(now_epoch)"
|
|
219
235
|
db_save_job "$job_id" "$issue_num" "$title" "$pid" "$worktree" "" "${PIPELINE_TEMPLATE:-autonomous}" "$goal" 2>/dev/null || true
|
|
220
236
|
fi
|
|
@@ -309,7 +325,7 @@ daemon_reap_completed() {
|
|
|
309
325
|
emit_event "daemon.reap" "issue=$issue_num" "result=$result_str" "duration_s=$dur_s"
|
|
310
326
|
|
|
311
327
|
# Update SQLite (mark job complete/failed)
|
|
312
|
-
if type db_complete_job
|
|
328
|
+
if type db_complete_job >/dev/null 2>&1 && type db_fail_job >/dev/null 2>&1; then
|
|
313
329
|
local _db_job_id="daemon-${issue_num}-${start_epoch}"
|
|
314
330
|
if [[ "$exit_code" -eq 0 ]]; then
|
|
315
331
|
db_complete_job "$_db_job_id" "$result_str" 2>/dev/null || true
|
|
@@ -343,7 +359,7 @@ daemon_reap_completed() {
|
|
|
343
359
|
--method PATCH \
|
|
344
360
|
--field status=completed \
|
|
345
361
|
--field conclusion=cancelled \
|
|
346
|
-
--silent 2>/dev/null || true
|
|
362
|
+
--silent --timeout 30 2>/dev/null || true
|
|
347
363
|
fi
|
|
348
364
|
fi
|
|
349
365
|
done < <(jq -r 'keys[]' "$check_ids_file" 2>/dev/null || true)
|
|
@@ -352,13 +368,18 @@ daemon_reap_completed() {
|
|
|
352
368
|
fi
|
|
353
369
|
|
|
354
370
|
# Finalize memory (capture failure patterns for future runs)
|
|
355
|
-
if type memory_finalize_pipeline
|
|
371
|
+
if type memory_finalize_pipeline >/dev/null 2>&1; then
|
|
356
372
|
local _job_state _job_artifacts
|
|
357
373
|
_job_state="${worktree:-.}/.claude/pipeline-state.md"
|
|
358
374
|
_job_artifacts="${worktree:-.}/.claude/pipeline-artifacts"
|
|
359
375
|
memory_finalize_pipeline "$_job_state" "$_job_artifacts" 2>/dev/null || true
|
|
360
376
|
fi
|
|
361
377
|
|
|
378
|
+
# Trigger learning after pipeline reap
|
|
379
|
+
if type optimize_full_analysis &>/dev/null; then
|
|
380
|
+
optimize_full_analysis &>/dev/null &
|
|
381
|
+
fi
|
|
382
|
+
|
|
362
383
|
# Clean up progress tracking for this job
|
|
363
384
|
daemon_clear_progress "$issue_num"
|
|
364
385
|
|
|
@@ -400,13 +421,15 @@ daemon_reap_completed() {
|
|
|
400
421
|
local current_active
|
|
401
422
|
current_active=$(locked_get_active_count)
|
|
402
423
|
if [[ "$current_active" -lt "$MAX_PARALLEL" ]]; then
|
|
403
|
-
local
|
|
404
|
-
|
|
405
|
-
if [[ -n "$
|
|
424
|
+
local next_issue_key
|
|
425
|
+
next_issue_key=$(dequeue_next)
|
|
426
|
+
if [[ -n "$next_issue_key" ]]; then
|
|
427
|
+
local next_issue_num="$next_issue_key" next_repo=""
|
|
428
|
+
[[ "$next_issue_key" == *:* ]] && next_repo="${next_issue_key%%:*}" && next_issue_num="${next_issue_key##*:}"
|
|
406
429
|
local next_title
|
|
407
|
-
next_title=$(jq -r --arg n "$
|
|
408
|
-
daemon_log INFO "Dequeuing issue #${
|
|
409
|
-
daemon_spawn_pipeline "$
|
|
430
|
+
next_title=$(jq -r --arg n "$next_issue_key" '.titles[$n] // ""' "$STATE_FILE" 2>/dev/null || true)
|
|
431
|
+
daemon_log INFO "Dequeuing issue #${next_issue_num}${next_repo:+, repo=${next_repo}}: ${next_title}"
|
|
432
|
+
daemon_spawn_pipeline "$next_issue_num" "$next_title" "$next_repo"
|
|
410
433
|
fi
|
|
411
434
|
fi
|
|
412
435
|
done <<< "$jobs"
|
|
@@ -453,12 +476,12 @@ daemon_on_success() {
|
|
|
453
476
|
|
|
454
477
|
if [[ "$NO_GITHUB" != "true" ]]; then
|
|
455
478
|
# Remove watch label, add success label
|
|
456
|
-
gh issue edit "$issue_num" \
|
|
479
|
+
_timeout 30 gh issue edit "$issue_num" \
|
|
457
480
|
--remove-label "$ON_SUCCESS_REMOVE_LABEL" \
|
|
458
481
|
--add-label "$ON_SUCCESS_ADD_LABEL" 2>/dev/null || true
|
|
459
482
|
|
|
460
483
|
# Comment on issue
|
|
461
|
-
gh issue comment "$issue_num" --body "## ✅ Pipeline Complete
|
|
484
|
+
_timeout 30 gh issue comment "$issue_num" --body "## ✅ Pipeline Complete
|
|
462
485
|
|
|
463
486
|
The autonomous pipeline finished successfully.
|
|
464
487
|
|
|
@@ -471,7 +494,7 @@ Check the associated PR for the implementation." 2>/dev/null || true
|
|
|
471
494
|
|
|
472
495
|
# Optionally close the issue
|
|
473
496
|
if [[ "$ON_SUCCESS_CLOSE_ISSUE" == "true" ]]; then
|
|
474
|
-
gh issue close "$issue_num" 2>/dev/null || true
|
|
497
|
+
_timeout 30 gh issue close "$issue_num" 2>/dev/null || true
|
|
475
498
|
fi
|
|
476
499
|
fi
|
|
477
500
|
|
|
@@ -11,7 +11,7 @@ _DAEMON_HEALTH_LOADED=1
|
|
|
11
11
|
daemon_health_timeout_for_stage() {
|
|
12
12
|
local stage="${1:-unknown}"
|
|
13
13
|
local fallback="${2:-120}"
|
|
14
|
-
if type policy_get
|
|
14
|
+
if type policy_get >/dev/null 2>&1; then
|
|
15
15
|
local policy_val
|
|
16
16
|
policy_val=$(policy_get ".daemon.stage_timeouts.$stage" "")
|
|
17
17
|
if [[ -n "$policy_val" && "$policy_val" =~ ^[0-9]+$ ]]; then
|
|
@@ -45,10 +45,22 @@ daemon_patrol() {
|
|
|
45
45
|
local findings=0
|
|
46
46
|
|
|
47
47
|
# npm audit
|
|
48
|
-
if [[ -f "package.json" ]] && command -v npm
|
|
48
|
+
if [[ -f "package.json" ]] && command -v npm >/dev/null 2>&1; then
|
|
49
49
|
local audit_json
|
|
50
|
-
audit_json=$(npm audit --json 2>/dev/null ||
|
|
51
|
-
|
|
50
|
+
audit_json=$(npm audit --json 2>/dev/null || echo '{}')
|
|
51
|
+
local audit_version
|
|
52
|
+
audit_version=$(echo "$audit_json" | jq -r '.auditReportVersion // 1')
|
|
53
|
+
|
|
54
|
+
local vuln_list
|
|
55
|
+
if [[ "$audit_version" == "2" ]]; then
|
|
56
|
+
# npm 7+ format: .vulnerabilities is an object keyed by package name
|
|
57
|
+
vuln_list=$(echo "$audit_json" | jq -c '[.vulnerabilities | to_entries[] | .value | {name: .name, severity: .severity, url: (.via[0].url // "N/A"), title: (.via[0].title // .name)}]' 2>/dev/null || echo '[]')
|
|
58
|
+
else
|
|
59
|
+
# npm 6 format: .advisories is an object keyed by advisory ID
|
|
60
|
+
vuln_list=$(echo "$audit_json" | jq -c '[.advisories | to_entries[] | .value | {name: .module_name, severity: .severity, url: .url, title: .title}]' 2>/dev/null || echo '[]')
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
if [[ -n "$vuln_list" && "$vuln_list" != "[]" ]]; then
|
|
52
64
|
while IFS= read -r vuln; do
|
|
53
65
|
local severity name advisory_url title
|
|
54
66
|
severity=$(echo "$vuln" | jq -r '.severity // "unknown"')
|
|
@@ -90,12 +102,12 @@ Auto-detected by \`shipwright daemon patrol\`." \
|
|
|
90
102
|
else
|
|
91
103
|
echo -e " ${RED}●${RESET} ${BOLD}${severity}${RESET}: ${title} in ${CYAN}${name}${RESET}"
|
|
92
104
|
fi
|
|
93
|
-
done < <(echo "$
|
|
105
|
+
done < <(echo "$vuln_list" | jq -c '.[]' 2>/dev/null)
|
|
94
106
|
fi
|
|
95
107
|
fi
|
|
96
108
|
|
|
97
109
|
# pip-audit
|
|
98
|
-
if [[ -f "requirements.txt" ]] && command -v pip-audit
|
|
110
|
+
if [[ -f "requirements.txt" ]] && command -v pip-audit >/dev/null 2>&1; then
|
|
99
111
|
local pip_json
|
|
100
112
|
pip_json=$(pip-audit --format=json 2>/dev/null || true)
|
|
101
113
|
if [[ -n "$pip_json" ]]; then
|
|
@@ -106,7 +118,7 @@ Auto-detected by \`shipwright daemon patrol\`." \
|
|
|
106
118
|
fi
|
|
107
119
|
|
|
108
120
|
# cargo audit
|
|
109
|
-
if [[ -f "Cargo.toml" ]] && command -v cargo-audit
|
|
121
|
+
if [[ -f "Cargo.toml" ]] && command -v cargo-audit >/dev/null 2>&1; then
|
|
110
122
|
local cargo_json
|
|
111
123
|
cargo_json=$(cargo audit --json 2>/dev/null || true)
|
|
112
124
|
if [[ -n "$cargo_json" ]]; then
|
|
@@ -117,8 +129,8 @@ Auto-detected by \`shipwright daemon patrol\`." \
|
|
|
117
129
|
fi
|
|
118
130
|
|
|
119
131
|
# Enrich with GitHub security alerts
|
|
120
|
-
if type gh_security_alerts
|
|
121
|
-
if type _gh_detect_repo
|
|
132
|
+
if type gh_security_alerts >/dev/null 2>&1 && [[ "${NO_GITHUB:-false}" != "true" ]]; then
|
|
133
|
+
if type _gh_detect_repo >/dev/null 2>&1; then
|
|
122
134
|
_gh_detect_repo 2>/dev/null || true
|
|
123
135
|
fi
|
|
124
136
|
local gh_owner="${GH_OWNER:-}" gh_repo="${GH_REPO:-}"
|
|
@@ -135,7 +147,7 @@ Auto-detected by \`shipwright daemon patrol\`." \
|
|
|
135
147
|
fi
|
|
136
148
|
|
|
137
149
|
# Enrich with GitHub Dependabot alerts
|
|
138
|
-
if type gh_dependabot_alerts
|
|
150
|
+
if type gh_dependabot_alerts >/dev/null 2>&1 && [[ "${NO_GITHUB:-false}" != "true" ]]; then
|
|
139
151
|
local gh_owner="${GH_OWNER:-}" gh_repo="${GH_REPO:-}"
|
|
140
152
|
if [[ -n "$gh_owner" && -n "$gh_repo" ]]; then
|
|
141
153
|
local dep_alerts
|
|
@@ -162,7 +174,7 @@ Auto-detected by \`shipwright daemon patrol\`." \
|
|
|
162
174
|
daemon_log INFO "Patrol: checking for stale dependencies"
|
|
163
175
|
local findings=0
|
|
164
176
|
|
|
165
|
-
if [[ -f "package.json" ]] && command -v npm
|
|
177
|
+
if [[ -f "package.json" ]] && command -v npm >/dev/null 2>&1; then
|
|
166
178
|
local outdated_json
|
|
167
179
|
outdated_json=$(npm outdated --json 2>/dev/null || true)
|
|
168
180
|
if [[ -n "$outdated_json" ]] && [[ "$outdated_json" != "{}" ]]; then
|
|
@@ -534,7 +546,7 @@ Auto-detected by \`shipwright daemon patrol\` on $(now_iso)." \
|
|
|
534
546
|
failures_json=$(
|
|
535
547
|
(
|
|
536
548
|
source "$memory_script" > /dev/null 2>&1 || true
|
|
537
|
-
if command -v memory_get_actionable_failures
|
|
549
|
+
if command -v memory_get_actionable_failures >/dev/null 2>&1; then
|
|
538
550
|
memory_get_actionable_failures "$PATROL_FAILURES_THRESHOLD"
|
|
539
551
|
else
|
|
540
552
|
echo "[]"
|
|
@@ -1046,7 +1058,7 @@ Auto-detected by \`shipwright daemon patrol\` on $(now_iso)." \
|
|
|
1046
1058
|
echo ""
|
|
1047
1059
|
|
|
1048
1060
|
# ── Stage 2: AI-Powered Confirmation (if enabled) ──
|
|
1049
|
-
if [[ "${PREDICTION_ENABLED:-false}" == "true" ]] && type patrol_ai_analyze
|
|
1061
|
+
if [[ "${PREDICTION_ENABLED:-false}" == "true" ]] && type patrol_ai_analyze >/dev/null 2>&1; then
|
|
1050
1062
|
daemon_log INFO "Intelligence: using AI patrol analysis (prediction enabled)"
|
|
1051
1063
|
echo -e " ${BOLD}AI Deep Analysis${RESET}"
|
|
1052
1064
|
# Sample recent source files for AI analysis
|
|
@@ -53,7 +53,7 @@ daemon_poll_issues() {
|
|
|
53
53
|
--owner "$ORG" \
|
|
54
54
|
--state open \
|
|
55
55
|
--json repository,number,title,labels,body,createdAt \
|
|
56
|
-
--limit
|
|
56
|
+
--limit "${ISSUE_LIMIT:-100}" 2>/dev/null) || {
|
|
57
57
|
# Handle rate limiting with exponential backoff
|
|
58
58
|
if [[ $BACKOFF_SECS -eq 0 ]]; then
|
|
59
59
|
BACKOFF_SECS=30
|
|
@@ -80,7 +80,7 @@ daemon_poll_issues() {
|
|
|
80
80
|
--label "$WATCH_LABEL" \
|
|
81
81
|
--state open \
|
|
82
82
|
--json number,title,labels,body,createdAt \
|
|
83
|
-
--limit
|
|
83
|
+
--limit 100 2>/dev/null) || {
|
|
84
84
|
# Handle rate limiting with exponential backoff
|
|
85
85
|
if [[ $BACKOFF_SECS -eq 0 ]]; then
|
|
86
86
|
BACKOFF_SECS=30
|
|
@@ -212,18 +212,22 @@ daemon_poll_issues() {
|
|
|
212
212
|
while IFS='|' read -r score issue_num repo_name; do
|
|
213
213
|
[[ -z "$issue_num" ]] && continue
|
|
214
214
|
|
|
215
|
+
local issue_key
|
|
216
|
+
issue_key="$issue_num"
|
|
217
|
+
[[ -n "$repo_name" ]] && issue_key="${repo_name}:${issue_num}"
|
|
218
|
+
|
|
215
219
|
local issue_title labels_csv
|
|
216
|
-
issue_title=$(echo "$issues_json" | jq -r --argjson n "$issue_num" '.[] | select(.number == $n) | .title')
|
|
217
|
-
labels_csv=$(echo "$issues_json" | jq -r --argjson n "$issue_num" '.[] | select(.number == $n) | [.labels[].name] | join(",")')
|
|
220
|
+
issue_title=$(echo "$issues_json" | jq -r --argjson n "$issue_num" --arg repo "$repo_name" '.[] | select(.number == $n) | select($repo == "" or (.repository.nameWithOwner // "") == $repo) | .title')
|
|
221
|
+
labels_csv=$(echo "$issues_json" | jq -r --argjson n "$issue_num" --arg repo "$repo_name" '.[] | select(.number == $n) | select($repo == "" or (.repository.nameWithOwner // "") == $repo) | [.labels[].name] | join(",")')
|
|
218
222
|
|
|
219
|
-
# Cache title in state for dashboard visibility
|
|
223
|
+
# Cache title in state for dashboard visibility (use issue_key for org mode)
|
|
220
224
|
if [[ -n "$issue_title" ]]; then
|
|
221
|
-
locked_state_update --arg num "$
|
|
225
|
+
locked_state_update --arg num "$issue_key" --arg title "$issue_title" \
|
|
222
226
|
'.titles[$num] = $title'
|
|
223
227
|
fi
|
|
224
228
|
|
|
225
229
|
# Skip if already inflight
|
|
226
|
-
if daemon_is_inflight "$
|
|
230
|
+
if daemon_is_inflight "$issue_key"; then
|
|
227
231
|
continue
|
|
228
232
|
fi
|
|
229
233
|
|
|
@@ -263,7 +267,7 @@ daemon_poll_issues() {
|
|
|
263
267
|
# Check capacity
|
|
264
268
|
active_count=$(locked_get_active_count)
|
|
265
269
|
if [[ "$active_count" -ge "$MAX_PARALLEL" ]]; then
|
|
266
|
-
enqueue_issue "$
|
|
270
|
+
enqueue_issue "$issue_key"
|
|
267
271
|
continue
|
|
268
272
|
fi
|
|
269
273
|
|
|
@@ -308,24 +312,26 @@ daemon_poll_issues() {
|
|
|
308
312
|
local drain_active
|
|
309
313
|
drain_active=$(locked_get_active_count)
|
|
310
314
|
while [[ "$drain_active" -lt "$MAX_PARALLEL" ]]; do
|
|
311
|
-
local
|
|
312
|
-
|
|
313
|
-
[[ -z "$
|
|
315
|
+
local drain_issue_key
|
|
316
|
+
drain_issue_key=$(dequeue_next)
|
|
317
|
+
[[ -z "$drain_issue_key" ]] && break
|
|
318
|
+
local drain_issue_num="$drain_issue_key" drain_repo=""
|
|
319
|
+
[[ "$drain_issue_key" == *:* ]] && drain_repo="${drain_issue_key%%:*}" && drain_issue_num="${drain_issue_key##*:}"
|
|
314
320
|
local drain_title
|
|
315
|
-
drain_title=$(jq -r --arg n "$
|
|
321
|
+
drain_title=$(jq -r --arg n "$drain_issue_key" '.titles[$n] // ""' "$STATE_FILE" 2>/dev/null || true)
|
|
316
322
|
|
|
317
323
|
local drain_labels drain_score drain_template
|
|
318
|
-
drain_labels=$(echo "$issues_json" | jq -r --argjson n "$
|
|
319
|
-
'.[] | select(.number == $n) | [.labels[].name] | join(",")' 2>/dev/null || echo "")
|
|
320
|
-
drain_score=$(echo "$sorted_order" | grep "|${
|
|
324
|
+
drain_labels=$(echo "$issues_json" | jq -r --argjson n "$drain_issue_num" --arg repo "$drain_repo" \
|
|
325
|
+
'.[] | select(.number == $n) | select($repo == "" or (.repository.nameWithOwner // "") == $repo) | [.labels[].name] | join(",")' 2>/dev/null || echo "")
|
|
326
|
+
drain_score=$(echo "$sorted_order" | grep "|${drain_issue_num}|" | cut -d'|' -f1 || echo "50")
|
|
321
327
|
drain_template=$(select_pipeline_template "$drain_labels" "${drain_score:-50}" 2>/dev/null | tail -1)
|
|
322
328
|
drain_template=$(printf '%s' "$drain_template" | sed $'s/\x1b\\[[0-9;]*m//g' | tr -cd '[:alnum:]-_')
|
|
323
329
|
[[ -z "$drain_template" ]] && drain_template="$PIPELINE_TEMPLATE"
|
|
324
330
|
|
|
325
|
-
daemon_log INFO "Draining queue: issue #${
|
|
331
|
+
daemon_log INFO "Draining queue: issue #${drain_issue_num}${drain_repo:+, repo=${drain_repo}}, template=${drain_template}"
|
|
326
332
|
local orig_template="$PIPELINE_TEMPLATE"
|
|
327
333
|
PIPELINE_TEMPLATE="$drain_template"
|
|
328
|
-
daemon_spawn_pipeline "$
|
|
334
|
+
daemon_spawn_pipeline "$drain_issue_num" "$drain_title" "$drain_repo"
|
|
329
335
|
PIPELINE_TEMPLATE="$orig_template"
|
|
330
336
|
drain_active=$(locked_get_active_count)
|
|
331
337
|
done
|
|
@@ -692,7 +698,7 @@ daemon_auto_scale() {
|
|
|
692
698
|
|
|
693
699
|
# ── Vitals-driven scaling factor ──
|
|
694
700
|
local max_by_vitals="$MAX_WORKERS"
|
|
695
|
-
if type pipeline_compute_vitals
|
|
701
|
+
if type pipeline_compute_vitals >/dev/null 2>&1 && [[ -f "$STATE_FILE" ]]; then
|
|
696
702
|
local _total_health=0 _health_count=0
|
|
697
703
|
while IFS= read -r _job; do
|
|
698
704
|
local _job_issue _job_worktree
|
|
@@ -813,7 +819,7 @@ daemon_self_optimize() {
|
|
|
813
819
|
fi
|
|
814
820
|
|
|
815
821
|
# ── Intelligence-powered optimization (if enabled) ──
|
|
816
|
-
if [[ "${OPTIMIZATION_ENABLED:-false}" == "true" ]] && type optimize_full_analysis
|
|
822
|
+
if [[ "${OPTIMIZATION_ENABLED:-false}" == "true" ]] && type optimize_full_analysis >/dev/null 2>&1; then
|
|
817
823
|
daemon_log INFO "Running intelligence-powered optimization"
|
|
818
824
|
optimize_full_analysis 2>/dev/null || {
|
|
819
825
|
daemon_log WARN "Intelligence optimization failed — falling back to DORA-based tuning"
|
|
@@ -968,7 +974,7 @@ daemon_cleanup_stale() {
|
|
|
968
974
|
now_e=$(now_epoch)
|
|
969
975
|
|
|
970
976
|
# ── 1. Clean old git worktrees ──
|
|
971
|
-
if command -v git
|
|
977
|
+
if command -v git >/dev/null 2>&1; then
|
|
972
978
|
while IFS= read -r line; do
|
|
973
979
|
local wt_path
|
|
974
980
|
wt_path=$(echo "$line" | awk '{print $1}')
|
|
@@ -976,7 +982,7 @@ daemon_cleanup_stale() {
|
|
|
976
982
|
[[ "$wt_path" == *"daemon-issue-"* ]] || continue
|
|
977
983
|
# Check worktree age via directory mtime
|
|
978
984
|
local mtime
|
|
979
|
-
mtime=$(
|
|
985
|
+
mtime=$(file_mtime "$wt_path")
|
|
980
986
|
if [[ $((now_e - mtime)) -gt $age_secs ]]; then
|
|
981
987
|
daemon_log INFO "Removing stale worktree: ${wt_path}"
|
|
982
988
|
git worktree remove "$wt_path" --force 2>/dev/null || true
|
|
@@ -1003,7 +1009,7 @@ daemon_cleanup_stale() {
|
|
|
1003
1009
|
while IFS= read -r artifact_dir; do
|
|
1004
1010
|
[[ -d "$artifact_dir" ]] || continue
|
|
1005
1011
|
local mtime
|
|
1006
|
-
mtime=$(
|
|
1012
|
+
mtime=$(file_mtime "$artifact_dir")
|
|
1007
1013
|
if [[ $((now_e - mtime)) -gt $age_secs ]]; then
|
|
1008
1014
|
daemon_log INFO "Removing stale artifact: ${artifact_dir}"
|
|
1009
1015
|
rm -rf "$artifact_dir"
|
|
@@ -1013,7 +1019,7 @@ daemon_cleanup_stale() {
|
|
|
1013
1019
|
fi
|
|
1014
1020
|
|
|
1015
1021
|
# ── 3. Clean orphaned daemon/* branches (no matching worktree or active job) ──
|
|
1016
|
-
if command -v git
|
|
1022
|
+
if command -v git >/dev/null 2>&1; then
|
|
1017
1023
|
while IFS= read -r branch; do
|
|
1018
1024
|
[[ -z "$branch" ]] && continue
|
|
1019
1025
|
branch="${branch## }" # trim leading spaces
|
|
@@ -1075,7 +1081,7 @@ daemon_cleanup_stale() {
|
|
|
1075
1081
|
ps_status=$(sed -n 's/^status: *//p' "$pipeline_state" 2>/dev/null | head -1 | tr -d ' ')
|
|
1076
1082
|
if [[ "$ps_status" == "running" ]]; then
|
|
1077
1083
|
local ps_mtime
|
|
1078
|
-
ps_mtime=$(
|
|
1084
|
+
ps_mtime=$(file_mtime "$pipeline_state")
|
|
1079
1085
|
local ps_age=$((now_e - ps_mtime))
|
|
1080
1086
|
# If pipeline-state.md has been "running" for more than 2 hours and no active job
|
|
1081
1087
|
if [[ "$ps_age" -gt 7200 ]]; then
|
|
@@ -1098,7 +1104,7 @@ daemon_cleanup_stale() {
|
|
|
1098
1104
|
fi
|
|
1099
1105
|
|
|
1100
1106
|
# ── 7. Clean remote branches for merged pipeline/* branches ──
|
|
1101
|
-
if command -v git
|
|
1107
|
+
if command -v git >/dev/null 2>&1 && [[ "${NO_GITHUB:-}" != "true" ]]; then
|
|
1102
1108
|
while IFS= read -r branch; do
|
|
1103
1109
|
[[ -z "$branch" ]] && continue
|
|
1104
1110
|
branch="${branch## }"
|
|
@@ -1138,6 +1144,12 @@ daemon_poll_loop() {
|
|
|
1138
1144
|
daemon_reap_completed || daemon_log WARN "daemon_reap_completed failed — continuing"
|
|
1139
1145
|
daemon_health_check || daemon_log WARN "daemon_health_check failed — continuing"
|
|
1140
1146
|
|
|
1147
|
+
# Fleet failover: re-queue work from offline machines
|
|
1148
|
+
if [[ -f "$HOME/.shipwright/machines.json" ]]; then
|
|
1149
|
+
[[ -f "$SCRIPT_DIR/lib/fleet-failover.sh" ]] && source "$SCRIPT_DIR/lib/fleet-failover.sh" 2>/dev/null || true
|
|
1150
|
+
fleet_failover_check 2>/dev/null || true
|
|
1151
|
+
fi
|
|
1152
|
+
|
|
1141
1153
|
# Increment cycle counter (must be before all modulo checks)
|
|
1142
1154
|
POLL_CYCLE_COUNT=$((POLL_CYCLE_COUNT + 1))
|
|
1143
1155
|
|