shipwright-cli 1.7.0 → 1.9.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/.claude/agents/code-reviewer.md +90 -0
- package/.claude/agents/devops-engineer.md +142 -0
- package/.claude/agents/pipeline-agent.md +80 -0
- package/.claude/agents/shell-script-specialist.md +150 -0
- package/.claude/agents/test-specialist.md +196 -0
- package/.claude/hooks/post-tool-use.sh +38 -0
- package/.claude/hooks/pre-tool-use.sh +25 -0
- package/.claude/hooks/session-started.sh +37 -0
- package/README.md +212 -814
- package/claude-code/CLAUDE.md.shipwright +54 -0
- package/claude-code/hooks/notify-idle.sh +2 -2
- package/claude-code/hooks/session-start.sh +24 -0
- package/claude-code/hooks/task-completed.sh +6 -2
- package/claude-code/settings.json.template +12 -0
- package/dashboard/public/app.js +4422 -0
- package/dashboard/public/index.html +816 -0
- package/dashboard/public/styles.css +4755 -0
- package/dashboard/server.ts +4315 -0
- package/docs/KNOWN-ISSUES.md +18 -10
- package/docs/TIPS.md +38 -26
- package/docs/patterns/README.md +33 -23
- package/package.json +9 -5
- package/scripts/adapters/iterm2-adapter.sh +1 -1
- package/scripts/adapters/tmux-adapter.sh +52 -23
- package/scripts/adapters/wezterm-adapter.sh +26 -14
- package/scripts/lib/compat.sh +200 -0
- package/scripts/lib/helpers.sh +72 -0
- package/scripts/postinstall.mjs +72 -13
- package/scripts/{cct → sw} +109 -21
- package/scripts/sw-adversarial.sh +274 -0
- package/scripts/sw-architecture-enforcer.sh +330 -0
- package/scripts/sw-checkpoint.sh +390 -0
- package/scripts/{cct-cleanup.sh → sw-cleanup.sh} +3 -1
- package/scripts/sw-connect.sh +619 -0
- package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
- package/scripts/{cct-daemon.sh → sw-daemon.sh} +2217 -204
- package/scripts/sw-dashboard.sh +477 -0
- package/scripts/sw-developer-simulation.sh +252 -0
- package/scripts/sw-docs.sh +635 -0
- package/scripts/sw-doctor.sh +907 -0
- package/scripts/{cct-fix.sh → sw-fix.sh} +10 -6
- package/scripts/{cct-fleet.sh → sw-fleet.sh} +498 -22
- package/scripts/sw-github-checks.sh +521 -0
- package/scripts/sw-github-deploy.sh +533 -0
- package/scripts/sw-github-graphql.sh +972 -0
- package/scripts/sw-heartbeat.sh +293 -0
- package/scripts/sw-init.sh +522 -0
- package/scripts/sw-intelligence.sh +1196 -0
- package/scripts/sw-jira.sh +643 -0
- package/scripts/sw-launchd.sh +364 -0
- package/scripts/sw-linear.sh +648 -0
- package/scripts/{cct-logs.sh → sw-logs.sh} +72 -2
- package/scripts/{cct-loop.sh → sw-loop.sh} +534 -44
- package/scripts/{cct-memory.sh → sw-memory.sh} +321 -38
- package/scripts/sw-patrol-meta.sh +417 -0
- package/scripts/sw-pipeline-composer.sh +455 -0
- package/scripts/{cct-pipeline.sh → sw-pipeline.sh} +2319 -178
- package/scripts/sw-predictive.sh +820 -0
- package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
- package/scripts/{cct-ps.sh → sw-ps.sh} +6 -4
- package/scripts/{cct-reaper.sh → sw-reaper.sh} +6 -4
- package/scripts/sw-remote.sh +687 -0
- package/scripts/sw-self-optimize.sh +947 -0
- package/scripts/sw-session.sh +519 -0
- package/scripts/sw-setup.sh +234 -0
- package/scripts/sw-status.sh +605 -0
- package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
- package/scripts/sw-tmux.sh +591 -0
- package/scripts/sw-tracker-jira.sh +277 -0
- package/scripts/sw-tracker-linear.sh +292 -0
- package/scripts/sw-tracker.sh +409 -0
- package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
- package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
- package/templates/pipelines/autonomous.json +27 -5
- package/templates/pipelines/full.json +12 -0
- package/templates/pipelines/standard.json +12 -0
- package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
- package/tmux/templates/accessibility.json +34 -0
- package/tmux/templates/api-design.json +35 -0
- package/tmux/templates/architecture.json +1 -0
- package/tmux/templates/bug-fix.json +9 -0
- package/tmux/templates/code-review.json +1 -0
- package/tmux/templates/compliance.json +36 -0
- package/tmux/templates/data-pipeline.json +36 -0
- package/tmux/templates/debt-paydown.json +34 -0
- package/tmux/templates/devops.json +1 -0
- package/tmux/templates/documentation.json +1 -0
- package/tmux/templates/exploration.json +1 -0
- package/tmux/templates/feature-dev.json +1 -0
- package/tmux/templates/full-stack.json +8 -0
- package/tmux/templates/i18n.json +34 -0
- package/tmux/templates/incident-response.json +36 -0
- package/tmux/templates/migration.json +1 -0
- package/tmux/templates/observability.json +35 -0
- package/tmux/templates/onboarding.json +33 -0
- package/tmux/templates/performance.json +35 -0
- package/tmux/templates/refactor.json +1 -0
- package/tmux/templates/release.json +35 -0
- package/tmux/templates/security-audit.json +8 -0
- package/tmux/templates/spike.json +34 -0
- package/tmux/templates/testing.json +1 -0
- package/tmux/tmux.conf +98 -9
- package/scripts/cct-doctor.sh +0 -328
- package/scripts/cct-init.sh +0 -282
- package/scripts/cct-session.sh +0 -284
- package/scripts/cct-status.sh +0 -169
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright heartbeat — File-based agent heartbeat protocol ║
|
|
4
|
+
# ║ Write · Check · List · Clear heartbeats for autonomous agents ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
|
+
|
|
9
|
+
VERSION="1.9.0"
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
|
|
12
|
+
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
13
|
+
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
14
|
+
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
15
|
+
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
16
|
+
GREEN='\033[38;2;74;222;128m' # success
|
|
17
|
+
YELLOW='\033[38;2;250;204;21m' # warning
|
|
18
|
+
RED='\033[38;2;248;113;113m' # error
|
|
19
|
+
DIM='\033[2m'
|
|
20
|
+
BOLD='\033[1m'
|
|
21
|
+
RESET='\033[0m'
|
|
22
|
+
|
|
23
|
+
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
24
|
+
# shellcheck source=lib/compat.sh
|
|
25
|
+
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
26
|
+
|
|
27
|
+
# ─── Output Helpers ─────────────────────────────────────────────────────────
|
|
28
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
29
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
30
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
31
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
32
|
+
|
|
33
|
+
# ─── Constants ──────────────────────────────────────────────────────────────
|
|
34
|
+
HEARTBEAT_DIR="$HOME/.shipwright/heartbeats"
|
|
35
|
+
|
|
36
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
37
|
+
|
|
38
|
+
# ─── Ensure heartbeat directory exists ──────────────────────────────────────
|
|
39
|
+
ensure_dir() {
|
|
40
|
+
mkdir -p "$HEARTBEAT_DIR"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# ─── Help ───────────────────────────────────────────────────────────────────
|
|
44
|
+
show_help() {
|
|
45
|
+
echo ""
|
|
46
|
+
echo -e "${CYAN}${BOLD} Shipwright Heartbeat${RESET} ${DIM}v${VERSION}${RESET}"
|
|
47
|
+
echo -e "${DIM} ══════════════════════════════════════════${RESET}"
|
|
48
|
+
echo ""
|
|
49
|
+
echo -e " ${BOLD}USAGE${RESET}"
|
|
50
|
+
echo -e " shipwright heartbeat <command> [options]"
|
|
51
|
+
echo ""
|
|
52
|
+
echo -e " ${BOLD}COMMANDS${RESET}"
|
|
53
|
+
echo -e " ${CYAN}write${RESET} <job-id> Write/update heartbeat for a job"
|
|
54
|
+
echo -e " ${CYAN}check${RESET} <job-id> Check if a job is alive (exit 0) or stale (exit 1)"
|
|
55
|
+
echo -e " ${CYAN}list${RESET} List all active heartbeats as JSON"
|
|
56
|
+
echo -e " ${CYAN}clear${RESET} <job-id> Remove heartbeat file for a job"
|
|
57
|
+
echo ""
|
|
58
|
+
echo -e " ${BOLD}WRITE OPTIONS${RESET}"
|
|
59
|
+
echo -e " --pid <pid> Process ID (default: current PID)"
|
|
60
|
+
echo -e " --issue <num> Issue number"
|
|
61
|
+
echo -e " --stage <stage> Pipeline stage name"
|
|
62
|
+
echo -e " --iteration <n> Build iteration number"
|
|
63
|
+
echo -e " --activity <desc> Description of current activity"
|
|
64
|
+
echo ""
|
|
65
|
+
echo -e " ${BOLD}CHECK OPTIONS${RESET}"
|
|
66
|
+
echo -e " --timeout <secs> Staleness threshold (default: 120)"
|
|
67
|
+
echo ""
|
|
68
|
+
echo -e " ${BOLD}EXAMPLES${RESET}"
|
|
69
|
+
echo -e " ${DIM}# Agent writes heartbeat every 30s${RESET}"
|
|
70
|
+
echo -e " shipwright heartbeat write job-42 --stage build --iteration 3 --activity \"Running tests\""
|
|
71
|
+
echo ""
|
|
72
|
+
echo -e " ${DIM}# Daemon checks if agent is alive${RESET}"
|
|
73
|
+
echo -e " shipwright heartbeat check job-42 --timeout 120"
|
|
74
|
+
echo ""
|
|
75
|
+
echo -e " ${DIM}# Dashboard lists all heartbeats${RESET}"
|
|
76
|
+
echo -e " shipwright heartbeat list"
|
|
77
|
+
echo ""
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# ─── Write Heartbeat ───────────────────────────────────────────────────────
|
|
81
|
+
cmd_write() {
|
|
82
|
+
local job_id="${1:-}"
|
|
83
|
+
if [[ -z "$job_id" ]]; then
|
|
84
|
+
error "Usage: shipwright heartbeat write <job-id> [options]"
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
shift
|
|
88
|
+
|
|
89
|
+
local pid="$$"
|
|
90
|
+
local issue=""
|
|
91
|
+
local stage=""
|
|
92
|
+
local iteration=""
|
|
93
|
+
local activity=""
|
|
94
|
+
|
|
95
|
+
while [[ $# -gt 0 ]]; do
|
|
96
|
+
case "$1" in
|
|
97
|
+
--pid) pid="${2:-}"; shift 2 ;;
|
|
98
|
+
--issue) issue="${2:-}"; shift 2 ;;
|
|
99
|
+
--stage) stage="${2:-}"; shift 2 ;;
|
|
100
|
+
--iteration) iteration="${2:-}"; shift 2 ;;
|
|
101
|
+
--activity) activity="${2:-}"; shift 2 ;;
|
|
102
|
+
*)
|
|
103
|
+
warn "Unknown option: $1"
|
|
104
|
+
shift
|
|
105
|
+
;;
|
|
106
|
+
esac
|
|
107
|
+
done
|
|
108
|
+
|
|
109
|
+
# Collect resource metrics from the process
|
|
110
|
+
local memory_mb=0
|
|
111
|
+
local cpu_pct=0
|
|
112
|
+
|
|
113
|
+
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
|
114
|
+
local rss_kb
|
|
115
|
+
rss_kb="$(ps -o rss= -p "$pid" 2>/dev/null || true)"
|
|
116
|
+
rss_kb="$(echo "$rss_kb" | tr -d ' ')"
|
|
117
|
+
if [[ -n "$rss_kb" && "$rss_kb" =~ ^[0-9]+$ ]]; then
|
|
118
|
+
memory_mb=$((rss_kb / 1024))
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
local cpu_raw
|
|
122
|
+
cpu_raw="$(ps -o %cpu= -p "$pid" 2>/dev/null || true)"
|
|
123
|
+
cpu_raw="$(echo "$cpu_raw" | tr -d ' ')"
|
|
124
|
+
if [[ -n "$cpu_raw" ]]; then
|
|
125
|
+
# Truncate to integer for JSON safety
|
|
126
|
+
cpu_pct="${cpu_raw%%.*}"
|
|
127
|
+
cpu_pct="${cpu_pct:-0}"
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
ensure_dir
|
|
132
|
+
|
|
133
|
+
local tmp_file
|
|
134
|
+
tmp_file="$(mktemp "${HEARTBEAT_DIR}/.tmp.XXXXXX")"
|
|
135
|
+
|
|
136
|
+
# Build JSON with jq for proper escaping
|
|
137
|
+
jq -n \
|
|
138
|
+
--argjson pid "$pid" \
|
|
139
|
+
--arg issue "$issue" \
|
|
140
|
+
--arg stage "$stage" \
|
|
141
|
+
--arg iteration "$iteration" \
|
|
142
|
+
--argjson memory_mb "$memory_mb" \
|
|
143
|
+
--arg cpu_pct "$cpu_pct" \
|
|
144
|
+
--arg last_activity "$activity" \
|
|
145
|
+
--arg updated_at "$(now_iso)" \
|
|
146
|
+
'{
|
|
147
|
+
pid: $pid,
|
|
148
|
+
issue: (if $issue == "" then null else ($issue | tonumber) end),
|
|
149
|
+
stage: (if $stage == "" then null else $stage end),
|
|
150
|
+
iteration: (if $iteration == "" then null else ($iteration | tonumber) end),
|
|
151
|
+
memory_mb: $memory_mb,
|
|
152
|
+
cpu_pct: ($cpu_pct | tonumber),
|
|
153
|
+
last_activity: $last_activity,
|
|
154
|
+
updated_at: $updated_at
|
|
155
|
+
}' > "$tmp_file" || { rm -f "$tmp_file"; return 1; }
|
|
156
|
+
|
|
157
|
+
# Atomic write
|
|
158
|
+
mv "$tmp_file" "${HEARTBEAT_DIR}/${job_id}.json"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# ─── Check Heartbeat ───────────────────────────────────────────────────────
|
|
162
|
+
cmd_check() {
|
|
163
|
+
local job_id="${1:-}"
|
|
164
|
+
if [[ -z "$job_id" ]]; then
|
|
165
|
+
error "Usage: shipwright heartbeat check <job-id> [--timeout <secs>]"
|
|
166
|
+
exit 1
|
|
167
|
+
fi
|
|
168
|
+
shift
|
|
169
|
+
|
|
170
|
+
local timeout=120
|
|
171
|
+
|
|
172
|
+
while [[ $# -gt 0 ]]; do
|
|
173
|
+
case "$1" in
|
|
174
|
+
--timeout) timeout="${2:-120}"; shift 2 ;;
|
|
175
|
+
*)
|
|
176
|
+
warn "Unknown option: $1"
|
|
177
|
+
shift
|
|
178
|
+
;;
|
|
179
|
+
esac
|
|
180
|
+
done
|
|
181
|
+
|
|
182
|
+
local hb_file="${HEARTBEAT_DIR}/${job_id}.json"
|
|
183
|
+
|
|
184
|
+
if [[ ! -f "$hb_file" ]]; then
|
|
185
|
+
error "No heartbeat found for job: ${job_id}"
|
|
186
|
+
return 1
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
local updated_at
|
|
190
|
+
updated_at="$(jq -r '.updated_at' "$hb_file" 2>/dev/null || true)"
|
|
191
|
+
|
|
192
|
+
if [[ -z "$updated_at" || "$updated_at" == "null" ]]; then
|
|
193
|
+
error "Invalid heartbeat file for job: ${job_id}"
|
|
194
|
+
return 1
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# Convert ISO timestamp to epoch for comparison
|
|
198
|
+
local hb_epoch now_epoch age_secs
|
|
199
|
+
|
|
200
|
+
# macOS date -j -f vs GNU date -d (TZ=UTC since timestamps are UTC)
|
|
201
|
+
if TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$updated_at" +%s &>/dev/null; then
|
|
202
|
+
hb_epoch="$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$updated_at" +%s 2>/dev/null)"
|
|
203
|
+
else
|
|
204
|
+
hb_epoch="$(date -d "$updated_at" +%s 2>/dev/null || echo 0)"
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
now_epoch="$(date +%s)"
|
|
208
|
+
age_secs=$((now_epoch - hb_epoch))
|
|
209
|
+
|
|
210
|
+
if [[ "$age_secs" -le "$timeout" ]]; then
|
|
211
|
+
success "Job ${job_id} alive (${age_secs}s ago)"
|
|
212
|
+
return 0
|
|
213
|
+
else
|
|
214
|
+
warn "Job ${job_id} stale (${age_secs}s ago, timeout: ${timeout}s)"
|
|
215
|
+
return 1
|
|
216
|
+
fi
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# ─── List Heartbeats ───────────────────────────────────────────────────────
|
|
220
|
+
cmd_list() {
|
|
221
|
+
ensure_dir
|
|
222
|
+
|
|
223
|
+
local result="["
|
|
224
|
+
local first=true
|
|
225
|
+
|
|
226
|
+
for hb_file in "${HEARTBEAT_DIR}"/*.json; do
|
|
227
|
+
# Handle no matches (glob returns literal pattern)
|
|
228
|
+
if [[ ! -f "$hb_file" ]]; then
|
|
229
|
+
continue
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
local job_id
|
|
233
|
+
job_id="$(basename "$hb_file" .json)"
|
|
234
|
+
|
|
235
|
+
local content
|
|
236
|
+
content="$(jq -c --arg job_id "$job_id" '. + {job_id: $job_id}' "$hb_file" 2>/dev/null || true)"
|
|
237
|
+
|
|
238
|
+
if [[ -z "$content" ]]; then
|
|
239
|
+
continue
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
if [[ "$first" == "true" ]]; then
|
|
243
|
+
first=false
|
|
244
|
+
else
|
|
245
|
+
result="${result},"
|
|
246
|
+
fi
|
|
247
|
+
result="${result}${content}"
|
|
248
|
+
done
|
|
249
|
+
|
|
250
|
+
result="${result}]"
|
|
251
|
+
echo "$result" | jq '.'
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# ─── Clear Heartbeat ───────────────────────────────────────────────────────
|
|
255
|
+
cmd_clear() {
|
|
256
|
+
local job_id="${1:-}"
|
|
257
|
+
if [[ -z "$job_id" ]]; then
|
|
258
|
+
error "Usage: shipwright heartbeat clear <job-id>"
|
|
259
|
+
exit 1
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
local hb_file="${HEARTBEAT_DIR}/${job_id}.json"
|
|
263
|
+
|
|
264
|
+
if [[ -f "$hb_file" ]]; then
|
|
265
|
+
rm -f "$hb_file"
|
|
266
|
+
success "Cleared heartbeat for job: ${job_id}"
|
|
267
|
+
else
|
|
268
|
+
warn "No heartbeat found for job: ${job_id}"
|
|
269
|
+
fi
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# ─── Command Router ────────────────────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
main() {
|
|
275
|
+
local cmd="${1:-help}"
|
|
276
|
+
shift 2>/dev/null || true
|
|
277
|
+
|
|
278
|
+
case "$cmd" in
|
|
279
|
+
write) cmd_write "$@" ;;
|
|
280
|
+
check) cmd_check "$@" ;;
|
|
281
|
+
list) cmd_list "$@" ;;
|
|
282
|
+
clear) cmd_clear "$@" ;;
|
|
283
|
+
help|--help|-h) show_help ;;
|
|
284
|
+
*)
|
|
285
|
+
error "Unknown command: ${cmd}"
|
|
286
|
+
echo ""
|
|
287
|
+
show_help
|
|
288
|
+
exit 1
|
|
289
|
+
;;
|
|
290
|
+
esac
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
main "$@"
|