shipwright-cli 1.7.1 → 1.10.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 (115) 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 +45 -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} +118 -22
  30. package/scripts/sw-adversarial.sh +274 -0
  31. package/scripts/sw-architecture-enforcer.sh +330 -0
  32. package/scripts/sw-checkpoint.sh +468 -0
  33. package/scripts/sw-cleanup.sh +359 -0
  34. package/scripts/sw-connect.sh +619 -0
  35. package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
  36. package/scripts/sw-daemon.sh +5574 -0
  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/{cct-init.sh → sw-init.sh} +144 -11
  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/sw-loop.sh +2217 -0
  54. package/scripts/{cct-memory.sh → sw-memory.sh} +514 -36
  55. package/scripts/sw-patrol-meta.sh +417 -0
  56. package/scripts/sw-pipeline-composer.sh +455 -0
  57. package/scripts/sw-pipeline-vitals.sh +1096 -0
  58. package/scripts/sw-pipeline.sh +7593 -0
  59. package/scripts/sw-predictive.sh +820 -0
  60. package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
  61. package/scripts/{cct-ps.sh → sw-ps.sh} +9 -6
  62. package/scripts/{cct-reaper.sh → sw-reaper.sh} +10 -6
  63. package/scripts/sw-remote.sh +687 -0
  64. package/scripts/sw-self-optimize.sh +1048 -0
  65. package/scripts/sw-session.sh +541 -0
  66. package/scripts/sw-setup.sh +234 -0
  67. package/scripts/sw-status.sh +796 -0
  68. package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
  69. package/scripts/sw-tmux.sh +591 -0
  70. package/scripts/sw-tracker-jira.sh +277 -0
  71. package/scripts/sw-tracker-linear.sh +292 -0
  72. package/scripts/sw-tracker.sh +409 -0
  73. package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
  74. package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
  75. package/templates/pipelines/autonomous.json +35 -6
  76. package/templates/pipelines/cost-aware.json +21 -0
  77. package/templates/pipelines/deployed.json +40 -6
  78. package/templates/pipelines/enterprise.json +16 -2
  79. package/templates/pipelines/fast.json +19 -0
  80. package/templates/pipelines/full.json +28 -2
  81. package/templates/pipelines/hotfix.json +19 -0
  82. package/templates/pipelines/standard.json +31 -0
  83. package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
  84. package/tmux/templates/accessibility.json +34 -0
  85. package/tmux/templates/api-design.json +35 -0
  86. package/tmux/templates/architecture.json +1 -0
  87. package/tmux/templates/bug-fix.json +9 -0
  88. package/tmux/templates/code-review.json +1 -0
  89. package/tmux/templates/compliance.json +36 -0
  90. package/tmux/templates/data-pipeline.json +36 -0
  91. package/tmux/templates/debt-paydown.json +34 -0
  92. package/tmux/templates/devops.json +1 -0
  93. package/tmux/templates/documentation.json +1 -0
  94. package/tmux/templates/exploration.json +1 -0
  95. package/tmux/templates/feature-dev.json +1 -0
  96. package/tmux/templates/full-stack.json +8 -0
  97. package/tmux/templates/i18n.json +34 -0
  98. package/tmux/templates/incident-response.json +36 -0
  99. package/tmux/templates/migration.json +1 -0
  100. package/tmux/templates/observability.json +35 -0
  101. package/tmux/templates/onboarding.json +33 -0
  102. package/tmux/templates/performance.json +35 -0
  103. package/tmux/templates/refactor.json +1 -0
  104. package/tmux/templates/release.json +35 -0
  105. package/tmux/templates/security-audit.json +8 -0
  106. package/tmux/templates/spike.json +34 -0
  107. package/tmux/templates/testing.json +1 -0
  108. package/tmux/tmux.conf +98 -9
  109. package/scripts/cct-cleanup.sh +0 -172
  110. package/scripts/cct-daemon.sh +0 -3189
  111. package/scripts/cct-doctor.sh +0 -414
  112. package/scripts/cct-loop.sh +0 -1332
  113. package/scripts/cct-pipeline.sh +0 -3844
  114. package/scripts/cct-session.sh +0 -284
  115. 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.10.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 "$@"
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # ╔═══════════════════════════════════════════════════════════════════════════╗
3
- # ║ shipwright init — Complete setup for Shipwright + Claude Code Teams
3
+ # ║ shipwright init — Complete setup for Shipwright + Shipwright
4
4
  # ║ ║
5
5
  # ║ Installs: tmux config, overlay, team & pipeline templates, Claude Code ║
6
6
  # ║ settings (with agent teams enabled), quality gate hooks, CLAUDE.md ║
@@ -8,7 +8,9 @@
8
8
  # ║ ║
9
9
  # ║ --deploy Detect platform and generate deployed.json template ║
10
10
  # ╚═══════════════════════════════════════════════════════════════════════════╝
11
+ VERSION="1.10.0"
11
12
  set -euo pipefail
13
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
12
14
 
13
15
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
16
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
@@ -23,6 +25,9 @@ DIM='\033[2m'
23
25
  BOLD='\033[1m'
24
26
  RESET='\033[0m'
25
27
 
28
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
29
+ # shellcheck source=lib/compat.sh
30
+ [[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
26
31
  info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
27
32
  success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
28
33
  warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
@@ -71,26 +76,95 @@ echo -e "${DIM}═════════════════════
71
76
  echo ""
72
77
 
73
78
  # ─── tmux.conf ────────────────────────────────────────────────────────────────
79
+ TOOK_FULL_TMUX_CONF=false
74
80
  if [[ -f "$REPO_DIR/tmux/tmux.conf" ]]; then
75
81
  if [[ -f "$HOME/.tmux.conf" ]]; then
76
82
  cp "$HOME/.tmux.conf" "$HOME/.tmux.conf.bak"
77
83
  warn "Backed up existing ~/.tmux.conf → ~/.tmux.conf.bak"
84
+ read -rp "$(echo -e "${CYAN}${BOLD}▸${RESET} Overwrite ~/.tmux.conf with the Shipwright config? [Y/n] ")" tmux_confirm
85
+ if [[ -z "$tmux_confirm" || "$(echo "$tmux_confirm" | tr '[:upper:]' '[:lower:]')" != "n" ]]; then
86
+ cp "$REPO_DIR/tmux/tmux.conf" "$HOME/.tmux.conf"
87
+ success "Installed ~/.tmux.conf"
88
+ TOOK_FULL_TMUX_CONF=true
89
+ else
90
+ info "Kept existing ~/.tmux.conf"
91
+ fi
92
+ else
93
+ cp "$REPO_DIR/tmux/tmux.conf" "$HOME/.tmux.conf"
94
+ success "Installed ~/.tmux.conf"
95
+ TOOK_FULL_TMUX_CONF=true
78
96
  fi
79
- cp "$REPO_DIR/tmux/tmux.conf" "$HOME/.tmux.conf"
80
- success "Installed ~/.tmux.conf"
81
97
  else
82
98
  warn "tmux.conf not found in package — skipping"
83
99
  fi
84
100
 
85
101
  # ─── Overlay ──────────────────────────────────────────────────────────────────
86
- if [[ -f "$REPO_DIR/tmux/claude-teams-overlay.conf" ]]; then
102
+ if [[ -f "$REPO_DIR/tmux/shipwright-overlay.conf" ]]; then
87
103
  mkdir -p "$HOME/.tmux"
88
- cp "$REPO_DIR/tmux/claude-teams-overlay.conf" "$HOME/.tmux/claude-teams-overlay.conf"
89
- success "Installed ~/.tmux/claude-teams-overlay.conf"
104
+ cp "$REPO_DIR/tmux/shipwright-overlay.conf" "$HOME/.tmux/shipwright-overlay.conf"
105
+ success "Installed ~/.tmux/shipwright-overlay.conf"
90
106
  else
91
107
  warn "Overlay not found in package — skipping"
92
108
  fi
93
109
 
110
+ # ─── Overlay injection ───────────────────────────────────────────────────────
111
+ # If user kept their own tmux.conf, ensure it sources the overlay
112
+ if [[ "$TOOK_FULL_TMUX_CONF" == "false" && -f "$HOME/.tmux.conf" ]]; then
113
+ if ! grep -q "shipwright-overlay" "$HOME/.tmux.conf" 2>/dev/null; then
114
+ read -rp "$(echo -e "${CYAN}${BOLD}▸${RESET} Add Shipwright overlay source to ~/.tmux.conf? [Y/n] ")" overlay_confirm
115
+ if [[ -z "$overlay_confirm" || "$(echo "$overlay_confirm" | tr '[:upper:]' '[:lower:]')" != "n" ]]; then
116
+ {
117
+ echo ""
118
+ echo "# Shipwright agent overlay"
119
+ echo "source-file -q ~/.tmux/shipwright-overlay.conf"
120
+ } >> "$HOME/.tmux.conf"
121
+ success "Appended overlay source to ~/.tmux.conf"
122
+ else
123
+ info "Skipped overlay injection. Add manually:"
124
+ echo -e " ${DIM}source-file -q ~/.tmux/shipwright-overlay.conf${RESET}"
125
+ fi
126
+ fi
127
+ fi
128
+
129
+ # ─── TPM (Tmux Plugin Manager) ────────────────────────────────────────────
130
+ if [[ ! -d "$HOME/.tmux/plugins/tpm" ]]; then
131
+ info "Installing TPM (Tmux Plugin Manager)..."
132
+ if git clone https://github.com/tmux-plugins/tpm "$HOME/.tmux/plugins/tpm" 2>/dev/null; then
133
+ success "TPM installed"
134
+ else
135
+ warn "Could not install TPM — install manually or run: shipwright tmux install"
136
+ fi
137
+ else
138
+ success "TPM already installed"
139
+ fi
140
+
141
+ # ─── Install TPM plugins ──────────────────────────────────────────────────
142
+ if [[ -x "$HOME/.tmux/plugins/tpm/bin/install_plugins" ]]; then
143
+ info "Installing tmux plugins..."
144
+ "$HOME/.tmux/plugins/tpm/bin/install_plugins" 2>/dev/null && \
145
+ success "Plugins installed (sensible, resurrect, continuum, yank, fzf)" || \
146
+ warn "Some plugins may not have installed — press prefix + I inside tmux"
147
+ fi
148
+
149
+ # ─── Reload tmux config if inside tmux ─────────────────────────────────────
150
+ if [[ -n "${TMUX:-}" ]]; then
151
+ tmux source-file "$HOME/.tmux.conf" 2>/dev/null && \
152
+ success "Reloaded tmux config (passthrough, extended-keys, plugins active)" || true
153
+ fi
154
+
155
+ # ─── Fix iTerm2 mouse reporting if disabled ────────────────────────────────
156
+ if [[ "${LC_TERMINAL:-${TERM_PROGRAM:-}}" == *iTerm* ]]; then
157
+ ITERM_MOUSE="$(defaults read com.googlecode.iterm2 "New Bookmarks" 2>/dev/null \
158
+ | grep '"Mouse Reporting"' | head -1 | grep -oE '[0-9]+' || echo "unknown")"
159
+ if [[ "$ITERM_MOUSE" == "0" ]]; then
160
+ warn "iTerm2 mouse reporting is disabled — tmux can't receive mouse clicks"
161
+ /usr/libexec/PlistBuddy -c "Set ':New Bookmarks:0:Mouse Reporting' 1" \
162
+ ~/Library/Preferences/com.googlecode.iterm2.plist 2>/dev/null && \
163
+ success "Enabled iTerm2 mouse reporting (open a new tab to activate)" || \
164
+ warn "Could not auto-fix — enable manually: Preferences → Profiles → Terminal → Report mouse clicks"
165
+ fi
166
+ fi
167
+
94
168
  # ─── Team Templates ──────────────────────────────────────────────────────────
95
169
  SHIPWRIGHT_DIR="$HOME/.shipwright"
96
170
  TEMPLATES_SRC="$REPO_DIR/tmux/templates"
@@ -101,10 +175,10 @@ if [[ -d "$TEMPLATES_SRC" ]]; then
101
175
  cp "$tpl" "$SHIPWRIGHT_DIR/templates/$(basename "$tpl")"
102
176
  done
103
177
  # Also install to legacy path for backward compatibility
104
- mkdir -p "$HOME/.claude-teams/templates"
178
+ mkdir -p "$HOME/.shipwright/templates"
105
179
  for tpl in "$TEMPLATES_SRC"/*.json; do
106
180
  [[ -f "$tpl" ]] || continue
107
- cp "$tpl" "$HOME/.claude-teams/templates/$(basename "$tpl")"
181
+ cp "$tpl" "$HOME/.shipwright/templates/$(basename "$tpl")"
108
182
  done
109
183
  tpl_count=$(find "$SHIPWRIGHT_DIR/templates" -name '*.json' -type f 2>/dev/null | wc -l | tr -d ' ')
110
184
  success "Installed ${tpl_count} team templates → ~/.shipwright/templates/"
@@ -150,7 +224,10 @@ if [[ -f "$SETTINGS_FILE" ]]; then
150
224
  fi
151
225
  fi
152
226
  elif [[ -f "$SETTINGS_TEMPLATE" ]]; then
153
- cp "$SETTINGS_TEMPLATE" "$SETTINGS_FILE"
227
+ # Strip JSONC comments (// lines) so jq can parse on subsequent runs
228
+ tmp=$(mktemp)
229
+ sed '/^[[:space:]]*\/\//d' "$SETTINGS_TEMPLATE" > "$tmp"
230
+ mv "$tmp" "$SETTINGS_FILE"
154
231
  success "Installed ~/.claude/settings.json (with agent teams enabled)"
155
232
  else
156
233
  # Create minimal settings.json with agent teams
@@ -189,6 +266,62 @@ if [[ -d "$HOOKS_SRC" ]]; then
189
266
  fi
190
267
  fi
191
268
 
269
+ # ─── Wire Hooks into settings.json ──────────────────────────────────────────
270
+ # Ensure each installed hook has a matching event config in settings.json
271
+ if [[ -f "$SETTINGS_FILE" ]] && jq -e '.' "$SETTINGS_FILE" &>/dev/null; then
272
+ hooks_wired=0
273
+
274
+ # Ensure .hooks object exists
275
+ if ! jq -e '.hooks' "$SETTINGS_FILE" &>/dev/null; then
276
+ tmp=$(mktemp)
277
+ jq '.hooks = {}' "$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
278
+ fi
279
+
280
+ # TeammateIdle
281
+ if [[ -f "$CLAUDE_DIR/hooks/teammate-idle.sh" ]] && ! jq -e '.hooks.TeammateIdle' "$SETTINGS_FILE" &>/dev/null; then
282
+ tmp=$(mktemp)
283
+ jq '.hooks.TeammateIdle = [{"hooks": [{"type": "command", "command": "~/.claude/hooks/teammate-idle.sh", "timeout": 30, "statusMessage": "Running typecheck before idle..."}]}]' \
284
+ "$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
285
+ hooks_wired=$((hooks_wired + 1))
286
+ fi
287
+
288
+ # TaskCompleted
289
+ if [[ -f "$CLAUDE_DIR/hooks/task-completed.sh" ]] && ! jq -e '.hooks.TaskCompleted' "$SETTINGS_FILE" &>/dev/null; then
290
+ tmp=$(mktemp)
291
+ jq '.hooks.TaskCompleted = [{"hooks": [{"type": "command", "command": "~/.claude/hooks/task-completed.sh", "timeout": 60, "statusMessage": "Running quality checks..."}]}]' \
292
+ "$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
293
+ hooks_wired=$((hooks_wired + 1))
294
+ fi
295
+
296
+ # Notification
297
+ if [[ -f "$CLAUDE_DIR/hooks/notify-idle.sh" ]] && ! jq -e '.hooks.Notification' "$SETTINGS_FILE" &>/dev/null; then
298
+ tmp=$(mktemp)
299
+ jq '.hooks.Notification = [{"hooks": [{"type": "command", "command": "~/.claude/hooks/notify-idle.sh", "async": true}]}]' \
300
+ "$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
301
+ hooks_wired=$((hooks_wired + 1))
302
+ fi
303
+
304
+ # PreCompact
305
+ if [[ -f "$CLAUDE_DIR/hooks/pre-compact-save.sh" ]] && ! jq -e '.hooks.PreCompact' "$SETTINGS_FILE" &>/dev/null; then
306
+ tmp=$(mktemp)
307
+ jq '.hooks.PreCompact = [{"matcher": "auto", "hooks": [{"type": "command", "command": "~/.claude/hooks/pre-compact-save.sh", "statusMessage": "Saving context before compaction..."}]}]' \
308
+ "$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
309
+ hooks_wired=$((hooks_wired + 1))
310
+ fi
311
+
312
+ # SessionStart
313
+ if [[ -f "$CLAUDE_DIR/hooks/session-start.sh" ]] && ! jq -e '.hooks.SessionStart' "$SETTINGS_FILE" &>/dev/null; then
314
+ tmp=$(mktemp)
315
+ jq '.hooks.SessionStart = [{"hooks": [{"type": "command", "command": "~/.claude/hooks/session-start.sh", "timeout": 5}]}]' \
316
+ "$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
317
+ hooks_wired=$((hooks_wired + 1))
318
+ fi
319
+
320
+ if [[ $hooks_wired -gt 0 ]]; then
321
+ success "Wired ${hooks_wired} hooks into settings.json"
322
+ fi
323
+ fi
324
+
192
325
  # ─── CLAUDE.md — Global agent instructions ────────────────────────────────────
193
326
  CLAUDE_MD_SRC="$REPO_DIR/claude-code/CLAUDE.md.shipwright"
194
327
  GLOBAL_CLAUDE_MD="$CLAUDE_DIR/CLAUDE.md"
@@ -236,7 +369,7 @@ fi
236
369
  echo ""
237
370
  echo -e "${CYAN}${BOLD}Running doctor...${RESET}"
238
371
  echo ""
239
- "$SCRIPT_DIR/cct-doctor.sh" || true
372
+ "$SCRIPT_DIR/sw-doctor.sh" || true
240
373
 
241
374
  echo ""
242
375
  echo -e "${BOLD}Quick start:${RESET}"
@@ -316,7 +449,7 @@ else
316
449
 
317
450
  # Confirm with user
318
451
  read -rp "$(echo -e "${CYAN}${BOLD}▸${RESET} Configure deploy for ${BOLD}${DEPLOY_PLATFORM}${RESET}? [Y/n] ")" confirm
319
- if [[ "${confirm,,}" == "n" ]]; then
452
+ if [[ "$(echo "$confirm" | tr '[:upper:]' '[:lower:]')" == "n" ]]; then
320
453
  info "Aborted. Use --platform to specify manually."
321
454
  exit 0
322
455
  fi