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,522 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright init — Complete setup for Shipwright + Shipwright ║
4
+ # ║ ║
5
+ # ║ Installs: tmux config, overlay, team & pipeline templates, Claude Code ║
6
+ # ║ settings (with agent teams enabled), quality gate hooks, CLAUDE.md ║
7
+ # ║ agent instructions (global + per-repo). Runs doctor at the end. ║
8
+ # ║ ║
9
+ # ║ --deploy Detect platform and generate deployed.json template ║
10
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
11
+ VERSION="1.9.0"
12
+ set -euo pipefail
13
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
+ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
17
+ ADAPTERS_DIR="$SCRIPT_DIR/adapters"
18
+
19
+ # ─── Colors ──────────────────────────────────────────────────────────────────
20
+ CYAN='\033[38;2;0;212;255m'
21
+ GREEN='\033[38;2;74;222;128m'
22
+ YELLOW='\033[38;2;250;204;21m'
23
+ RED='\033[38;2;248;113;113m'
24
+ DIM='\033[2m'
25
+ BOLD='\033[1m'
26
+ RESET='\033[0m'
27
+
28
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
29
+ # shellcheck source=lib/compat.sh
30
+ [[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
31
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
32
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
33
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
34
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
35
+
36
+ # ─── Flag parsing ───────────────────────────────────────────────────────────
37
+ DEPLOY_SETUP=false
38
+ DEPLOY_PLATFORM=""
39
+ SKIP_CLAUDE_MD=false
40
+
41
+ while [[ $# -gt 0 ]]; do
42
+ case "$1" in
43
+ --deploy)
44
+ DEPLOY_SETUP=true
45
+ shift
46
+ ;;
47
+ --platform)
48
+ DEPLOY_PLATFORM="${2:-}"
49
+ [[ -z "$DEPLOY_PLATFORM" ]] && { error "Missing value for --platform"; exit 1; }
50
+ shift 2
51
+ ;;
52
+ --no-claude-md)
53
+ SKIP_CLAUDE_MD=true
54
+ shift
55
+ ;;
56
+ --help|-h)
57
+ echo "Usage: shipwright init [--deploy] [--platform vercel|fly|railway|docker] [--no-claude-md]"
58
+ echo ""
59
+ echo "Options:"
60
+ echo " --deploy Detect deploy platform and generate deployed.json"
61
+ echo " --platform PLATFORM Skip detection, use specified platform"
62
+ echo " --no-claude-md Skip creating .claude/CLAUDE.md"
63
+ echo " --help, -h Show this help"
64
+ exit 0
65
+ ;;
66
+ *)
67
+ warn "Unknown option: $1"
68
+ shift
69
+ ;;
70
+ esac
71
+ done
72
+
73
+ echo ""
74
+ echo -e "${CYAN}${BOLD}shipwright init${RESET} — Complete setup"
75
+ echo -e "${DIM}══════════════════════════════════════════${RESET}"
76
+ echo ""
77
+
78
+ # ─── tmux.conf ────────────────────────────────────────────────────────────────
79
+ TOOK_FULL_TMUX_CONF=false
80
+ if [[ -f "$REPO_DIR/tmux/tmux.conf" ]]; then
81
+ if [[ -f "$HOME/.tmux.conf" ]]; then
82
+ cp "$HOME/.tmux.conf" "$HOME/.tmux.conf.bak"
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
96
+ fi
97
+ else
98
+ warn "tmux.conf not found in package — skipping"
99
+ fi
100
+
101
+ # ─── Overlay ──────────────────────────────────────────────────────────────────
102
+ if [[ -f "$REPO_DIR/tmux/shipwright-overlay.conf" ]]; then
103
+ mkdir -p "$HOME/.tmux"
104
+ cp "$REPO_DIR/tmux/shipwright-overlay.conf" "$HOME/.tmux/shipwright-overlay.conf"
105
+ success "Installed ~/.tmux/shipwright-overlay.conf"
106
+ else
107
+ warn "Overlay not found in package — skipping"
108
+ fi
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
+
168
+ # ─── Team Templates ──────────────────────────────────────────────────────────
169
+ SHIPWRIGHT_DIR="$HOME/.shipwright"
170
+ TEMPLATES_SRC="$REPO_DIR/tmux/templates"
171
+ if [[ -d "$TEMPLATES_SRC" ]]; then
172
+ mkdir -p "$SHIPWRIGHT_DIR/templates"
173
+ for tpl in "$TEMPLATES_SRC"/*.json; do
174
+ [[ -f "$tpl" ]] || continue
175
+ cp "$tpl" "$SHIPWRIGHT_DIR/templates/$(basename "$tpl")"
176
+ done
177
+ # Also install to legacy path for backward compatibility
178
+ mkdir -p "$HOME/.shipwright/templates"
179
+ for tpl in "$TEMPLATES_SRC"/*.json; do
180
+ [[ -f "$tpl" ]] || continue
181
+ cp "$tpl" "$HOME/.shipwright/templates/$(basename "$tpl")"
182
+ done
183
+ tpl_count=$(find "$SHIPWRIGHT_DIR/templates" -name '*.json' -type f 2>/dev/null | wc -l | tr -d ' ')
184
+ success "Installed ${tpl_count} team templates → ~/.shipwright/templates/"
185
+ fi
186
+
187
+ # ─── Pipeline Templates ──────────────────────────────────────────────────────
188
+ PIPELINES_SRC="$REPO_DIR/templates/pipelines"
189
+ if [[ -d "$PIPELINES_SRC" ]]; then
190
+ mkdir -p "$SHIPWRIGHT_DIR/pipelines"
191
+ for tpl in "$PIPELINES_SRC"/*.json; do
192
+ [[ -f "$tpl" ]] || continue
193
+ cp "$tpl" "$SHIPWRIGHT_DIR/pipelines/$(basename "$tpl")"
194
+ done
195
+ pip_count=$(find "$SHIPWRIGHT_DIR/pipelines" -name '*.json' -type f 2>/dev/null | wc -l | tr -d ' ')
196
+ success "Installed ${pip_count} pipeline templates → ~/.shipwright/pipelines/"
197
+ fi
198
+
199
+ # ─── Claude Code Settings ────────────────────────────────────────────────────
200
+ CLAUDE_DIR="$HOME/.claude"
201
+ SETTINGS_FILE="$CLAUDE_DIR/settings.json"
202
+ SETTINGS_TEMPLATE="$REPO_DIR/claude-code/settings.json.template"
203
+
204
+ mkdir -p "$CLAUDE_DIR"
205
+
206
+ if [[ -f "$SETTINGS_FILE" ]]; then
207
+ # Settings exists — check for agent teams env var
208
+ if grep -q 'CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS' "$SETTINGS_FILE" 2>/dev/null; then
209
+ success "Agent teams already enabled in settings.json"
210
+ else
211
+ # Try to add using jq
212
+ if jq -e '.env' "$SETTINGS_FILE" &>/dev/null 2>&1; then
213
+ tmp=$(mktemp)
214
+ jq '.env["CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS"] = "1"' "$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
215
+ success "Enabled agent teams in existing settings.json"
216
+ elif jq -e '.' "$SETTINGS_FILE" &>/dev/null 2>&1; then
217
+ tmp=$(mktemp)
218
+ jq '. + {"env": {"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"}}' "$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
219
+ success "Added agent teams env to settings.json"
220
+ else
221
+ warn "Could not auto-configure settings.json (JSONC detected)"
222
+ echo -e " ${DIM}Add to ~/.claude/settings.json:${RESET}"
223
+ echo -e " ${DIM}\"env\": { \"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS\": \"1\" }${RESET}"
224
+ fi
225
+ fi
226
+ elif [[ -f "$SETTINGS_TEMPLATE" ]]; then
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"
231
+ success "Installed ~/.claude/settings.json (with agent teams enabled)"
232
+ else
233
+ # Create minimal settings.json with agent teams
234
+ cat > "$SETTINGS_FILE" << 'SETTINGS_EOF'
235
+ {
236
+ "hooks": {},
237
+ "env": {
238
+ "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1",
239
+ "CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY": "5",
240
+ "CLAUDE_CODE_AUTOCOMPACT_PCT_OVERRIDE": "70",
241
+ "CLAUDE_CODE_SUBAGENT_MODEL": "haiku"
242
+ }
243
+ }
244
+ SETTINGS_EOF
245
+ success "Created ~/.claude/settings.json with agent teams enabled"
246
+ fi
247
+
248
+ # ─── Hooks ────────────────────────────────────────────────────────────────────
249
+ HOOKS_SRC="$REPO_DIR/claude-code/hooks"
250
+ if [[ -d "$HOOKS_SRC" ]]; then
251
+ mkdir -p "$CLAUDE_DIR/hooks"
252
+ hook_count=0
253
+ for hook in "$HOOKS_SRC"/*.sh; do
254
+ [[ -f "$hook" ]] || continue
255
+ dest="$CLAUDE_DIR/hooks/$(basename "$hook")"
256
+ if [[ ! -f "$dest" ]]; then
257
+ cp "$hook" "$dest"
258
+ chmod +x "$dest"
259
+ hook_count=$((hook_count + 1))
260
+ fi
261
+ done
262
+ if [[ $hook_count -gt 0 ]]; then
263
+ success "Installed ${hook_count} quality gate hooks → ~/.claude/hooks/"
264
+ else
265
+ info "Hooks already installed — skipping"
266
+ fi
267
+ fi
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
+
325
+ # ─── CLAUDE.md — Global agent instructions ────────────────────────────────────
326
+ CLAUDE_MD_SRC="$REPO_DIR/claude-code/CLAUDE.md.shipwright"
327
+ GLOBAL_CLAUDE_MD="$CLAUDE_DIR/CLAUDE.md"
328
+
329
+ if [[ "$SKIP_CLAUDE_MD" == "false" && -f "$CLAUDE_MD_SRC" ]]; then
330
+ if [[ -f "$GLOBAL_CLAUDE_MD" ]]; then
331
+ if grep -q "Shipwright" "$GLOBAL_CLAUDE_MD" 2>/dev/null; then
332
+ info "~/.claude/CLAUDE.md already contains Shipwright instructions"
333
+ else
334
+ { echo ""; echo "---"; echo ""; cat "$CLAUDE_MD_SRC"; } >> "$GLOBAL_CLAUDE_MD"
335
+ success "Appended Shipwright instructions to ~/.claude/CLAUDE.md"
336
+ fi
337
+ else
338
+ cp "$CLAUDE_MD_SRC" "$GLOBAL_CLAUDE_MD"
339
+ success "Installed ~/.claude/CLAUDE.md"
340
+ fi
341
+ fi
342
+
343
+ # ─── CLAUDE.md — Per-repo agent instructions ─────────────────────────────────
344
+ LOCAL_CLAUDE_MD=".claude/CLAUDE.md"
345
+
346
+ if [[ "$SKIP_CLAUDE_MD" == "false" && -f "$CLAUDE_MD_SRC" ]]; then
347
+ if [[ -f "$LOCAL_CLAUDE_MD" ]]; then
348
+ if grep -q "Shipwright" "$LOCAL_CLAUDE_MD" 2>/dev/null; then
349
+ info ".claude/CLAUDE.md already contains Shipwright instructions"
350
+ else
351
+ { echo ""; echo "---"; echo ""; cat "$CLAUDE_MD_SRC"; } >> "$LOCAL_CLAUDE_MD"
352
+ success "Appended Shipwright instructions to ${LOCAL_CLAUDE_MD}"
353
+ fi
354
+ else
355
+ mkdir -p ".claude"
356
+ cp "$CLAUDE_MD_SRC" "$LOCAL_CLAUDE_MD"
357
+ success "Created ${LOCAL_CLAUDE_MD} with Shipwright agent instructions"
358
+ fi
359
+ fi
360
+
361
+ # ─── Reload tmux if inside a session ──────────────────────────────────────────
362
+ if [[ -n "${TMUX:-}" ]]; then
363
+ tmux source-file "$HOME/.tmux.conf" 2>/dev/null && \
364
+ success "Reloaded tmux config" || \
365
+ warn "Could not reload tmux config (reload manually with prefix + r)"
366
+ fi
367
+
368
+ # ─── Validation ───────────────────────────────────────────────────────────────
369
+ echo ""
370
+ echo -e "${CYAN}${BOLD}Running doctor...${RESET}"
371
+ echo ""
372
+ "$SCRIPT_DIR/sw-doctor.sh" || true
373
+
374
+ echo ""
375
+ echo -e "${BOLD}Quick start:${RESET}"
376
+ if [[ -z "${TMUX:-}" ]]; then
377
+ echo -e " ${DIM}1.${RESET} tmux new -s dev"
378
+ echo -e " ${DIM}2.${RESET} shipwright session my-feature --template feature-dev"
379
+ else
380
+ echo -e " ${DIM}1.${RESET} shipwright session my-feature --template feature-dev"
381
+ fi
382
+ echo ""
383
+
384
+ # ─── Deploy setup (--deploy) ─────────────────────────────────────────────────
385
+ [[ "$DEPLOY_SETUP" == "false" ]] && exit 0
386
+
387
+ echo -e "${CYAN}${BOLD}Deploy Setup${RESET}"
388
+ echo -e "${DIM}══════════════════════════════════════════${RESET}"
389
+ echo ""
390
+
391
+ # Platform detection
392
+ detect_deploy_platform() {
393
+ local detected=""
394
+
395
+ for adapter_file in "$ADAPTERS_DIR"/*-deploy.sh; do
396
+ [[ -f "$adapter_file" ]] || continue
397
+ # Source the adapter in a subshell to get detection
398
+ if ( source "$adapter_file" && detect_platform ); then
399
+ local name
400
+ name=$(basename "$adapter_file" | sed 's/-deploy\.sh$//')
401
+ if [[ -n "$detected" ]]; then
402
+ detected="$detected $name"
403
+ else
404
+ detected="$name"
405
+ fi
406
+ fi
407
+ done
408
+
409
+ echo "$detected"
410
+ }
411
+
412
+ if [[ -n "$DEPLOY_PLATFORM" ]]; then
413
+ # User specified --platform, validate it
414
+ if [[ ! -f "$ADAPTERS_DIR/${DEPLOY_PLATFORM}-deploy.sh" ]]; then
415
+ error "Unknown platform: $DEPLOY_PLATFORM"
416
+ echo -e " Available: vercel, fly, railway, docker"
417
+ exit 1
418
+ fi
419
+ info "Using specified platform: ${BOLD}${DEPLOY_PLATFORM}${RESET}"
420
+ else
421
+ info "Detecting deploy platform..."
422
+ detected=$(detect_deploy_platform)
423
+
424
+ if [[ -z "$detected" ]]; then
425
+ warn "No platform detected in current directory"
426
+ echo ""
427
+ echo -e " Supported platforms:"
428
+ echo -e " ${CYAN}vercel${RESET} — vercel.json or .vercel/"
429
+ echo -e " ${CYAN}fly${RESET} — fly.toml"
430
+ echo -e " ${CYAN}railway${RESET} — railway.toml or .railway/"
431
+ echo -e " ${CYAN}docker${RESET} — Dockerfile or docker-compose.yml"
432
+ echo ""
433
+ echo -e " Specify manually: ${DIM}shipwright init --deploy --platform vercel${RESET}"
434
+ exit 1
435
+ fi
436
+
437
+ # If multiple platforms detected, use the first and warn
438
+ platform_count=$(echo "$detected" | wc -w | tr -d ' ')
439
+ DEPLOY_PLATFORM=$(echo "$detected" | awk '{print $1}')
440
+
441
+ if [[ "$platform_count" -gt 1 ]]; then
442
+ warn "Multiple platforms detected: ${BOLD}${detected}${RESET}"
443
+ info "Using: ${BOLD}${DEPLOY_PLATFORM}${RESET}"
444
+ echo -e " ${DIM}Override with: shipwright init --deploy --platform <name>${RESET}"
445
+ echo ""
446
+ else
447
+ success "Detected platform: ${BOLD}${DEPLOY_PLATFORM}${RESET}"
448
+ fi
449
+
450
+ # Confirm with user
451
+ read -rp "$(echo -e "${CYAN}${BOLD}▸${RESET} Configure deploy for ${BOLD}${DEPLOY_PLATFORM}${RESET}? [Y/n] ")" confirm
452
+ if [[ "$(echo "$confirm" | tr '[:upper:]' '[:lower:]')" == "n" ]]; then
453
+ info "Aborted. Use --platform to specify manually."
454
+ exit 0
455
+ fi
456
+ fi
457
+
458
+ # Source the adapter to get command values
459
+ ADAPTER_FILE="$ADAPTERS_DIR/${DEPLOY_PLATFORM}-deploy.sh"
460
+ source "$ADAPTER_FILE"
461
+
462
+ staging_cmd=$(get_staging_cmd)
463
+ production_cmd=$(get_production_cmd)
464
+ rollback_cmd=$(get_rollback_cmd)
465
+ health_url=$(get_health_url)
466
+ smoke_cmd=$(get_smoke_cmd)
467
+
468
+ # Generate deployed.json from template
469
+ TEMPLATE_SRC="$REPO_DIR/templates/pipelines/deployed.json"
470
+ TEMPLATE_DST=".claude/pipeline-templates/deployed.json"
471
+
472
+ if [[ ! -f "$TEMPLATE_SRC" ]]; then
473
+ error "Template not found: $TEMPLATE_SRC"
474
+ exit 1
475
+ fi
476
+
477
+ mkdir -p ".claude/pipeline-templates"
478
+
479
+ # Use jq to properly fill in the template values
480
+ jq --arg staging "$staging_cmd" \
481
+ --arg production "$production_cmd" \
482
+ --arg rollback "$rollback_cmd" \
483
+ --arg health "$health_url" \
484
+ --arg smoke "$smoke_cmd" \
485
+ --arg platform "$DEPLOY_PLATFORM" \
486
+ '
487
+ .name = "deployed-" + $platform |
488
+ .description = "Autonomous pipeline with " + $platform + " deploy — generated by shipwright init --deploy" |
489
+ (.stages[] | select(.id == "deploy") | .config) |= {
490
+ staging_cmd: $staging,
491
+ production_cmd: $production,
492
+ rollback_cmd: $rollback
493
+ } |
494
+ (.stages[] | select(.id == "validate") | .config) |= {
495
+ smoke_cmd: $smoke,
496
+ health_url: $health,
497
+ close_issue: true
498
+ } |
499
+ (.stages[] | select(.id == "monitor") | .config) |= (
500
+ .health_url = $health |
501
+ .rollback_cmd = $rollback
502
+ )
503
+ ' "$TEMPLATE_SRC" > "$TEMPLATE_DST"
504
+
505
+ success "Generated ${BOLD}${TEMPLATE_DST}${RESET}"
506
+
507
+ echo ""
508
+ echo -e "${BOLD}Deploy configured for ${DEPLOY_PLATFORM}!${RESET}"
509
+ echo ""
510
+ echo -e "${BOLD}Commands configured:${RESET}"
511
+ echo -e " ${DIM}staging:${RESET} $staging_cmd"
512
+ echo -e " ${DIM}production:${RESET} $production_cmd"
513
+ echo -e " ${DIM}rollback:${RESET} $rollback_cmd"
514
+ if [[ -n "$health_url" ]]; then
515
+ echo -e " ${DIM}health:${RESET} $health_url"
516
+ fi
517
+ echo ""
518
+ echo -e "${BOLD}Usage:${RESET}"
519
+ echo -e " ${DIM}shipwright pipeline start --issue 42 --template .claude/pipeline-templates/deployed.json${RESET}"
520
+ echo ""
521
+ echo -e "${DIM}Edit ${TEMPLATE_DST} to customize deploy commands, gates, or thresholds.${RESET}"
522
+ echo ""