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,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 ""
|