shipwright-cli 2.4.0 → 3.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 +16 -11
- package/completions/_shipwright +248 -94
- package/completions/shipwright.bash +68 -19
- package/completions/shipwright.fish +310 -42
- package/config/decision-tiers.json +55 -0
- package/config/defaults.json +111 -0
- package/config/event-schema.json +218 -0
- package/config/policy.json +21 -18
- 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 +7 -9
- 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 +127 -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 +63 -17
- package/scripts/lib/daemon-failure.sh +0 -0
- package/scripts/lib/daemon-health.sh +1 -1
- package/scripts/lib/daemon-patrol.sh +64 -17
- package/scripts/lib/daemon-poll.sh +54 -25
- package/scripts/lib/daemon-state.sh +125 -23
- package/scripts/lib/daemon-triage.sh +31 -9
- package/scripts/lib/decide-autonomy.sh +295 -0
- package/scripts/lib/decide-scoring.sh +228 -0
- package/scripts/lib/decide-signals.sh +462 -0
- package/scripts/lib/fleet-failover.sh +63 -0
- package/scripts/lib/helpers.sh +29 -6
- package/scripts/lib/pipeline-detection.sh +2 -2
- package/scripts/lib/pipeline-github.sh +9 -9
- package/scripts/lib/pipeline-intelligence.sh +105 -38
- package/scripts/lib/pipeline-quality-checks.sh +17 -16
- package/scripts/lib/pipeline-quality.sh +1 -1
- package/scripts/lib/pipeline-stages.sh +440 -59
- package/scripts/lib/pipeline-state.sh +54 -4
- package/scripts/lib/policy.sh +0 -0
- package/scripts/lib/test-helpers.sh +247 -0
- package/scripts/postinstall.mjs +78 -12
- package/scripts/signals/example-collector.sh +36 -0
- package/scripts/sw +17 -7
- 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 +17 -18
- package/scripts/sw-daemon.sh +76 -71
- package/scripts/sw-dashboard.sh +57 -17
- package/scripts/sw-db.sh +524 -26
- package/scripts/sw-decide.sh +685 -0
- 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 +138 -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 +114 -30
- 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 +48 -74
- package/scripts/sw-init.sh +35 -37
- package/scripts/sw-instrument.sh +1 -11
- package/scripts/sw-intelligence.sh +368 -53
- 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 +905 -104
- package/scripts/sw-memory.sh +263 -20
- package/scripts/sw-mission-control.sh +2 -12
- package/scripts/sw-model-router.sh +73 -34
- package/scripts/sw-otel.sh +15 -23
- 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 +550 -122
- package/scripts/sw-pm.sh +2 -12
- package/scripts/sw-pr-lifecycle.sh +33 -28
- 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 +85 -14
- 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 +1 -1
- package/scripts/sw-scale.sh +174 -41
- package/scripts/sw-security-audit.sh +12 -22
- package/scripts/sw-self-optimize.sh +239 -23
- package/scripts/sw-session.sh +5 -15
- 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 +29 -39
- 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 +73 -0
- package/templates/pipelines/tdd.json +72 -0
- package/scripts/sw-pipeline.sh.mock +0 -7
|
@@ -63,6 +63,23 @@ get_stage_timing_seconds() {
|
|
|
63
63
|
fi
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
# Name of the slowest completed stage (for pipeline.completed event)
|
|
67
|
+
get_slowest_stage() {
|
|
68
|
+
local slowest="" max_sec=0
|
|
69
|
+
local stage_ids
|
|
70
|
+
stage_ids=$(echo "$STAGE_TIMINGS" | grep "_start:" | sed 's/_start:.*//' | sort -u)
|
|
71
|
+
for sid in $stage_ids; do
|
|
72
|
+
[[ -z "$sid" ]] && continue
|
|
73
|
+
local sec
|
|
74
|
+
sec=$(get_stage_timing_seconds "$sid")
|
|
75
|
+
if [[ -n "$sec" && "$sec" =~ ^[0-9]+$ && "$sec" -gt "$max_sec" ]]; then
|
|
76
|
+
max_sec="$sec"
|
|
77
|
+
slowest="$sid"
|
|
78
|
+
fi
|
|
79
|
+
done
|
|
80
|
+
echo "${slowest:-}"
|
|
81
|
+
}
|
|
82
|
+
|
|
66
83
|
get_stage_description() {
|
|
67
84
|
local stage_id="$1"
|
|
68
85
|
|
|
@@ -159,6 +176,13 @@ mark_stage_complete() {
|
|
|
159
176
|
write_state
|
|
160
177
|
|
|
161
178
|
record_stage_effectiveness "$stage_id" "complete"
|
|
179
|
+
|
|
180
|
+
# Record stage completion in SQLite pipeline_stages table
|
|
181
|
+
if type record_stage >/dev/null 2>&1; then
|
|
182
|
+
local _stage_secs
|
|
183
|
+
_stage_secs=$(get_stage_timing_seconds "$stage_id")
|
|
184
|
+
record_stage "${SHIPWRIGHT_PIPELINE_ID:-}" "$stage_id" "complete" "${_stage_secs:-0}" "" 2>/dev/null || true
|
|
185
|
+
fi
|
|
162
186
|
# Update memory baselines and predictive baselines for stage durations
|
|
163
187
|
if [[ "$stage_id" == "test" || "$stage_id" == "build" ]]; then
|
|
164
188
|
local secs
|
|
@@ -191,7 +215,7 @@ mark_stage_complete() {
|
|
|
191
215
|
fi
|
|
192
216
|
|
|
193
217
|
# Update GitHub Check Run for this stage
|
|
194
|
-
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_stage_update
|
|
218
|
+
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_stage_update >/dev/null 2>&1; then
|
|
195
219
|
gh_checks_stage_update "$stage_id" "completed" "success" "Stage $stage_id: ${timing}" 2>/dev/null || true
|
|
196
220
|
fi
|
|
197
221
|
|
|
@@ -215,9 +239,18 @@ mark_stage_complete() {
|
|
|
215
239
|
fi
|
|
216
240
|
|
|
217
241
|
# Durable WAL: publish stage completion event
|
|
218
|
-
if type publish_event
|
|
242
|
+
if type publish_event >/dev/null 2>&1; then
|
|
219
243
|
publish_event "stage.complete" "{\"stage\":\"${stage_id}\",\"issue\":\"${ISSUE_NUMBER:-0}\",\"timing\":\"${timing}\"}" 2>/dev/null || true
|
|
220
244
|
fi
|
|
245
|
+
|
|
246
|
+
# Durable checkpoint: save to DB for pipeline resume
|
|
247
|
+
if type db_save_checkpoint >/dev/null 2>&1; then
|
|
248
|
+
local checkpoint_data
|
|
249
|
+
checkpoint_data=$(jq -nc --arg stage "$stage_id" --arg status "${PIPELINE_STATUS:-running}" \
|
|
250
|
+
--arg issue "${ISSUE_NUMBER:-}" --arg goal "${GOAL:-}" --arg template "${PIPELINE_TEMPLATE:-}" \
|
|
251
|
+
'{stage: $stage, status: $status, issue: $issue, goal: $goal, template: $template, ts: "'"$(now_iso)"'"}')
|
|
252
|
+
db_save_checkpoint "pipeline-${SHIPWRIGHT_PIPELINE_ID:-$$}" "$checkpoint_data" 2>/dev/null || true
|
|
253
|
+
fi
|
|
221
254
|
}
|
|
222
255
|
|
|
223
256
|
persist_artifacts() {
|
|
@@ -328,6 +361,13 @@ mark_stage_failed() {
|
|
|
328
361
|
log_stage "$stage_id" "failed (${timing})"
|
|
329
362
|
write_state
|
|
330
363
|
|
|
364
|
+
# Record stage failure in SQLite pipeline_stages table
|
|
365
|
+
if type record_stage >/dev/null 2>&1; then
|
|
366
|
+
local _stage_secs
|
|
367
|
+
_stage_secs=$(get_stage_timing_seconds "$stage_id")
|
|
368
|
+
record_stage "${SHIPWRIGHT_PIPELINE_ID:-}" "$stage_id" "failed" "${_stage_secs:-0}" "" 2>/dev/null || true
|
|
369
|
+
fi
|
|
370
|
+
|
|
331
371
|
# Update GitHub progress + comment failure
|
|
332
372
|
if [[ -n "$ISSUE_NUMBER" ]]; then
|
|
333
373
|
local body
|
|
@@ -350,7 +390,7 @@ $(tail -5 "$ARTIFACTS_DIR/${stage_id}"*.log 2>/dev/null || echo 'No log availabl
|
|
|
350
390
|
fi
|
|
351
391
|
|
|
352
392
|
# Update GitHub Check Run for this stage
|
|
353
|
-
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_stage_update
|
|
393
|
+
if [[ "${NO_GITHUB:-false}" != "true" ]] && type gh_checks_stage_update >/dev/null 2>&1; then
|
|
354
394
|
local fail_summary
|
|
355
395
|
fail_summary=$(tail -3 "$ARTIFACTS_DIR/${stage_id}"*.log 2>/dev/null | head -c 500 || echo "Stage $stage_id failed")
|
|
356
396
|
gh_checks_stage_update "$stage_id" "completed" "failure" "$fail_summary" 2>/dev/null || true
|
|
@@ -368,7 +408,7 @@ $(tail -5 "$ARTIFACTS_DIR/${stage_id}"*.log 2>/dev/null || echo 'No log availabl
|
|
|
368
408
|
fi
|
|
369
409
|
|
|
370
410
|
# Durable WAL: publish stage failure event
|
|
371
|
-
if type publish_event
|
|
411
|
+
if type publish_event >/dev/null 2>&1; then
|
|
372
412
|
publish_event "stage.failed" "{\"stage\":\"${stage_id}\",\"issue\":\"${ISSUE_NUMBER:-0}\",\"timing\":\"${timing}\"}" 2>/dev/null || true
|
|
373
413
|
fi
|
|
374
414
|
}
|
|
@@ -446,6 +486,16 @@ _SW_STATE_END_
|
|
|
446
486
|
printf '## Log\n'
|
|
447
487
|
printf '%s\n' "$LOG_ENTRIES"
|
|
448
488
|
} >> "$STATE_FILE"
|
|
489
|
+
|
|
490
|
+
# Update pipeline_runs in DB
|
|
491
|
+
if type update_pipeline_status >/dev/null 2>&1 && db_available 2>/dev/null; then
|
|
492
|
+
local _job_id="${SHIPWRIGHT_PIPELINE_ID:-pipeline-$$-${ISSUE_NUMBER:-0}}"
|
|
493
|
+
local _dur_secs=0
|
|
494
|
+
if [[ -n "$PIPELINE_START_EPOCH" ]]; then
|
|
495
|
+
_dur_secs=$(( $(now_epoch) - PIPELINE_START_EPOCH ))
|
|
496
|
+
fi
|
|
497
|
+
update_pipeline_status "$_job_id" "$PIPELINE_STATUS" "$CURRENT_STAGE" "" "$_dur_secs" 2>/dev/null || true
|
|
498
|
+
fi
|
|
449
499
|
}
|
|
450
500
|
|
|
451
501
|
resume_state() {
|
package/scripts/lib/policy.sh
CHANGED
|
File without changes
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright test-helpers — Shared test harness for all unit tests ║
|
|
4
|
+
# ║ Source this from any *-test.sh file to get assert_*, setup, teardown ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
# source "$SCRIPT_DIR/lib/test-helpers.sh"
|
|
10
|
+
#
|
|
11
|
+
# Provides:
|
|
12
|
+
# Colors, counters, assert_pass/fail/eq/contains/contains_regex/gt/json_key
|
|
13
|
+
# setup_test_env / cleanup_test_env (temp dir, mock PATH, mock HOME)
|
|
14
|
+
# print_test_header / print_test_results
|
|
15
|
+
# Mock helpers: mock_binary, mock_jq, mock_git, mock_gh, mock_claude
|
|
16
|
+
|
|
17
|
+
[[ -n "${_TEST_HELPERS_LOADED:-}" ]] && return 0
|
|
18
|
+
_TEST_HELPERS_LOADED=1
|
|
19
|
+
|
|
20
|
+
# ─── Colors ──────────────────────────────────────────────────────────────────
|
|
21
|
+
CYAN='\033[38;2;0;212;255m'
|
|
22
|
+
GREEN='\033[38;2;74;222;128m'
|
|
23
|
+
RED='\033[38;2;248;113;113m'
|
|
24
|
+
YELLOW='\033[38;2;250;204;21m'
|
|
25
|
+
DIM='\033[2m'
|
|
26
|
+
BOLD='\033[1m'
|
|
27
|
+
RESET='\033[0m'
|
|
28
|
+
|
|
29
|
+
# ─── Counters ────────────────────────────────────────────────────────────────
|
|
30
|
+
PASS=0
|
|
31
|
+
FAIL=0
|
|
32
|
+
TOTAL=0
|
|
33
|
+
FAILURES=()
|
|
34
|
+
TEST_TEMP_DIR=""
|
|
35
|
+
|
|
36
|
+
# ─── Assertions ──────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
assert_pass() {
|
|
39
|
+
local desc="$1"
|
|
40
|
+
TOTAL=$((TOTAL + 1))
|
|
41
|
+
PASS=$((PASS + 1))
|
|
42
|
+
echo -e " ${GREEN}✓${RESET} ${desc}"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
assert_fail() {
|
|
46
|
+
local desc="$1"
|
|
47
|
+
local detail="${2:-}"
|
|
48
|
+
TOTAL=$((TOTAL + 1))
|
|
49
|
+
FAIL=$((FAIL + 1))
|
|
50
|
+
FAILURES+=("$desc")
|
|
51
|
+
echo -e " ${RED}✗${RESET} ${desc}"
|
|
52
|
+
[[ -n "$detail" ]] && echo -e " ${DIM}${detail}${RESET}"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
assert_eq() {
|
|
56
|
+
local desc="$1"
|
|
57
|
+
local expected="$2"
|
|
58
|
+
local actual="$3"
|
|
59
|
+
if [[ "$expected" == "$actual" ]]; then
|
|
60
|
+
assert_pass "$desc"
|
|
61
|
+
else
|
|
62
|
+
assert_fail "$desc" "expected: $expected, got: $actual"
|
|
63
|
+
fi
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
assert_contains() {
|
|
67
|
+
local desc="$1"
|
|
68
|
+
local haystack="$2"
|
|
69
|
+
local needle="$3"
|
|
70
|
+
if echo "$haystack" | grep -qF "$needle" 2>/dev/null; then
|
|
71
|
+
assert_pass "$desc"
|
|
72
|
+
else
|
|
73
|
+
assert_fail "$desc" "output missing: $needle"
|
|
74
|
+
fi
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
assert_contains_regex() {
|
|
78
|
+
local desc="$1"
|
|
79
|
+
local haystack="$2"
|
|
80
|
+
local pattern="$3"
|
|
81
|
+
if echo "$haystack" | grep -qE "$pattern" 2>/dev/null; then
|
|
82
|
+
assert_pass "$desc"
|
|
83
|
+
else
|
|
84
|
+
assert_fail "$desc" "output missing pattern: $pattern"
|
|
85
|
+
fi
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
assert_gt() {
|
|
89
|
+
local desc="$1"
|
|
90
|
+
local actual="$2"
|
|
91
|
+
local threshold="$3"
|
|
92
|
+
if [[ "$actual" -gt "$threshold" ]] 2>/dev/null; then
|
|
93
|
+
assert_pass "$desc"
|
|
94
|
+
else
|
|
95
|
+
assert_fail "$desc" "expected >$threshold, got: $actual"
|
|
96
|
+
fi
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
assert_json_key() {
|
|
100
|
+
local desc="$1"
|
|
101
|
+
local json="$2"
|
|
102
|
+
local key="$3"
|
|
103
|
+
local expected="$4"
|
|
104
|
+
local actual
|
|
105
|
+
actual=$(echo "$json" | jq -r "$key" 2>/dev/null)
|
|
106
|
+
if [[ "$actual" == "$expected" ]]; then
|
|
107
|
+
assert_pass "$desc"
|
|
108
|
+
else
|
|
109
|
+
assert_fail "$desc" "key $key: expected $expected, got: $actual"
|
|
110
|
+
fi
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
assert_exit_code() {
|
|
114
|
+
local desc="$1"
|
|
115
|
+
local expected="$2"
|
|
116
|
+
local actual="$3"
|
|
117
|
+
if [[ "$expected" == "$actual" ]]; then
|
|
118
|
+
assert_pass "$desc (exit $actual)"
|
|
119
|
+
else
|
|
120
|
+
assert_fail "$desc" "expected exit code: $expected, got: $actual"
|
|
121
|
+
fi
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
assert_file_exists() {
|
|
125
|
+
local desc="$1"
|
|
126
|
+
local filepath="$2"
|
|
127
|
+
if [[ -f "$filepath" ]]; then
|
|
128
|
+
assert_pass "$desc"
|
|
129
|
+
else
|
|
130
|
+
assert_fail "$desc" "file not found: $filepath"
|
|
131
|
+
fi
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
assert_file_not_exists() {
|
|
135
|
+
local desc="$1"
|
|
136
|
+
local filepath="$2"
|
|
137
|
+
if [[ ! -f "$filepath" ]]; then
|
|
138
|
+
assert_pass "$desc"
|
|
139
|
+
else
|
|
140
|
+
assert_fail "$desc" "file should not exist: $filepath"
|
|
141
|
+
fi
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# ─── Test Environment ────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
setup_test_env() {
|
|
147
|
+
local test_name="${1:-sw-test}"
|
|
148
|
+
TEST_TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/${test_name}.XXXXXX")
|
|
149
|
+
mkdir -p "$TEST_TEMP_DIR/home/.shipwright"
|
|
150
|
+
mkdir -p "$TEST_TEMP_DIR/bin"
|
|
151
|
+
mkdir -p "$TEST_TEMP_DIR/project"
|
|
152
|
+
mkdir -p "$TEST_TEMP_DIR/logs"
|
|
153
|
+
|
|
154
|
+
ORIG_HOME="${HOME}"
|
|
155
|
+
ORIG_PATH="${PATH}"
|
|
156
|
+
export HOME="$TEST_TEMP_DIR/home"
|
|
157
|
+
export PATH="$TEST_TEMP_DIR/bin:$PATH"
|
|
158
|
+
export NO_GITHUB=true
|
|
159
|
+
|
|
160
|
+
# Link real jq if available
|
|
161
|
+
if command -v jq >/dev/null 2>&1; then
|
|
162
|
+
ln -sf "$(command -v jq)" "$TEST_TEMP_DIR/bin/jq"
|
|
163
|
+
fi
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
cleanup_test_env() {
|
|
167
|
+
if [[ -n "$TEST_TEMP_DIR" && -d "$TEST_TEMP_DIR" ]]; then
|
|
168
|
+
rm -rf "$TEST_TEMP_DIR"
|
|
169
|
+
fi
|
|
170
|
+
[[ -n "${ORIG_HOME:-}" ]] && export HOME="$ORIG_HOME"
|
|
171
|
+
[[ -n "${ORIG_PATH:-}" ]] && export PATH="$ORIG_PATH"
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# ─── Mock Helpers ────────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
mock_binary() {
|
|
177
|
+
local name="$1"
|
|
178
|
+
local script="${2:-exit 0}"
|
|
179
|
+
cat > "$TEST_TEMP_DIR/bin/$name" <<MOCK
|
|
180
|
+
#!/usr/bin/env bash
|
|
181
|
+
$script
|
|
182
|
+
MOCK
|
|
183
|
+
chmod +x "$TEST_TEMP_DIR/bin/$name"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
mock_git() {
|
|
187
|
+
mock_binary "git" 'case "${1:-}" in
|
|
188
|
+
rev-parse)
|
|
189
|
+
if [[ "${2:-}" == "--show-toplevel" ]]; then echo "/tmp/mock-repo"
|
|
190
|
+
elif [[ "${2:-}" == "--abbrev-ref" ]]; then echo "main"
|
|
191
|
+
else echo "/tmp/mock-repo"
|
|
192
|
+
fi ;;
|
|
193
|
+
remote) echo "https://github.com/testuser/testrepo.git" ;;
|
|
194
|
+
branch) echo "" ;;
|
|
195
|
+
log) echo "" ;;
|
|
196
|
+
*) echo "" ;;
|
|
197
|
+
esac
|
|
198
|
+
exit 0'
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
mock_gh() {
|
|
202
|
+
mock_binary "gh" 'case "${1:-}" in
|
|
203
|
+
api) echo "{}" ;;
|
|
204
|
+
issue) echo "[]" ;;
|
|
205
|
+
pr) echo "[]" ;;
|
|
206
|
+
*) echo "" ;;
|
|
207
|
+
esac
|
|
208
|
+
exit 0'
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
mock_claude() {
|
|
212
|
+
mock_binary "claude" 'echo "Mock claude response"
|
|
213
|
+
exit 0'
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# ─── Output Helpers ──────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
print_test_header() {
|
|
219
|
+
local title="$1"
|
|
220
|
+
echo ""
|
|
221
|
+
echo -e "${CYAN}${BOLD} ${title}${RESET}"
|
|
222
|
+
echo -e "${DIM} ══════════════════════════════════════════${RESET}"
|
|
223
|
+
echo ""
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
print_test_section() {
|
|
227
|
+
local title="$1"
|
|
228
|
+
echo ""
|
|
229
|
+
echo -e " ${CYAN}${title}${RESET}"
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
print_test_results() {
|
|
233
|
+
echo ""
|
|
234
|
+
echo -e "${DIM} ──────────────────────────────────────────${RESET}"
|
|
235
|
+
echo ""
|
|
236
|
+
if [[ $FAIL -eq 0 ]]; then
|
|
237
|
+
echo -e " ${GREEN}${BOLD}All $TOTAL tests passed${RESET}"
|
|
238
|
+
else
|
|
239
|
+
echo -e " ${RED}${BOLD}$FAIL of $TOTAL tests failed${RESET}"
|
|
240
|
+
echo ""
|
|
241
|
+
for f in "${FAILURES[@]}"; do
|
|
242
|
+
echo -e " ${RED}✗${RESET} $f"
|
|
243
|
+
done
|
|
244
|
+
fi
|
|
245
|
+
echo ""
|
|
246
|
+
exit "$FAIL"
|
|
247
|
+
}
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -11,8 +11,11 @@ import {
|
|
|
11
11
|
readFileSync,
|
|
12
12
|
writeFileSync,
|
|
13
13
|
appendFileSync,
|
|
14
|
+
chmodSync,
|
|
15
|
+
readdirSync,
|
|
14
16
|
} from "fs";
|
|
15
|
-
import { join } from "path";
|
|
17
|
+
import { join, basename } from "path";
|
|
18
|
+
import { execSync } from "child_process";
|
|
16
19
|
|
|
17
20
|
const HOME = process.env.HOME || process.env.USERPROFILE;
|
|
18
21
|
const PKG_DIR = join(import.meta.dirname, "..");
|
|
@@ -130,22 +133,85 @@ try {
|
|
|
130
133
|
success("Migrated legacy config (originals preserved)");
|
|
131
134
|
}
|
|
132
135
|
|
|
136
|
+
// Set executable bits on all scripts (npm strips them on some platforms)
|
|
137
|
+
const scriptsDir = join(PKG_DIR, "scripts");
|
|
138
|
+
if (existsSync(scriptsDir)) {
|
|
139
|
+
let madeExecutable = 0;
|
|
140
|
+
for (const file of readdirSync(scriptsDir)) {
|
|
141
|
+
const fp = join(scriptsDir, file);
|
|
142
|
+
try {
|
|
143
|
+
chmodSync(fp, 0o755);
|
|
144
|
+
madeExecutable++;
|
|
145
|
+
} catch (_) {
|
|
146
|
+
// skip non-files
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const libDir = join(scriptsDir, "lib");
|
|
150
|
+
if (existsSync(libDir)) {
|
|
151
|
+
for (const file of readdirSync(libDir)) {
|
|
152
|
+
try {
|
|
153
|
+
chmodSync(join(libDir, file), 0o755);
|
|
154
|
+
madeExecutable++;
|
|
155
|
+
} catch (_) {}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
success(`Set executable bits on ${madeExecutable} scripts`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Install shell completions for the user's current shell
|
|
162
|
+
const completionsDir = join(PKG_DIR, "completions");
|
|
163
|
+
if (existsSync(completionsDir)) {
|
|
164
|
+
const shell = basename(process.env.SHELL || "/bin/bash");
|
|
165
|
+
try {
|
|
166
|
+
if (shell === "bash") {
|
|
167
|
+
const dest =
|
|
168
|
+
process.env.BASH_COMPLETION_USER_DIR ||
|
|
169
|
+
join(
|
|
170
|
+
process.env.XDG_DATA_HOME || join(HOME, ".local", "share"),
|
|
171
|
+
"bash-completion",
|
|
172
|
+
"completions",
|
|
173
|
+
);
|
|
174
|
+
ensureDir(dest);
|
|
175
|
+
cpSync(
|
|
176
|
+
join(completionsDir, "shipwright.bash"),
|
|
177
|
+
join(dest, "shipwright"),
|
|
178
|
+
);
|
|
179
|
+
cpSync(join(completionsDir, "shipwright.bash"), join(dest, "sw"));
|
|
180
|
+
success(`Installed bash completions to ${dest}`);
|
|
181
|
+
} else if (shell === "zsh") {
|
|
182
|
+
const dest = join(HOME, ".zfunc");
|
|
183
|
+
ensureDir(dest);
|
|
184
|
+
cpSync(join(completionsDir, "_shipwright"), join(dest, "_shipwright"));
|
|
185
|
+
cpSync(join(completionsDir, "_shipwright"), join(dest, "_sw"));
|
|
186
|
+
success(`Installed zsh completions to ${dest}`);
|
|
187
|
+
} else if (shell === "fish") {
|
|
188
|
+
const dest = join(
|
|
189
|
+
process.env.XDG_CONFIG_HOME || join(HOME, ".config"),
|
|
190
|
+
"fish",
|
|
191
|
+
"completions",
|
|
192
|
+
);
|
|
193
|
+
ensureDir(dest);
|
|
194
|
+
cpSync(
|
|
195
|
+
join(completionsDir, "shipwright.fish"),
|
|
196
|
+
join(dest, "shipwright.fish"),
|
|
197
|
+
);
|
|
198
|
+
cpSync(join(completionsDir, "shipwright.fish"), join(dest, "sw.fish"));
|
|
199
|
+
success(`Installed fish completions to ${dest}`);
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {
|
|
202
|
+
warn(`Could not auto-install completions: ${e.message}`);
|
|
203
|
+
info(`Run: shipwright init (or: bash scripts/install-completions.sh)`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
133
207
|
// Print success banner
|
|
134
|
-
const version = JSON.parse(
|
|
135
|
-
readFileSync(join(PKG_DIR, "package.json"), "utf8"),
|
|
136
|
-
).version;
|
|
137
|
-
console.log();
|
|
138
|
-
console.log(`${CYAN}${BOLD} ⚓ Shipwright v${version} installed${RESET}`);
|
|
139
208
|
console.log();
|
|
140
|
-
console.log(
|
|
141
|
-
console.log(
|
|
142
|
-
` ${DIM}$${RESET} shipwright doctor ${DIM}# Verify your setup${RESET}`,
|
|
143
|
-
);
|
|
209
|
+
console.log(`${GREEN}${BOLD}Shipwright CLI installed!${RESET} Next steps:`);
|
|
144
210
|
console.log(
|
|
145
|
-
` ${DIM}
|
|
211
|
+
` ${DIM}shipwright init${RESET} ${DIM}# Set up tmux, hooks, and templates${RESET}`,
|
|
146
212
|
);
|
|
147
213
|
console.log(
|
|
148
|
-
` ${DIM}
|
|
214
|
+
` ${DIM}shipwright doctor${RESET} ${DIM}# Verify your setup${RESET}`,
|
|
149
215
|
);
|
|
150
216
|
console.log();
|
|
151
217
|
} catch (err) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ Example External Signal Collector for Shipwright Decision Engine ║
|
|
4
|
+
# ║ Place custom collectors in scripts/signals/ — they're auto-discovered ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
#
|
|
7
|
+
# Output: one JSON candidate per line (JSONL).
|
|
8
|
+
# Required fields: id, signal, category, title, description, risk_score,
|
|
9
|
+
# confidence, dedup_key
|
|
10
|
+
# Optional fields: evidence (object)
|
|
11
|
+
#
|
|
12
|
+
# The decision engine collects output from all scripts/signals/*.sh files,
|
|
13
|
+
# validates each line as JSON, and includes valid candidates in the scoring
|
|
14
|
+
# pipeline.
|
|
15
|
+
#
|
|
16
|
+
# Categories (determines autonomy tier):
|
|
17
|
+
# auto: deps_patch, deps_minor, security_patch, test_coverage,
|
|
18
|
+
# doc_sync, dead_code
|
|
19
|
+
# propose: refactor_hotspot, architecture_drift, performance_regression,
|
|
20
|
+
# deps_major, security_critical, recurring_failure, dora_regression
|
|
21
|
+
# draft: new_feature, breaking_change, business_logic, api_change,
|
|
22
|
+
# data_model_change
|
|
23
|
+
#
|
|
24
|
+
# Example: detect a custom condition and emit a candidate
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
set -euo pipefail
|
|
28
|
+
|
|
29
|
+
# Example: check if a TODO count exceeds a threshold
|
|
30
|
+
TODO_COUNT=$(grep -r "TODO" --include="*.ts" --include="*.js" --include="*.sh" . 2>/dev/null | wc -l | tr -d ' ' || echo "0")
|
|
31
|
+
|
|
32
|
+
if [[ "${TODO_COUNT:-0}" -gt 50 ]]; then
|
|
33
|
+
cat <<EOF
|
|
34
|
+
{"id":"custom-todo-cleanup","signal":"custom","category":"dead_code","title":"Clean up ${TODO_COUNT} TODOs","description":"Codebase has ${TODO_COUNT} TODO comments — consider a cleanup sprint","evidence":{"todo_count":${TODO_COUNT}},"risk_score":15,"confidence":"0.70","dedup_key":"custom:todo:cleanup"}
|
|
35
|
+
EOF
|
|
36
|
+
fi
|
package/scripts/sw
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
|
|
8
|
-
VERSION="
|
|
8
|
+
VERSION="3.1.0"
|
|
9
9
|
|
|
10
10
|
# Resolve symlinks (required for npm global install where bin/ symlinks to node_modules/)
|
|
11
11
|
SOURCE="${BASH_SOURCE[0]}"
|
|
@@ -51,7 +51,7 @@ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
|
51
51
|
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
52
52
|
|
|
53
53
|
check_tmux() {
|
|
54
|
-
if ! command -v tmux
|
|
54
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
55
55
|
error "tmux is not installed. Install it first:"
|
|
56
56
|
echo -e " ${DIM}brew install tmux${RESET} (macOS)"
|
|
57
57
|
echo -e " ${DIM}sudo apt install tmux${RESET} (Ubuntu/Debian)"
|
|
@@ -81,14 +81,14 @@ ensure_repo_setup() {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
show_version() {
|
|
84
|
-
echo -e "${CYAN}${BOLD}shipwright${RESET} ${DIM}v${VERSION}${RESET} — Orchestrate AI Coding Teams ${DIM}(
|
|
84
|
+
echo -e "${CYAN}${BOLD}shipwright${RESET} ${DIM}v${VERSION}${RESET} — Orchestrate AI Coding Teams ${DIM}(alias: sw)${RESET}"
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
show_help() {
|
|
88
88
|
show_version
|
|
89
89
|
echo ""
|
|
90
90
|
echo -e "${BOLD}USAGE${RESET}"
|
|
91
|
-
echo -e " ${CYAN}shipwright${RESET} <command> [options] ${DIM}(
|
|
91
|
+
echo -e " ${CYAN}shipwright${RESET} <command> [options] ${DIM}(alias: sw)${RESET}"
|
|
92
92
|
echo ""
|
|
93
93
|
echo -e "${BOLD}GETTING STARTED${RESET}"
|
|
94
94
|
echo -e " ${CYAN}init${RESET} One-command setup — tmux, CLI, templates, hooks"
|
|
@@ -193,7 +193,8 @@ route_quality() {
|
|
|
193
193
|
security-audit|audit) exec "$SCRIPT_DIR/sw-security-audit.sh" "$@" ;;
|
|
194
194
|
testgen) exec "$SCRIPT_DIR/sw-testgen.sh" "$@" ;;
|
|
195
195
|
hygiene) exec "$SCRIPT_DIR/sw-hygiene.sh" "$@" ;;
|
|
196
|
-
|
|
196
|
+
validate|gate) exec "$SCRIPT_DIR/sw-quality.sh" "$@" ;;
|
|
197
|
+
help|*) echo "Usage: shipwright quality {code-review|security-audit|testgen|hygiene|validate}"; exit 1 ;;
|
|
197
198
|
esac
|
|
198
199
|
}
|
|
199
200
|
|
|
@@ -467,6 +468,9 @@ main() {
|
|
|
467
468
|
replay)
|
|
468
469
|
exec "$SCRIPT_DIR/sw-replay.sh" "$@"
|
|
469
470
|
;;
|
|
471
|
+
review-rerun|rr)
|
|
472
|
+
exec "$SCRIPT_DIR/sw-review-rerun.sh" "$@"
|
|
473
|
+
;;
|
|
470
474
|
scale)
|
|
471
475
|
exec "$SCRIPT_DIR/sw-scale.sh" "$@"
|
|
472
476
|
;;
|
|
@@ -524,14 +528,20 @@ main() {
|
|
|
524
528
|
eventbus)
|
|
525
529
|
exec "$SCRIPT_DIR/sw-eventbus.sh" "$@"
|
|
526
530
|
;;
|
|
531
|
+
evidence|ev)
|
|
532
|
+
exec "$SCRIPT_DIR/sw-evidence.sh" "$@"
|
|
533
|
+
;;
|
|
534
|
+
decide)
|
|
535
|
+
exec "$SCRIPT_DIR/sw-decide.sh" "$@"
|
|
536
|
+
;;
|
|
527
537
|
otel)
|
|
528
538
|
exec "$SCRIPT_DIR/sw-otel.sh" "$@"
|
|
529
539
|
;;
|
|
530
540
|
triage)
|
|
531
541
|
exec "$SCRIPT_DIR/sw-triage.sh" "$@"
|
|
532
542
|
;;
|
|
533
|
-
|
|
534
|
-
exec "$SCRIPT_DIR/sw-
|
|
543
|
+
pipeline-composer|composer)
|
|
544
|
+
exec "$SCRIPT_DIR/sw-pipeline-composer.sh" "$@"
|
|
535
545
|
;;
|
|
536
546
|
oversight)
|
|
537
547
|
exec "$SCRIPT_DIR/sw-oversight.sh" "$@"
|
package/scripts/sw-activity.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.1.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
|
|
12
12
|
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
@@ -33,16 +33,6 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
|
|
|
33
33
|
echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
|
|
34
34
|
}
|
|
35
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
36
|
# ─── Event File & Filters ─────────────────────────────────────────────────────
|
|
47
37
|
EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
|
|
48
38
|
FILTER_TYPE=""
|