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
package/scripts/sw-incident.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="
|
|
9
|
+
VERSION="3.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -16,6 +16,10 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
16
16
|
# Canonical helpers (colors, output, events)
|
|
17
17
|
# shellcheck source=lib/helpers.sh
|
|
18
18
|
[[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
|
|
19
|
+
# DB layer for dual-read (SQLite + JSONL fallback)
|
|
20
|
+
# shellcheck source=sw-db.sh
|
|
21
|
+
[[ -f "$SCRIPT_DIR/sw-db.sh" ]] && source "$SCRIPT_DIR/sw-db.sh"
|
|
22
|
+
[[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
|
|
19
23
|
# Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
|
|
20
24
|
[[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
|
|
21
25
|
[[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
|
|
@@ -25,24 +29,6 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
|
25
29
|
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
26
30
|
now_epoch() { date +%s; }
|
|
27
31
|
fi
|
|
28
|
-
if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
29
|
-
emit_event() {
|
|
30
|
-
local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
|
|
31
|
-
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
32
|
-
while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
|
|
33
|
-
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
34
|
-
}
|
|
35
|
-
fi
|
|
36
|
-
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
37
|
-
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
38
|
-
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
39
|
-
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
40
|
-
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
41
|
-
RED="${RED:-\033[38;2;248;113;113m}"
|
|
42
|
-
DIM="${DIM:-\033[2m}"
|
|
43
|
-
BOLD="${BOLD:-\033[1m}"
|
|
44
|
-
RESET="${RESET:-\033[0m}"
|
|
45
|
-
|
|
46
32
|
format_duration() {
|
|
47
33
|
local secs="$1"
|
|
48
34
|
if [[ "$secs" -ge 3600 ]]; then
|
|
@@ -93,50 +79,25 @@ detect_pipeline_failures() {
|
|
|
93
79
|
local since="${1:-3600}" # Last N seconds
|
|
94
80
|
local cutoff_time=$(($(now_epoch) - since))
|
|
95
81
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
awk -v cutoff="$cutoff_time" -F'"' '
|
|
99
|
-
BEGIN { count=0 }
|
|
100
|
-
/pipeline\.failed|stage\.failed|test\.failed|deploy\.failed/ {
|
|
101
|
-
for (i=1; i<=NF; i++) {
|
|
102
|
-
if ($i ~ /ts_epoch/) {
|
|
103
|
-
ts_epoch_val=$(i+2)
|
|
104
|
-
gsub(/^[^0-9]*/, "", ts_epoch_val)
|
|
105
|
-
gsub(/[^0-9].*/, "", ts_epoch_val)
|
|
106
|
-
if (ts_epoch_val+0 > cutoff) {
|
|
107
|
-
print $0
|
|
108
|
-
count++
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
END { exit (count > 0 ? 0 : 1) }
|
|
114
|
-
' "$EVENTS_FILE"
|
|
82
|
+
db_query_events_since "$cutoff_time" 2>/dev/null | jq -e 'map(select((.type | tostring) | (test("failed") or test("error") or test("timeout")))) | length > 0' >/dev/null 2>/dev/null && return 0 || return 1
|
|
115
83
|
}
|
|
116
84
|
|
|
117
85
|
get_recent_failures() {
|
|
118
86
|
local since="${1:-3600}"
|
|
119
87
|
local cutoff_time=$(($(now_epoch) - since))
|
|
120
88
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
issue: .issue,
|
|
134
|
-
stage: .stage,
|
|
135
|
-
reason: .reason,
|
|
136
|
-
error: .error
|
|
137
|
-
}
|
|
138
|
-
)
|
|
139
|
-
' "$EVENTS_FILE" 2>/dev/null || echo "[]"
|
|
89
|
+
db_query_events_since "$cutoff_time" 2>/dev/null | jq '
|
|
90
|
+
map(select((.type | tostring) | (test("failed") or test("error") or test("timeout")))) |
|
|
91
|
+
map({
|
|
92
|
+
ts: .ts,
|
|
93
|
+
ts_epoch: .ts_epoch,
|
|
94
|
+
type: .type,
|
|
95
|
+
issue: .issue,
|
|
96
|
+
stage: .stage,
|
|
97
|
+
reason: .reason,
|
|
98
|
+
error: .error
|
|
99
|
+
})
|
|
100
|
+
' 2>/dev/null || echo "[]"
|
|
140
101
|
}
|
|
141
102
|
|
|
142
103
|
# ─── Severity Classification ───────────────────────────────────────────────
|
|
@@ -226,7 +187,7 @@ create_hotfix_issue() {
|
|
|
226
187
|
local severity="$2"
|
|
227
188
|
local root_cause="$3"
|
|
228
189
|
|
|
229
|
-
if ! command -v gh
|
|
190
|
+
if ! command -v gh >/dev/null 2>&1; then
|
|
230
191
|
warn "gh CLI not found, skipping GitHub issue creation"
|
|
231
192
|
return 1
|
|
232
193
|
fi
|
|
@@ -245,7 +206,7 @@ This issue was automatically created by the incident commander.
|
|
|
245
206
|
|
|
246
207
|
# shipwright label so daemon picks up; hotfix for routing
|
|
247
208
|
local issue_url
|
|
248
|
-
issue_url=$(gh issue create --title "$title" --body "$body" --label "hotfix,shipwright" 2>/dev/null || echo "")
|
|
209
|
+
issue_url=$(gh issue create --title "$title" --body "$body" --label "$(_config_get "labels.incident_labels" "hotfix,shipwright")" 2>/dev/null || echo "")
|
|
249
210
|
|
|
250
211
|
if [[ -n "$issue_url" ]]; then
|
|
251
212
|
success "Created hotfix issue: $issue_url"
|
|
@@ -314,7 +275,7 @@ cmd_watch() {
|
|
|
314
275
|
local failures_json
|
|
315
276
|
failures_json=$(get_recent_failures "$interval")
|
|
316
277
|
local failure_count
|
|
317
|
-
failure_count=$(echo "$failures_json" | jq 'length')
|
|
278
|
+
failure_count=$(echo "$failures_json" | jq 'length' 2>/dev/null || echo "0")
|
|
318
279
|
|
|
319
280
|
if [[ "$failure_count" -gt 0 ]]; then
|
|
320
281
|
info "Detected $failure_count failure(s)"
|
|
@@ -334,6 +295,9 @@ cmd_watch() {
|
|
|
334
295
|
info "Incident $incident_id created (severity: $severity)"
|
|
335
296
|
emit_event "incident.detected" "incident_id=$incident_id" "severity=$severity"
|
|
336
297
|
|
|
298
|
+
# Create harness gap for test case tracking (Code Factory pattern)
|
|
299
|
+
create_harness_gap "$incident_id" "$severity" "$root_cause" 2>/dev/null || true
|
|
300
|
+
|
|
337
301
|
# Auto-response for P0/P1: hotfix issue, trigger pipeline, optional rollback
|
|
338
302
|
if [[ "$severity" == "P0" ]] || [[ "$severity" == "P1" ]]; then
|
|
339
303
|
local auto_rollback
|
|
@@ -507,14 +471,14 @@ cmd_stats() {
|
|
|
507
471
|
mttr=$(jq -r '.mttr_seconds // 0' "$incident_file" 2>/dev/null || echo "0")
|
|
508
472
|
|
|
509
473
|
case "$sev" in
|
|
510
|
-
P0) ((p0_count
|
|
511
|
-
P1) ((p1_count
|
|
512
|
-
P2) ((p2_count
|
|
513
|
-
*) ((p3_count
|
|
474
|
+
P0) p0_count=$((p0_count + 1)) ;;
|
|
475
|
+
P1) p1_count=$((p1_count + 1)) ;;
|
|
476
|
+
P2) p2_count=$((p2_count + 1)) ;;
|
|
477
|
+
*) p3_count=$((p3_count + 1)) ;;
|
|
514
478
|
esac
|
|
515
479
|
|
|
516
480
|
if [[ "$status" == "resolved" ]]; then
|
|
517
|
-
((resolved_count
|
|
481
|
+
resolved_count=$((resolved_count + 1))
|
|
518
482
|
mttr_sum=$((mttr_sum + mttr))
|
|
519
483
|
fi
|
|
520
484
|
done <<< "$incident_files"
|
|
@@ -556,6 +520,242 @@ cmd_stats() {
|
|
|
556
520
|
esac
|
|
557
521
|
}
|
|
558
522
|
|
|
523
|
+
# ─── Harness Gap Loop ─────────────────────────────────────────────────────
|
|
524
|
+
# Code Factory pattern: production regression → harness gap issue → test case
|
|
525
|
+
# added → SLA tracked. Every incident must produce a test case within SLA.
|
|
526
|
+
|
|
527
|
+
HARNESS_GAPS_DIR="${INCIDENTS_DIR}/harness-gaps"
|
|
528
|
+
|
|
529
|
+
load_harness_gap_policy() {
|
|
530
|
+
local policy="${REPO_DIR}/config/policy.json"
|
|
531
|
+
if [[ -f "$policy" ]]; then
|
|
532
|
+
HARNESS_GAP_ENABLED=$(jq -r '.harnessGapPolicy.enabled // false' "$policy" 2>/dev/null || echo "false")
|
|
533
|
+
HARNESS_GAP_P0_SLA=$(jq -r '.harnessGapPolicy.p0SlaHours // 24' "$policy" 2>/dev/null || echo "24")
|
|
534
|
+
HARNESS_GAP_P1_SLA=$(jq -r '.harnessGapPolicy.p1SlaHours // 72' "$policy" 2>/dev/null || echo "72")
|
|
535
|
+
HARNESS_GAP_P2_SLA=$(jq -r '.harnessGapPolicy.p2SlaHours // 168' "$policy" 2>/dev/null || echo "168")
|
|
536
|
+
HARNESS_GAP_AUTO_CREATE=$(jq -r '.harnessGapPolicy.autoCreateGapIssue // true' "$policy" 2>/dev/null || echo "true")
|
|
537
|
+
HARNESS_GAP_REQUIRE_TEST=$(jq -r '.harnessGapPolicy.requireTestCaseBeforeClose // true' "$policy" 2>/dev/null || echo "true")
|
|
538
|
+
else
|
|
539
|
+
HARNESS_GAP_ENABLED="false"
|
|
540
|
+
fi
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
create_harness_gap() {
|
|
544
|
+
local incident_id="$1"
|
|
545
|
+
local severity="$2"
|
|
546
|
+
local root_cause="$3"
|
|
547
|
+
|
|
548
|
+
mkdir -p "$HARNESS_GAPS_DIR"
|
|
549
|
+
load_harness_gap_policy
|
|
550
|
+
|
|
551
|
+
if [[ "$HARNESS_GAP_ENABLED" != "true" ]]; then
|
|
552
|
+
return 0
|
|
553
|
+
fi
|
|
554
|
+
|
|
555
|
+
local gap_id="gap-${incident_id}"
|
|
556
|
+
local gap_file="${HARNESS_GAPS_DIR}/${gap_id}.json"
|
|
557
|
+
local sla_hours
|
|
558
|
+
|
|
559
|
+
case "$severity" in
|
|
560
|
+
P0) sla_hours="$HARNESS_GAP_P0_SLA" ;;
|
|
561
|
+
P1) sla_hours="$HARNESS_GAP_P1_SLA" ;;
|
|
562
|
+
P2) sla_hours="$HARNESS_GAP_P2_SLA" ;;
|
|
563
|
+
*) sla_hours="$HARNESS_GAP_P2_SLA" ;;
|
|
564
|
+
esac
|
|
565
|
+
|
|
566
|
+
local created_at
|
|
567
|
+
created_at=$(now_iso)
|
|
568
|
+
local created_epoch
|
|
569
|
+
created_epoch=$(now_epoch)
|
|
570
|
+
local sla_deadline_epoch=$((created_epoch + sla_hours * 3600))
|
|
571
|
+
|
|
572
|
+
cat > "$gap_file" << EOF
|
|
573
|
+
{
|
|
574
|
+
"gap_id": "${gap_id}",
|
|
575
|
+
"incident_id": "${incident_id}",
|
|
576
|
+
"severity": "${severity}",
|
|
577
|
+
"root_cause": "${root_cause}",
|
|
578
|
+
"created_at": "${created_at}",
|
|
579
|
+
"created_epoch": ${created_epoch},
|
|
580
|
+
"sla_hours": ${sla_hours},
|
|
581
|
+
"sla_deadline_epoch": ${sla_deadline_epoch},
|
|
582
|
+
"status": "open",
|
|
583
|
+
"test_case_file": null,
|
|
584
|
+
"github_issue": null,
|
|
585
|
+
"resolved_at": null
|
|
586
|
+
}
|
|
587
|
+
EOF
|
|
588
|
+
|
|
589
|
+
info "Harness gap created: ${gap_id} (SLA: ${sla_hours}h)"
|
|
590
|
+
emit_event "harness_gap.created" "gap_id=${gap_id}" "incident=${incident_id}" "sla_hours=${sla_hours}"
|
|
591
|
+
|
|
592
|
+
# Auto-create GitHub issue for gap tracking
|
|
593
|
+
if [[ "$HARNESS_GAP_AUTO_CREATE" == "true" ]] && command -v gh >/dev/null 2>&1; then
|
|
594
|
+
local title="[HARNESS GAP] ${severity}: Add test case for ${root_cause}"
|
|
595
|
+
local body="## Harness Gap
|
|
596
|
+
|
|
597
|
+
**Incident:** \`${incident_id}\`
|
|
598
|
+
**Severity:** ${severity}
|
|
599
|
+
**Root Cause:** ${root_cause}
|
|
600
|
+
**SLA:** ${sla_hours} hours
|
|
601
|
+
|
|
602
|
+
## Required Action
|
|
603
|
+
Add a regression test case that covers this failure scenario.
|
|
604
|
+
|
|
605
|
+
## Acceptance Criteria
|
|
606
|
+
- [ ] Test case file created in \`scripts/\`
|
|
607
|
+
- [ ] Test reproduces the original failure condition
|
|
608
|
+
- [ ] Test passes after the fix is applied
|
|
609
|
+
- [ ] Gap record resolved via \`shipwright incident gap resolve ${gap_id} <test_file>\`
|
|
610
|
+
|
|
611
|
+
## Context
|
|
612
|
+
This gap was automatically created by the Shipwright incident commander.
|
|
613
|
+
Part of the Code Factory harness-gap loop: every production regression
|
|
614
|
+
must produce a harness test case within the SLA window.
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
*Auto-generated by Shipwright Code Factory*"
|
|
618
|
+
|
|
619
|
+
local issue_url
|
|
620
|
+
issue_url=$(gh issue create --title "$title" --body "$body" --label "$(_config_get "labels.harness_gap_labels" "harness-gap,shipwright")" 2>/dev/null || echo "")
|
|
621
|
+
if [[ -n "$issue_url" ]]; then
|
|
622
|
+
local issue_num
|
|
623
|
+
issue_num=$(echo "$issue_url" | sed -n 's|.*/issues/\([0-9]*\)|\1|p')
|
|
624
|
+
jq --arg issue "$issue_num" '.github_issue = $issue' "$gap_file" > "${gap_file}.tmp" && mv "${gap_file}.tmp" "$gap_file"
|
|
625
|
+
success "Created harness gap issue: $issue_url"
|
|
626
|
+
fi
|
|
627
|
+
fi
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
resolve_harness_gap() {
|
|
631
|
+
local gap_id="$1"
|
|
632
|
+
local test_case_file="${2:-}"
|
|
633
|
+
|
|
634
|
+
local gap_file="${HARNESS_GAPS_DIR}/${gap_id}.json"
|
|
635
|
+
if [[ ! -f "$gap_file" ]]; then
|
|
636
|
+
error "Harness gap not found: ${gap_id}"
|
|
637
|
+
return 1
|
|
638
|
+
fi
|
|
639
|
+
|
|
640
|
+
load_harness_gap_policy
|
|
641
|
+
|
|
642
|
+
if [[ "$HARNESS_GAP_REQUIRE_TEST" == "true" && -z "$test_case_file" ]]; then
|
|
643
|
+
error "Test case file required to resolve gap (policy: requireTestCaseBeforeClose=true)"
|
|
644
|
+
echo "Usage: shipwright incident gap resolve ${gap_id} <test_case_file>"
|
|
645
|
+
return 1
|
|
646
|
+
fi
|
|
647
|
+
|
|
648
|
+
if [[ -n "$test_case_file" && ! -f "$test_case_file" ]]; then
|
|
649
|
+
error "Test case file not found: ${test_case_file}"
|
|
650
|
+
return 1
|
|
651
|
+
fi
|
|
652
|
+
|
|
653
|
+
local resolved_at
|
|
654
|
+
resolved_at=$(now_iso)
|
|
655
|
+
jq --arg resolved_at "$resolved_at" --arg test_file "${test_case_file:-null}" \
|
|
656
|
+
'.status = "resolved" | .resolved_at = $resolved_at | .test_case_file = $test_file' \
|
|
657
|
+
"$gap_file" > "${gap_file}.tmp" && mv "${gap_file}.tmp" "$gap_file"
|
|
658
|
+
|
|
659
|
+
success "Harness gap resolved: ${gap_id}"
|
|
660
|
+
emit_event "harness_gap.resolved" "gap_id=${gap_id}" "test_file=${test_case_file:-none}"
|
|
661
|
+
|
|
662
|
+
# Close the GitHub issue if it exists
|
|
663
|
+
local github_issue
|
|
664
|
+
github_issue=$(jq -r '.github_issue // empty' "$gap_file" 2>/dev/null)
|
|
665
|
+
if [[ -n "$github_issue" ]] && command -v gh >/dev/null 2>&1; then
|
|
666
|
+
gh issue close "$github_issue" --comment "Harness gap resolved. Test case: \`${test_case_file:-none}\`" 2>/dev/null || true
|
|
667
|
+
fi
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
cmd_gap() {
|
|
671
|
+
local subcmd="${1:-list}"
|
|
672
|
+
shift || true
|
|
673
|
+
|
|
674
|
+
mkdir -p "$HARNESS_GAPS_DIR"
|
|
675
|
+
|
|
676
|
+
case "$subcmd" in
|
|
677
|
+
create)
|
|
678
|
+
create_harness_gap "$@"
|
|
679
|
+
;;
|
|
680
|
+
resolve)
|
|
681
|
+
resolve_harness_gap "$@"
|
|
682
|
+
;;
|
|
683
|
+
list)
|
|
684
|
+
local current_epoch
|
|
685
|
+
current_epoch=$(now_epoch)
|
|
686
|
+
echo -e "${BOLD}Harness Gaps${RESET}"
|
|
687
|
+
echo -e "${DIM}────────────────────────────────────────────────────────────────${RESET}"
|
|
688
|
+
|
|
689
|
+
local gap_files
|
|
690
|
+
gap_files=$(find "$HARNESS_GAPS_DIR" -name 'gap-*.json' -type f 2>/dev/null || true)
|
|
691
|
+
|
|
692
|
+
if [[ -z "$gap_files" ]]; then
|
|
693
|
+
info "No harness gaps recorded"
|
|
694
|
+
return 0
|
|
695
|
+
fi
|
|
696
|
+
|
|
697
|
+
while IFS= read -r gf; do
|
|
698
|
+
[[ -z "$gf" ]] && continue
|
|
699
|
+
local gid sev status sla_deadline
|
|
700
|
+
gid=$(jq -r '.gap_id // "unknown"' "$gf" 2>/dev/null)
|
|
701
|
+
sev=$(jq -r '.severity // "P3"' "$gf" 2>/dev/null)
|
|
702
|
+
status=$(jq -r '.status // "open"' "$gf" 2>/dev/null)
|
|
703
|
+
sla_deadline=$(jq -r '.sla_deadline_epoch // 0' "$gf" 2>/dev/null)
|
|
704
|
+
|
|
705
|
+
local sla_remaining=""
|
|
706
|
+
if [[ "$status" == "open" ]]; then
|
|
707
|
+
local remaining=$((sla_deadline - current_epoch))
|
|
708
|
+
if [[ "$remaining" -lt 0 ]]; then
|
|
709
|
+
sla_remaining="${RED}OVERDUE${RESET}"
|
|
710
|
+
else
|
|
711
|
+
sla_remaining="$(format_duration "$remaining") remaining"
|
|
712
|
+
fi
|
|
713
|
+
else
|
|
714
|
+
sla_remaining="${GREEN}resolved${RESET}"
|
|
715
|
+
fi
|
|
716
|
+
|
|
717
|
+
printf " %-20s %-4s %-8s %b\n" "$gid" "$sev" "$status" "$sla_remaining"
|
|
718
|
+
done <<< "$gap_files"
|
|
719
|
+
;;
|
|
720
|
+
sla)
|
|
721
|
+
# Show SLA compliance metrics
|
|
722
|
+
load_harness_gap_policy
|
|
723
|
+
local current_epoch
|
|
724
|
+
current_epoch=$(now_epoch)
|
|
725
|
+
local total=0 resolved=0 overdue=0 within_sla=0
|
|
726
|
+
|
|
727
|
+
local gap_files
|
|
728
|
+
gap_files=$(find "$HARNESS_GAPS_DIR" -name 'gap-*.json' -type f 2>/dev/null || true)
|
|
729
|
+
|
|
730
|
+
while IFS= read -r gf; do
|
|
731
|
+
[[ -z "$gf" ]] && continue
|
|
732
|
+
total=$((total + 1))
|
|
733
|
+
local status sla_deadline
|
|
734
|
+
status=$(jq -r '.status // "open"' "$gf" 2>/dev/null)
|
|
735
|
+
sla_deadline=$(jq -r '.sla_deadline_epoch // 0' "$gf" 2>/dev/null)
|
|
736
|
+
|
|
737
|
+
if [[ "$status" == "resolved" ]]; then
|
|
738
|
+
resolved=$((resolved + 1))
|
|
739
|
+
within_sla=$((within_sla + 1))
|
|
740
|
+
elif [[ "$current_epoch" -gt "$sla_deadline" ]]; then
|
|
741
|
+
overdue=$((overdue + 1))
|
|
742
|
+
fi
|
|
743
|
+
done <<< "$gap_files"
|
|
744
|
+
|
|
745
|
+
echo -e "${BOLD}Harness Gap SLA Compliance${RESET}"
|
|
746
|
+
echo -e "${DIM}────────────────────────────────────────────────────────────────${RESET}"
|
|
747
|
+
echo "Total gaps: $total"
|
|
748
|
+
echo "Resolved: $resolved"
|
|
749
|
+
echo "Overdue: $overdue"
|
|
750
|
+
echo "SLA compliance: $( [[ $total -gt 0 ]] && echo "$((within_sla * 100 / total))%" || echo "N/A" )"
|
|
751
|
+
;;
|
|
752
|
+
*)
|
|
753
|
+
echo "Usage: shipwright incident gap <create|resolve|list|sla>"
|
|
754
|
+
return 1
|
|
755
|
+
;;
|
|
756
|
+
esac
|
|
757
|
+
}
|
|
758
|
+
|
|
559
759
|
# ─── Stop Command ──────────────────────────────────────────────────────────
|
|
560
760
|
|
|
561
761
|
cmd_stop() {
|
|
@@ -589,6 +789,7 @@ show_help() {
|
|
|
589
789
|
echo -e " ${CYAN}show${RESET} <incident-id> Show details for an incident"
|
|
590
790
|
echo -e " ${CYAN}report${RESET} <incident-id> Generate post-mortem report"
|
|
591
791
|
echo -e " ${CYAN}stats${RESET} [format] Show incident statistics (table|json)"
|
|
792
|
+
echo -e " ${CYAN}gap${RESET} <cmd> Harness gap loop (list|create|resolve|sla)"
|
|
592
793
|
echo -e " ${CYAN}config${RESET} <cmd> Configure incident response (show|set)"
|
|
593
794
|
echo -e " ${CYAN}help${RESET} Show this help"
|
|
594
795
|
echo ""
|
|
@@ -627,9 +828,25 @@ main() {
|
|
|
627
828
|
stats)
|
|
628
829
|
cmd_stats "$@"
|
|
629
830
|
;;
|
|
831
|
+
gap)
|
|
832
|
+
cmd_gap "$@"
|
|
833
|
+
;;
|
|
630
834
|
config)
|
|
631
|
-
|
|
632
|
-
|
|
835
|
+
local policy="${REPO_DIR}/config/policy.json"
|
|
836
|
+
if [[ ! -f "$policy" ]]; then
|
|
837
|
+
warn "No policy file found at ${policy}"
|
|
838
|
+
echo " Use: shipwright init to create one"
|
|
839
|
+
return 1
|
|
840
|
+
fi
|
|
841
|
+
echo -e "${BOLD}Incident & Harness Gap Configuration${RESET}"
|
|
842
|
+
echo ""
|
|
843
|
+
echo -e " Policy file: ${DIM}${policy}${RESET}"
|
|
844
|
+
echo -e " Harness gap enabled: $(jq -r '.harnessGapPolicy.enabled // false' "$policy" 2>/dev/null)"
|
|
845
|
+
echo -e " P0 SLA (hours): $(jq -r '.harnessGapPolicy.p0SlaHours // 24' "$policy" 2>/dev/null)"
|
|
846
|
+
echo -e " P1 SLA (hours): $(jq -r '.harnessGapPolicy.p1SlaHours // 72' "$policy" 2>/dev/null)"
|
|
847
|
+
echo -e " P2 SLA (hours): $(jq -r '.harnessGapPolicy.p2SlaHours // 168' "$policy" 2>/dev/null)"
|
|
848
|
+
echo -e " Auto-create gap issues: $(jq -r '.harnessGapPolicy.autoCreateGapIssue // true' "$policy" 2>/dev/null)"
|
|
849
|
+
echo -e " Require test before close: $(jq -r '.harnessGapPolicy.requireTestCaseBeforeClose // true' "$policy" 2>/dev/null)"
|
|
633
850
|
;;
|
|
634
851
|
help|--help|-h)
|
|
635
852
|
show_help
|
package/scripts/sw-init.sh
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# ║ ║
|
|
9
9
|
# ║ --deploy Detect platform and generate deployed.json template ║
|
|
10
10
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
11
|
-
VERSION="
|
|
11
|
+
VERSION="3.0.0"
|
|
12
12
|
set -euo pipefail
|
|
13
13
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
14
14
|
trap 'rm -f "${tmp:-}"' EXIT
|
|
@@ -33,24 +33,6 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
|
|
|
33
33
|
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
34
34
|
now_epoch() { date +%s; }
|
|
35
35
|
fi
|
|
36
|
-
if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
37
|
-
emit_event() {
|
|
38
|
-
local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
|
|
39
|
-
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
40
|
-
while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
|
|
41
|
-
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
42
|
-
}
|
|
43
|
-
fi
|
|
44
|
-
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
45
|
-
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
46
|
-
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
47
|
-
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
48
|
-
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
49
|
-
RED="${RED:-\033[38;2;248;113;113m}"
|
|
50
|
-
DIM="${DIM:-\033[2m}"
|
|
51
|
-
BOLD="${BOLD:-\033[1m}"
|
|
52
|
-
RESET="${RESET:-\033[0m}"
|
|
53
|
-
|
|
54
36
|
# ─── Flag parsing ───────────────────────────────────────────────────────────
|
|
55
37
|
DEPLOY_SETUP=false
|
|
56
38
|
DEPLOY_PLATFORM=""
|
|
@@ -111,7 +93,7 @@ if [[ "$REPAIR_MODE" == "true" ]]; then
|
|
|
111
93
|
# Strip legacy overlay source lines from user's tmux.conf
|
|
112
94
|
if [[ -f "$HOME/.tmux.conf" ]] && grep -q "claude-teams-overlay" "$HOME/.tmux.conf" 2>/dev/null; then
|
|
113
95
|
tmp=$(mktemp)
|
|
114
|
-
grep -v "claude-teams-overlay" "$HOME/.tmux.conf" > "$tmp" && mv "$tmp" "$HOME/.tmux.conf"
|
|
96
|
+
grep -v "claude-teams-overlay" "$HOME/.tmux.conf" > "$tmp" && mv "$tmp" "$HOME/.tmux.conf" || rm -f "$tmp"
|
|
115
97
|
success "Removed legacy claude-teams-overlay references from ~/.tmux.conf"
|
|
116
98
|
fi
|
|
117
99
|
fi
|
|
@@ -307,14 +289,14 @@ if [[ $_verify_fail -eq 0 ]]; then
|
|
|
307
289
|
fi
|
|
308
290
|
|
|
309
291
|
# ─── CLI Bootstrap (symlinks + PATH) ─────────────────────────────────────────
|
|
310
|
-
# Install sw/shipwright
|
|
292
|
+
# Install sw/shipwright symlinks so the CLI works from anywhere
|
|
311
293
|
BIN_DIR="$HOME/.local/bin"
|
|
312
294
|
mkdir -p "$BIN_DIR"
|
|
313
295
|
|
|
314
296
|
SW_SRC="$SCRIPT_DIR/sw"
|
|
315
297
|
if [[ -f "$SW_SRC" ]]; then
|
|
316
298
|
_cli_changed=false
|
|
317
|
-
for _cmd in sw shipwright
|
|
299
|
+
for _cmd in sw shipwright; do
|
|
318
300
|
_dest="$BIN_DIR/$_cmd"
|
|
319
301
|
if [[ -L "$_dest" ]] && [[ "$(readlink "$_dest")" == "$SW_SRC" ]]; then
|
|
320
302
|
continue
|
|
@@ -322,8 +304,13 @@ if [[ -f "$SW_SRC" ]]; then
|
|
|
322
304
|
ln -sf "$SW_SRC" "$_dest"
|
|
323
305
|
_cli_changed=true
|
|
324
306
|
done
|
|
307
|
+
# Clean up legacy cct symlink if present
|
|
308
|
+
if [[ -L "$BIN_DIR/cct" ]]; then
|
|
309
|
+
rm -f "$BIN_DIR/cct"
|
|
310
|
+
_cli_changed=true
|
|
311
|
+
fi
|
|
325
312
|
if [[ "$_cli_changed" == "true" ]]; then
|
|
326
|
-
success "CLI symlinks: sw, shipwright
|
|
313
|
+
success "CLI symlinks: sw, shipwright → $BIN_DIR"
|
|
327
314
|
else
|
|
328
315
|
success "CLI symlinks already correct"
|
|
329
316
|
fi
|
|
@@ -390,6 +377,13 @@ if [[ -d "$PIPELINES_SRC" ]]; then
|
|
|
390
377
|
success "Installed ${pip_count} pipeline templates → ~/.shipwright/pipelines/"
|
|
391
378
|
fi
|
|
392
379
|
|
|
380
|
+
# ─── Bootstrap optimization & memory (cold-start) ─────────────────────────────
|
|
381
|
+
if [[ -f "$SCRIPT_DIR/lib/bootstrap.sh" ]]; then
|
|
382
|
+
source "$SCRIPT_DIR/lib/bootstrap.sh"
|
|
383
|
+
bootstrap_optimization 2>/dev/null || true
|
|
384
|
+
bootstrap_memory 2>/dev/null || true
|
|
385
|
+
fi
|
|
386
|
+
|
|
393
387
|
# ─── Shell Completions ────────────────────────────────────────────────────────
|
|
394
388
|
# Detect shell type and install completions to the correct location
|
|
395
389
|
# Detect the user's login shell (not the script's running shell).
|
|
@@ -493,11 +487,11 @@ if [[ -f "$SETTINGS_FILE" ]]; then
|
|
|
493
487
|
success "Agent teams already enabled in settings.json"
|
|
494
488
|
else
|
|
495
489
|
# Try to add using jq
|
|
496
|
-
if jq -e '.env' "$SETTINGS_FILE"
|
|
490
|
+
if jq -e '.env' "$SETTINGS_FILE" >/dev/null 2>&1; then
|
|
497
491
|
tmp=$(mktemp)
|
|
498
492
|
jq '.env["CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS"] = "1"' "$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
499
493
|
success "Enabled agent teams in existing settings.json"
|
|
500
|
-
elif jq -e '.' "$SETTINGS_FILE"
|
|
494
|
+
elif jq -e '.' "$SETTINGS_FILE" >/dev/null 2>&1; then
|
|
501
495
|
tmp=$(mktemp)
|
|
502
496
|
jq '. + {"env": {"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"}}' "$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
503
497
|
success "Added agent teams env to settings.json"
|
|
@@ -552,17 +546,17 @@ fi
|
|
|
552
546
|
|
|
553
547
|
# ─── Wire Hooks into settings.json ──────────────────────────────────────────
|
|
554
548
|
# Ensure each installed hook has a matching event config in settings.json
|
|
555
|
-
if [[ -f "$SETTINGS_FILE" ]] && jq -e '.' "$SETTINGS_FILE"
|
|
549
|
+
if [[ -f "$SETTINGS_FILE" ]] && jq -e '.' "$SETTINGS_FILE" >/dev/null 2>&1; then
|
|
556
550
|
hooks_wired=0
|
|
557
551
|
|
|
558
552
|
# Ensure .hooks object exists
|
|
559
|
-
if ! jq -e '.hooks' "$SETTINGS_FILE"
|
|
553
|
+
if ! jq -e '.hooks' "$SETTINGS_FILE" >/dev/null 2>&1; then
|
|
560
554
|
tmp=$(mktemp)
|
|
561
555
|
jq '.hooks = {}' "$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
562
556
|
fi
|
|
563
557
|
|
|
564
558
|
# TeammateIdle
|
|
565
|
-
if [[ -f "$CLAUDE_DIR/hooks/teammate-idle.sh" ]] && ! jq -e '.hooks.TeammateIdle' "$SETTINGS_FILE"
|
|
559
|
+
if [[ -f "$CLAUDE_DIR/hooks/teammate-idle.sh" ]] && ! jq -e '.hooks.TeammateIdle' "$SETTINGS_FILE" >/dev/null 2>&1; then
|
|
566
560
|
tmp=$(mktemp)
|
|
567
561
|
jq '.hooks.TeammateIdle = [{"hooks": [{"type": "command", "command": "~/.claude/hooks/teammate-idle.sh", "timeout": 30, "statusMessage": "Running typecheck before idle..."}]}]' \
|
|
568
562
|
"$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
@@ -570,7 +564,7 @@ if [[ -f "$SETTINGS_FILE" ]] && jq -e '.' "$SETTINGS_FILE" &>/dev/null; then
|
|
|
570
564
|
fi
|
|
571
565
|
|
|
572
566
|
# TaskCompleted
|
|
573
|
-
if [[ -f "$CLAUDE_DIR/hooks/task-completed.sh" ]] && ! jq -e '.hooks.TaskCompleted' "$SETTINGS_FILE"
|
|
567
|
+
if [[ -f "$CLAUDE_DIR/hooks/task-completed.sh" ]] && ! jq -e '.hooks.TaskCompleted' "$SETTINGS_FILE" >/dev/null 2>&1; then
|
|
574
568
|
tmp=$(mktemp)
|
|
575
569
|
jq '.hooks.TaskCompleted = [{"hooks": [{"type": "command", "command": "~/.claude/hooks/task-completed.sh", "timeout": 60, "statusMessage": "Running quality checks..."}]}]' \
|
|
576
570
|
"$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
@@ -578,7 +572,7 @@ if [[ -f "$SETTINGS_FILE" ]] && jq -e '.' "$SETTINGS_FILE" &>/dev/null; then
|
|
|
578
572
|
fi
|
|
579
573
|
|
|
580
574
|
# Notification
|
|
581
|
-
if [[ -f "$CLAUDE_DIR/hooks/notify-idle.sh" ]] && ! jq -e '.hooks.Notification' "$SETTINGS_FILE"
|
|
575
|
+
if [[ -f "$CLAUDE_DIR/hooks/notify-idle.sh" ]] && ! jq -e '.hooks.Notification' "$SETTINGS_FILE" >/dev/null 2>&1; then
|
|
582
576
|
tmp=$(mktemp)
|
|
583
577
|
jq '.hooks.Notification = [{"hooks": [{"type": "command", "command": "~/.claude/hooks/notify-idle.sh", "async": true}]}]' \
|
|
584
578
|
"$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
@@ -586,7 +580,7 @@ if [[ -f "$SETTINGS_FILE" ]] && jq -e '.' "$SETTINGS_FILE" &>/dev/null; then
|
|
|
586
580
|
fi
|
|
587
581
|
|
|
588
582
|
# PreCompact
|
|
589
|
-
if [[ -f "$CLAUDE_DIR/hooks/pre-compact-save.sh" ]] && ! jq -e '.hooks.PreCompact' "$SETTINGS_FILE"
|
|
583
|
+
if [[ -f "$CLAUDE_DIR/hooks/pre-compact-save.sh" ]] && ! jq -e '.hooks.PreCompact' "$SETTINGS_FILE" >/dev/null 2>&1; then
|
|
590
584
|
tmp=$(mktemp)
|
|
591
585
|
jq '.hooks.PreCompact = [{"matcher": "auto", "hooks": [{"type": "command", "command": "~/.claude/hooks/pre-compact-save.sh", "statusMessage": "Saving context before compaction..."}]}]' \
|
|
592
586
|
"$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
@@ -594,7 +588,7 @@ if [[ -f "$SETTINGS_FILE" ]] && jq -e '.' "$SETTINGS_FILE" &>/dev/null; then
|
|
|
594
588
|
fi
|
|
595
589
|
|
|
596
590
|
# SessionStart
|
|
597
|
-
if [[ -f "$CLAUDE_DIR/hooks/session-start.sh" ]] && ! jq -e '.hooks.SessionStart' "$SETTINGS_FILE"
|
|
591
|
+
if [[ -f "$CLAUDE_DIR/hooks/session-start.sh" ]] && ! jq -e '.hooks.SessionStart' "$SETTINGS_FILE" >/dev/null 2>&1; then
|
|
598
592
|
tmp=$(mktemp)
|
|
599
593
|
jq '.hooks.SessionStart = [{"hooks": [{"type": "command", "command": "~/.claude/hooks/session-start.sh", "timeout": 5}]}]' \
|
|
600
594
|
"$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
@@ -644,8 +638,8 @@ fi
|
|
|
644
638
|
|
|
645
639
|
# ─── GitHub CLI Authentication ────────────────────────────────────────────────
|
|
646
640
|
# gh auth is required for daemon, pipeline, PR creation, and issue management
|
|
647
|
-
if command -v gh
|
|
648
|
-
if gh auth status
|
|
641
|
+
if command -v gh >/dev/null 2>&1; then
|
|
642
|
+
if gh auth status >/dev/null 2>&1; then
|
|
649
643
|
success "GitHub CLI authenticated"
|
|
650
644
|
else
|
|
651
645
|
warn "GitHub CLI installed but not authenticated"
|
|
@@ -669,13 +663,13 @@ if [[ -n "${TMUX:-}" ]]; then
|
|
|
669
663
|
fi
|
|
670
664
|
|
|
671
665
|
# ─── Bun (required for dashboard) ──────────────────────────────────────────
|
|
672
|
-
if command -v bun
|
|
666
|
+
if command -v bun >/dev/null 2>&1 || [[ -x "$HOME/.bun/bin/bun" ]]; then
|
|
673
667
|
_bun_cmd="bun"
|
|
674
668
|
[[ -x "$HOME/.bun/bin/bun" ]] && _bun_cmd="$HOME/.bun/bin/bun"
|
|
675
669
|
success "Bun $($_bun_cmd --version 2>/dev/null || echo "installed") — dashboard ready"
|
|
676
670
|
else
|
|
677
671
|
info "Installing Bun (required for ${BOLD}shipwright dashboard${RESET})..."
|
|
678
|
-
if curl -fsSL https://bun.sh/install | bash 2>/dev/null; then
|
|
672
|
+
if curl -fsSL --connect-timeout 10 --max-time 120 https://bun.sh/install | bash 2>/dev/null; then
|
|
679
673
|
export PATH="$HOME/.bun/bin:$PATH"
|
|
680
674
|
success "Bun installed — dashboard ready"
|
|
681
675
|
else
|
|
@@ -781,7 +775,11 @@ else
|
|
|
781
775
|
fi
|
|
782
776
|
|
|
783
777
|
# Confirm with user
|
|
784
|
-
|
|
778
|
+
if [[ -t 0 ]]; then
|
|
779
|
+
read -rp "$(echo -e "${CYAN}${BOLD}▸${RESET} Configure deploy for ${BOLD}${DEPLOY_PLATFORM}${RESET}? [Y/n] ")" confirm
|
|
780
|
+
else
|
|
781
|
+
confirm="y"
|
|
782
|
+
fi
|
|
785
783
|
if [[ "$(echo "$confirm" | tr '[:upper:]' '[:lower:]')" == "n" ]]; then
|
|
786
784
|
info "Aborted. Use --platform to specify manually."
|
|
787
785
|
exit 0
|
package/scripts/sw-instrument.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="
|
|
9
|
+
VERSION="3.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -34,16 +34,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
34
34
|
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
35
35
|
}
|
|
36
36
|
fi
|
|
37
|
-
CYAN="${CYAN:-\033[38;2;0;212;255m}"
|
|
38
|
-
PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
|
|
39
|
-
BLUE="${BLUE:-\033[38;2;0;102;255m}"
|
|
40
|
-
GREEN="${GREEN:-\033[38;2;74;222;128m}"
|
|
41
|
-
YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
|
|
42
|
-
RED="${RED:-\033[38;2;248;113;113m}"
|
|
43
|
-
DIM="${DIM:-\033[2m}"
|
|
44
|
-
BOLD="${BOLD:-\033[1m}"
|
|
45
|
-
RESET="${RESET:-\033[0m}"
|
|
46
|
-
|
|
47
37
|
format_duration() {
|
|
48
38
|
local secs="$1"
|
|
49
39
|
if [[ "$secs" -ge 3600 ]]; then
|