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.
Files changed (106) hide show
  1. package/.claude/agents/code-reviewer.md +90 -0
  2. package/.claude/agents/devops-engineer.md +142 -0
  3. package/.claude/agents/pipeline-agent.md +80 -0
  4. package/.claude/agents/shell-script-specialist.md +150 -0
  5. package/.claude/agents/test-specialist.md +196 -0
  6. package/.claude/hooks/post-tool-use.sh +38 -0
  7. package/.claude/hooks/pre-tool-use.sh +25 -0
  8. package/.claude/hooks/session-started.sh +37 -0
  9. package/README.md +212 -814
  10. package/claude-code/CLAUDE.md.shipwright +54 -0
  11. package/claude-code/hooks/notify-idle.sh +2 -2
  12. package/claude-code/hooks/session-start.sh +24 -0
  13. package/claude-code/hooks/task-completed.sh +6 -2
  14. package/claude-code/settings.json.template +12 -0
  15. package/dashboard/public/app.js +4422 -0
  16. package/dashboard/public/index.html +816 -0
  17. package/dashboard/public/styles.css +4755 -0
  18. package/dashboard/server.ts +4315 -0
  19. package/docs/KNOWN-ISSUES.md +18 -10
  20. package/docs/TIPS.md +38 -26
  21. package/docs/patterns/README.md +33 -23
  22. package/package.json +9 -5
  23. package/scripts/adapters/iterm2-adapter.sh +1 -1
  24. package/scripts/adapters/tmux-adapter.sh +52 -23
  25. package/scripts/adapters/wezterm-adapter.sh +26 -14
  26. package/scripts/lib/compat.sh +200 -0
  27. package/scripts/lib/helpers.sh +72 -0
  28. package/scripts/postinstall.mjs +72 -13
  29. package/scripts/{cct → sw} +109 -21
  30. package/scripts/sw-adversarial.sh +274 -0
  31. package/scripts/sw-architecture-enforcer.sh +330 -0
  32. package/scripts/sw-checkpoint.sh +390 -0
  33. package/scripts/{cct-cleanup.sh → sw-cleanup.sh} +3 -1
  34. package/scripts/sw-connect.sh +619 -0
  35. package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
  36. package/scripts/{cct-daemon.sh → sw-daemon.sh} +2217 -204
  37. package/scripts/sw-dashboard.sh +477 -0
  38. package/scripts/sw-developer-simulation.sh +252 -0
  39. package/scripts/sw-docs.sh +635 -0
  40. package/scripts/sw-doctor.sh +907 -0
  41. package/scripts/{cct-fix.sh → sw-fix.sh} +10 -6
  42. package/scripts/{cct-fleet.sh → sw-fleet.sh} +498 -22
  43. package/scripts/sw-github-checks.sh +521 -0
  44. package/scripts/sw-github-deploy.sh +533 -0
  45. package/scripts/sw-github-graphql.sh +972 -0
  46. package/scripts/sw-heartbeat.sh +293 -0
  47. package/scripts/sw-init.sh +522 -0
  48. package/scripts/sw-intelligence.sh +1196 -0
  49. package/scripts/sw-jira.sh +643 -0
  50. package/scripts/sw-launchd.sh +364 -0
  51. package/scripts/sw-linear.sh +648 -0
  52. package/scripts/{cct-logs.sh → sw-logs.sh} +72 -2
  53. package/scripts/{cct-loop.sh → sw-loop.sh} +534 -44
  54. package/scripts/{cct-memory.sh → sw-memory.sh} +321 -38
  55. package/scripts/sw-patrol-meta.sh +417 -0
  56. package/scripts/sw-pipeline-composer.sh +455 -0
  57. package/scripts/{cct-pipeline.sh → sw-pipeline.sh} +2319 -178
  58. package/scripts/sw-predictive.sh +820 -0
  59. package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
  60. package/scripts/{cct-ps.sh → sw-ps.sh} +6 -4
  61. package/scripts/{cct-reaper.sh → sw-reaper.sh} +6 -4
  62. package/scripts/sw-remote.sh +687 -0
  63. package/scripts/sw-self-optimize.sh +947 -0
  64. package/scripts/sw-session.sh +519 -0
  65. package/scripts/sw-setup.sh +234 -0
  66. package/scripts/sw-status.sh +605 -0
  67. package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
  68. package/scripts/sw-tmux.sh +591 -0
  69. package/scripts/sw-tracker-jira.sh +277 -0
  70. package/scripts/sw-tracker-linear.sh +292 -0
  71. package/scripts/sw-tracker.sh +409 -0
  72. package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
  73. package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
  74. package/templates/pipelines/autonomous.json +27 -5
  75. package/templates/pipelines/full.json +12 -0
  76. package/templates/pipelines/standard.json +12 -0
  77. package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
  78. package/tmux/templates/accessibility.json +34 -0
  79. package/tmux/templates/api-design.json +35 -0
  80. package/tmux/templates/architecture.json +1 -0
  81. package/tmux/templates/bug-fix.json +9 -0
  82. package/tmux/templates/code-review.json +1 -0
  83. package/tmux/templates/compliance.json +36 -0
  84. package/tmux/templates/data-pipeline.json +36 -0
  85. package/tmux/templates/debt-paydown.json +34 -0
  86. package/tmux/templates/devops.json +1 -0
  87. package/tmux/templates/documentation.json +1 -0
  88. package/tmux/templates/exploration.json +1 -0
  89. package/tmux/templates/feature-dev.json +1 -0
  90. package/tmux/templates/full-stack.json +8 -0
  91. package/tmux/templates/i18n.json +34 -0
  92. package/tmux/templates/incident-response.json +36 -0
  93. package/tmux/templates/migration.json +1 -0
  94. package/tmux/templates/observability.json +35 -0
  95. package/tmux/templates/onboarding.json +33 -0
  96. package/tmux/templates/performance.json +35 -0
  97. package/tmux/templates/refactor.json +1 -0
  98. package/tmux/templates/release.json +35 -0
  99. package/tmux/templates/security-audit.json +8 -0
  100. package/tmux/templates/spike.json +34 -0
  101. package/tmux/templates/testing.json +1 -0
  102. package/tmux/tmux.conf +98 -9
  103. package/scripts/cct-doctor.sh +0 -328
  104. package/scripts/cct-init.sh +0 -282
  105. package/scripts/cct-session.sh +0 -284
  106. 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 "$@"