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,541 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ sw-session.sh — Launch a Claude Code team session in a new tmux window║
4
+ # ║ ║
5
+ # ║ Uses new-window (NOT split-window) to avoid the tmux send-keys race ║
6
+ # ║ condition that affects 4+ agents. See KNOWN-ISSUES.md for details. ║
7
+ # ║ ║
8
+ # ║ Supports --template to scaffold from a team template and --terminal ║
9
+ # ║ to select a terminal adapter (tmux, iterm2, wezterm). ║
10
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
11
+ VERSION="1.10.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
+
17
+ # ─── Colors ──────────────────────────────────────────────────────────────────
18
+ CYAN='\033[38;2;0;212;255m'
19
+ PURPLE='\033[38;2;124;58;237m'
20
+ GREEN='\033[38;2;74;222;128m'
21
+ YELLOW='\033[38;2;250;204;21m'
22
+ RED='\033[38;2;248;113;113m'
23
+ DIM='\033[2m'
24
+ BOLD='\033[1m'
25
+ RESET='\033[0m'
26
+
27
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
28
+ # shellcheck source=lib/compat.sh
29
+ [[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
30
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
31
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
32
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
33
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
34
+
35
+ # ─── Parse Arguments ────────────────────────────────────────────────────────
36
+
37
+ TEAM_NAME=""
38
+ TEMPLATE_NAME=""
39
+ TERMINAL_ADAPTER=""
40
+ AUTO_LAUNCH=true
41
+ DRY_RUN=false
42
+ SKIP_PERMISSIONS="auto"
43
+ GOAL=""
44
+
45
+ while [[ $# -gt 0 ]]; do
46
+ case "$1" in
47
+ --template|-t)
48
+ TEMPLATE_NAME="${2:-}"
49
+ [[ -z "$TEMPLATE_NAME" ]] && { error "Missing template name after --template"; exit 1; }
50
+ shift 2
51
+ ;;
52
+ --terminal)
53
+ TERMINAL_ADAPTER="${2:-}"
54
+ [[ -z "$TERMINAL_ADAPTER" ]] && { error "Missing adapter name after --terminal"; exit 1; }
55
+ shift 2
56
+ ;;
57
+ --goal|-g)
58
+ GOAL="${2:-}"
59
+ [[ -z "$GOAL" ]] && { error "Missing goal after --goal"; exit 1; }
60
+ shift 2
61
+ ;;
62
+ --no-launch)
63
+ AUTO_LAUNCH=false
64
+ shift
65
+ ;;
66
+ --dry-run)
67
+ DRY_RUN=true
68
+ shift
69
+ ;;
70
+ --skip-permissions)
71
+ SKIP_PERMISSIONS=true
72
+ shift
73
+ ;;
74
+ --no-skip-permissions)
75
+ SKIP_PERMISSIONS=false
76
+ shift
77
+ ;;
78
+ --help|-h)
79
+ echo -e "${CYAN}${BOLD}shipwright session${RESET} — Create and launch a team session"
80
+ echo ""
81
+ echo -e "${BOLD}USAGE${RESET}"
82
+ echo -e " shipwright session [name] [--template <name>] [--goal \"...\"]"
83
+ echo ""
84
+ echo -e "${BOLD}OPTIONS${RESET}"
85
+ echo -e " ${CYAN}--template, -t${RESET} <name> Use a team template (see: shipwright templates list)"
86
+ echo -e " ${CYAN}--goal, -g${RESET} <text> Goal for the team (what to build/fix/refactor)"
87
+ echo -e " ${CYAN}--terminal${RESET} <adapter> Terminal adapter: tmux (default), iterm2, wezterm"
88
+ echo -e " ${CYAN}--no-launch${RESET} Create window only, don't auto-launch Claude"
89
+ echo -e " ${CYAN}--skip-permissions${RESET} Pass --dangerously-skip-permissions (default with agents)"
90
+ echo -e " ${CYAN}--no-skip-permissions${RESET} Require permission prompts even with agents"
91
+ echo -e " ${CYAN}--dry-run${RESET} Print team prompt and launcher script, don't create anything"
92
+ echo ""
93
+ echo -e "${BOLD}EXAMPLES${RESET}"
94
+ echo -e " ${DIM}shipwright session auth-refactor -t feature-dev -g \"Refactor auth to use JWT\"${RESET}"
95
+ echo -e " ${DIM}shipwright session my-feature --template feature-dev${RESET}"
96
+ echo -e " ${DIM}shipwright session bugfix -t bug-fix -g \"Fix login timeout issue\"${RESET}"
97
+ echo -e " ${DIM}shipwright session explore --no-launch${RESET}"
98
+ exit 0
99
+ ;;
100
+ -*)
101
+ error "Unknown option: $1"
102
+ exit 1
103
+ ;;
104
+ *)
105
+ # Positional: team name
106
+ [[ -z "$TEAM_NAME" ]] && TEAM_NAME="$1" || { error "Unexpected argument: $1"; exit 1; }
107
+ shift
108
+ ;;
109
+ esac
110
+ done
111
+
112
+ TEAM_NAME="${TEAM_NAME:-team-$(date +%s)}"
113
+ WINDOW_NAME="claude-${TEAM_NAME}"
114
+
115
+ # ─── Template Suggestion ──────────────────────────────────────────────────────
116
+
117
+ suggest_template() {
118
+ local goal="$1" templates_dir="$2"
119
+ local best="" best_score=0
120
+ [[ -d "$templates_dir" ]] || return 1
121
+
122
+ local goal_lower
123
+ goal_lower=$(echo "$goal" | tr '[:upper:]' '[:lower:]')
124
+
125
+ for tpl in "$templates_dir"/*.json; do
126
+ [[ -f "$tpl" ]] || continue
127
+ local name score=0
128
+ name=$(jq -r '.name // ""' "$tpl" 2>/dev/null) || continue
129
+
130
+ while IFS= read -r kw; do
131
+ [[ -z "$kw" ]] && continue
132
+ if echo "$goal_lower" | grep -qi "$kw"; then
133
+ score=$((score + 1))
134
+ fi
135
+ done < <(jq -r '(.keywords // []) | .[]' "$tpl" 2>/dev/null)
136
+
137
+ if [[ $score -gt $best_score ]]; then
138
+ best_score=$score
139
+ best="$name"
140
+ fi
141
+ done
142
+
143
+ [[ $best_score -gt 0 ]] && echo "$best"
144
+ }
145
+
146
+ # ─── Template Auto-Suggestion ────────────────────────────────────────────
147
+ if [[ -z "$TEMPLATE_NAME" && -n "$GOAL" ]]; then
148
+ REPO_TPL_DIR="$(cd "$SCRIPT_DIR/../tmux/templates" 2>/dev/null && pwd)" || REPO_TPL_DIR=""
149
+ USER_TPL_DIR="${HOME}/.shipwright/templates"
150
+ SUGGESTED=""
151
+
152
+ if [[ -d "$USER_TPL_DIR" ]]; then
153
+ SUGGESTED=$(suggest_template "$GOAL" "$USER_TPL_DIR") || true
154
+ fi
155
+ if [[ -z "$SUGGESTED" && -n "$REPO_TPL_DIR" ]]; then
156
+ SUGGESTED=$(suggest_template "$GOAL" "$REPO_TPL_DIR") || true
157
+ fi
158
+
159
+ if [[ -n "$SUGGESTED" ]]; then
160
+ info "Auto-suggesting template: ${PURPLE}${BOLD}${SUGGESTED}${RESET}"
161
+ TEMPLATE_NAME="$SUGGESTED"
162
+ fi
163
+ fi
164
+
165
+ # ─── Template Loading ───────────────────────────────────────────────────────
166
+
167
+ TEMPLATE_FILE=""
168
+ TEMPLATE_LAYOUT=""
169
+ TEMPLATE_LAYOUT_STYLE=""
170
+ TEMPLATE_MAIN_PANE_PERCENT=""
171
+ TEMPLATE_DESC=""
172
+ TEMPLATE_AGENTS=() # Populated as "name|role|focus" entries
173
+
174
+ if [[ -n "$TEMPLATE_NAME" ]]; then
175
+ # Search for template: user dir first, then repo dir
176
+ USER_TEMPLATES_DIR="${HOME}/.shipwright/templates"
177
+ REPO_TEMPLATES_DIR="$(cd "$SCRIPT_DIR/../tmux/templates" 2>/dev/null && pwd)" || REPO_TEMPLATES_DIR=""
178
+
179
+ TEMPLATE_NAME="${TEMPLATE_NAME%.json}"
180
+
181
+ if [[ -f "$USER_TEMPLATES_DIR/${TEMPLATE_NAME}.json" ]]; then
182
+ TEMPLATE_FILE="$USER_TEMPLATES_DIR/${TEMPLATE_NAME}.json"
183
+ elif [[ -n "$REPO_TEMPLATES_DIR" && -f "$REPO_TEMPLATES_DIR/${TEMPLATE_NAME}.json" ]]; then
184
+ TEMPLATE_FILE="$REPO_TEMPLATES_DIR/${TEMPLATE_NAME}.json"
185
+ else
186
+ error "Template '${TEMPLATE_NAME}' not found."
187
+ echo -e " Run ${DIM}shipwright templates list${RESET} to see available templates."
188
+ exit 1
189
+ fi
190
+
191
+ info "Loading template: ${PURPLE}${BOLD}${TEMPLATE_NAME}${RESET}"
192
+
193
+ # Parse template — single jq call extracts all fields + agents in one pass
194
+ if command -v jq &>/dev/null; then
195
+ # Single jq call: outputs metadata lines then agent lines
196
+ # Format: META<tab>field<tab>value for metadata, AGENT<tab>name|role|focus for agents
197
+ while IFS=$'\t' read -r tag key value; do
198
+ case "$tag" in
199
+ META)
200
+ case "$key" in
201
+ description) TEMPLATE_DESC="$value" ;;
202
+ layout) TEMPLATE_LAYOUT="$value" ;;
203
+ layout_style) TEMPLATE_LAYOUT_STYLE="$value" ;;
204
+ main_pane_percent) TEMPLATE_MAIN_PANE_PERCENT="$value" ;;
205
+ esac
206
+ ;;
207
+ AGENT) [[ -n "$key" ]] && TEMPLATE_AGENTS+=("$key") ;;
208
+ esac
209
+ done < <(jq -r '
210
+ "META\tdescription\t\(.description // "")",
211
+ "META\tlayout\t\(.layout // "tiled")",
212
+ "META\tlayout_style\t\(.layout_style // "")",
213
+ "META\tmain_pane_percent\t\(.main_pane_percent // "")",
214
+ (.agents // [] | .[] | "AGENT\t\(.name)|\(.role // "")|\(.focus // "")\t")
215
+ ' "$TEMPLATE_FILE")
216
+ else
217
+ error "jq is required for template parsing."
218
+ echo -e " ${DIM}brew install jq${RESET}"
219
+ exit 1
220
+ fi
221
+
222
+ # Validate template parsed correctly — if jq failed, TEMPLATE_AGENTS is empty
223
+ if [[ ${#TEMPLATE_AGENTS[@]} -eq 0 ]]; then
224
+ error "Template '${TEMPLATE_NAME}' parsed with no agents. Check template JSON."
225
+ echo -e " ${DIM}File: ${TEMPLATE_FILE}${RESET}"
226
+ exit 1
227
+ fi
228
+
229
+ echo -e " ${DIM}${TEMPLATE_DESC}${RESET}"
230
+ echo -e " ${DIM}Agents: ${#TEMPLATE_AGENTS[@]} Layout: ${TEMPLATE_LAYOUT}${RESET}"
231
+ fi
232
+
233
+ # ─── Resolve Permissions ──────────────────────────────────────────────────
234
+ # Default: skip permissions when agents are being spawned (autonomous teams)
235
+ if [[ "$SKIP_PERMISSIONS" == "auto" ]]; then
236
+ if [[ ${#TEMPLATE_AGENTS[@]} -gt 0 || -n "$GOAL" ]]; then
237
+ SKIP_PERMISSIONS=true
238
+ else
239
+ SKIP_PERMISSIONS=false
240
+ fi
241
+ fi
242
+
243
+ # ─── Resolve Terminal Adapter ───────────────────────────────────────────────
244
+
245
+ # Auto-detect if not specified
246
+ if [[ -z "$TERMINAL_ADAPTER" ]]; then
247
+ TERMINAL_ADAPTER="tmux"
248
+ fi
249
+
250
+ ADAPTER_FILE="$SCRIPT_DIR/adapters/${TERMINAL_ADAPTER}-adapter.sh"
251
+ if [[ -f "$ADAPTER_FILE" ]]; then
252
+ # shellcheck source=/dev/null
253
+ source "$ADAPTER_FILE"
254
+ else
255
+ # Default to inline tmux behavior (backwards compatible)
256
+ if [[ "$TERMINAL_ADAPTER" != "tmux" ]]; then
257
+ error "Terminal adapter '${TERMINAL_ADAPTER}' not found."
258
+ echo -e " Available: tmux (default), iterm2, wezterm"
259
+ echo -e " Adapter dir: ${DIM}${SCRIPT_DIR}/adapters/${RESET}"
260
+ exit 1
261
+ fi
262
+ fi
263
+
264
+ # ─── Build Team Prompt ───────────────────────────────────────────────────────
265
+ # Claude Code's TeamCreate + Task tools handle pane creation automatically.
266
+ # We create ONE window, launch claude with a team setup prompt, and let
267
+ # Claude orchestrate the agents. No pre-splitting needed.
268
+
269
+ build_team_prompt() {
270
+ local prompt=""
271
+ local project_dir
272
+ project_dir="$(tmux display-message -p '#{pane_current_path}' 2>/dev/null || pwd)"
273
+
274
+ local memory_context=""
275
+ if [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
276
+ memory_context=$(bash "$SCRIPT_DIR/sw-memory.sh" inject "build" 2>/dev/null) || true
277
+ fi
278
+
279
+ if [[ ${#TEMPLATE_AGENTS[@]} -gt 0 ]]; then
280
+ if [[ -n "$GOAL" ]]; then
281
+ prompt="GOAL: ${GOAL}"
282
+ prompt+=$'\n\n'
283
+ fi
284
+
285
+ prompt+="You are the team lead for \"${TEAM_NAME}\". You are in: ${project_dir}"
286
+ prompt+=$'\n\n'"Follow these steps:"
287
+ prompt+=$'\n'"1. Call TeamCreate with team_name=\"${TEAM_NAME}\""
288
+ prompt+=$'\n'"2. Create tasks using TaskCreate for each agent's work"
289
+ prompt+=$'\n'"3. Spawn each agent using the Task tool with team_name=\"${TEAM_NAME}\" and the agent name below"
290
+ prompt+=$'\n'"4. Assign tasks using TaskUpdate with owner set to each agent's name"
291
+ prompt+=$'\n'"5. Coordinate work and monitor progress"
292
+ prompt+=$'\n\n'"Agents to spawn:"
293
+
294
+ for agent_entry in "${TEMPLATE_AGENTS[@]}"; do
295
+ IFS='|' read -r aname arole afocus <<< "$agent_entry"
296
+ prompt+=$'\n'"- name=\"${aname}\": ${arole}"
297
+ if [[ -n "$afocus" ]]; then
298
+ prompt+=". Focus on files: ${afocus}"
299
+ fi
300
+ done
301
+
302
+ prompt+=$'\n\n'"Give each agent a detailed prompt describing their role and which files they own. Agents should work on DIFFERENT files to avoid merge conflicts."
303
+ else
304
+ # No template — simple team creation prompt
305
+ if [[ -n "$GOAL" ]]; then
306
+ prompt="GOAL: ${GOAL}"
307
+ prompt+=$'\n\n'"You are the team lead for \"${TEAM_NAME}\". You are in: ${project_dir}"
308
+ prompt+=$'\n\n'"Follow these steps:"
309
+ prompt+=$'\n'"1. Call TeamCreate with team_name=\"${TEAM_NAME}\""
310
+ prompt+=$'\n'"2. Decide the right number and types of agents for this goal"
311
+ prompt+=$'\n'"3. Create tasks using TaskCreate, then spawn agents with the Task tool (team_name=\"${TEAM_NAME}\")"
312
+ prompt+=$'\n'"4. Assign tasks and coordinate work"
313
+ prompt+=$'\n\n'"Assign different files to each agent to avoid merge conflicts."
314
+ fi
315
+ fi
316
+
317
+ if [[ -n "$prompt" ]]; then
318
+ if [[ -n "$memory_context" ]]; then
319
+ prompt+=$'\n\n'"Historical context (lessons from previous runs):"
320
+ prompt+=$'\n'"${memory_context}"
321
+ fi
322
+
323
+ prompt+=$'\n\n'"IMPORTANT: Read .claude/CLAUDE.md for project-specific conventions, patterns, and instructions."
324
+ fi
325
+
326
+ echo "$prompt"
327
+ }
328
+
329
+ # ─── Dry Run ────────────────────────────────────────────────────────────────
330
+
331
+ if [[ "$DRY_RUN" == true ]]; then
332
+ TEAM_PROMPT="$(build_team_prompt)"
333
+ PROJECT_DIR="$(tmux display-message -p '#{pane_current_path}' 2>/dev/null || pwd)"
334
+ DRY_RUN_FLAGS=""
335
+ [[ "$SKIP_PERMISSIONS" == true ]] && DRY_RUN_FLAGS=" --dangerously-skip-permissions"
336
+
337
+ echo -e "${CYAN}${BOLD}═══ Team Prompt ═══${RESET}"
338
+ echo ""
339
+ if [[ -n "$TEAM_PROMPT" ]]; then
340
+ echo "$TEAM_PROMPT"
341
+ else
342
+ echo -e "${DIM}(empty — no template or goal specified)${RESET}"
343
+ fi
344
+
345
+ echo ""
346
+ echo -e "${CYAN}${BOLD}═══ Launcher Script ═══${RESET}"
347
+ echo ""
348
+ if [[ -n "$TEAM_PROMPT" ]]; then
349
+ cat << EOF
350
+ #!/usr/bin/env bash
351
+ # Auto-generated by shipwright session — safe to delete
352
+ cd ${PROJECT_DIR} || exit 1
353
+ printf '\\033]2;${TEAM_NAME}-lead\\033\\\\'
354
+ PROMPT=\$(cat <prompt-file>)
355
+ rm -f <prompt-file> "\$0"
356
+ claude${DRY_RUN_FLAGS} "\$PROMPT"
357
+ echo ""
358
+ echo "Claude exited. Type 'claude' to restart, or 'exit' to close."
359
+ exec "\$SHELL" -l
360
+ EOF
361
+ else
362
+ cat << EOF
363
+ #!/usr/bin/env bash
364
+ cd ${PROJECT_DIR} || exit 1
365
+ printf '\\033]2;${TEAM_NAME}-lead\\033\\\\'
366
+ rm -f "\$0"
367
+ claude${DRY_RUN_FLAGS}
368
+ echo ""
369
+ echo "Claude exited. Type 'claude' to restart, or 'exit' to close."
370
+ exec "\$SHELL" -l
371
+ EOF
372
+ fi
373
+
374
+ echo ""
375
+ echo -e "${DIM}Window name: ${WINDOW_NAME}${RESET}"
376
+ echo -e "${DIM}Terminal adapter: ${TERMINAL_ADAPTER}${RESET}"
377
+ echo -e "${DIM}Auto-launch: ${AUTO_LAUNCH}${RESET}"
378
+ exit 0
379
+ fi
380
+
381
+ # ─── Create Session ──────────────────────────────────────────────────────────
382
+
383
+ if [[ "$TERMINAL_ADAPTER" == "tmux" ]]; then
384
+ # ─── tmux session creation ─────────────────────────────────────────────
385
+ # Uses launcher script passed to `tmux new-window` as command argument
386
+ # to eliminate the send-keys race condition (shell startup vs keystrokes).
387
+
388
+ # Secure temp directory — restrictive permissions for prompt/launcher files
389
+ SECURE_TMPDIR=$(mktemp -d) || { error "Cannot create temp dir"; exit 1; }
390
+ chmod 700 "$SECURE_TMPDIR"
391
+ trap 'rm -rf "$SECURE_TMPDIR"' EXIT
392
+
393
+ # Check if a window with this name already exists
394
+ if tmux list-windows -F '#W' 2>/dev/null | grep -qx "$WINDOW_NAME"; then
395
+ warn "Window '${WINDOW_NAME}' already exists. Switching to it."
396
+ tmux select-window -t "$WINDOW_NAME"
397
+ exit 0
398
+ fi
399
+
400
+ info "Creating team session: ${CYAN}${BOLD}${TEAM_NAME}${RESET}"
401
+ if [[ "$SKIP_PERMISSIONS" == true ]]; then
402
+ warn "${YELLOW}--dangerously-skip-permissions enabled${RESET}"
403
+ fi
404
+
405
+ # Resolve project directory (use current pane's path)
406
+ PROJECT_DIR="$(tmux display-message -p '#{pane_current_path}' 2>/dev/null || pwd)"
407
+
408
+ TEAM_PROMPT="$(build_team_prompt)"
409
+
410
+ if [[ "$AUTO_LAUNCH" == true && -n "$TEAM_PROMPT" ]]; then
411
+ info "Launching Claude Code with team setup..."
412
+
413
+ # Write prompt to a file (avoids all quoting/escaping issues)
414
+ PROMPT_FILE="$SECURE_TMPDIR/prompt.txt"
415
+ printf '%s' "$TEAM_PROMPT" > "$PROMPT_FILE"
416
+
417
+ # Build launcher — quoted heredoc (no expansion), then sed for variables.
418
+ # When claude exits, falls back to interactive shell so pane stays alive.
419
+ LAUNCHER="$SECURE_TMPDIR/launcher.sh"
420
+ cat > "$LAUNCHER" << 'LAUNCHER_STATIC'
421
+ #!/usr/bin/env bash
422
+ # Auto-generated by shipwright session — safe to delete
423
+ cd __DIR__ || exit 1
424
+ printf '\033]2;__TEAM__-lead\033\\'
425
+ PROMPT=$(cat __PROMPT__)
426
+ rm -f __PROMPT__ "$0"
427
+ claude __CLAUDE_FLAGS__ "$PROMPT"
428
+ echo ""
429
+ echo "Claude exited. Type 'claude' to restart, or 'exit' to close."
430
+ exec "$SHELL" -l
431
+ LAUNCHER_STATIC
432
+ CLAUDE_FLAGS=""
433
+ if [[ "$SKIP_PERMISSIONS" == true ]]; then
434
+ CLAUDE_FLAGS="--dangerously-skip-permissions"
435
+ fi
436
+ # Use awk for safe string replacement — sed breaks on & | \ in paths
437
+ awk -v dir="$PROJECT_DIR" -v team="$TEAM_NAME" -v prompt="$PROMPT_FILE" -v flags="$CLAUDE_FLAGS" \
438
+ '{gsub(/__DIR__/, dir); gsub(/__TEAM__/, team); gsub(/__PROMPT__/, prompt); gsub(/__CLAUDE_FLAGS__/, flags); print}' \
439
+ "$LAUNCHER" > "${LAUNCHER}.tmp" && mv "${LAUNCHER}.tmp" "$LAUNCHER"
440
+ chmod +x "$LAUNCHER"
441
+
442
+ # Create window with command — no race condition!
443
+ # bash --login loads PATH (needed for ~/.local/bin/claude)
444
+ if ! tmux new-window -n "$WINDOW_NAME" -c "$PROJECT_DIR" \
445
+ "bash --login ${LAUNCHER}"; then
446
+ error "Failed to create tmux window '${WINDOW_NAME}'"
447
+ exit 1
448
+ fi
449
+
450
+ elif [[ "$AUTO_LAUNCH" == true && -z "$TEAM_PROMPT" ]]; then
451
+ # No template and no goal — just launch claude interactively
452
+ info "Launching Claude Code..."
453
+
454
+ LAUNCHER="$SECURE_TMPDIR/launcher.sh"
455
+ cat > "$LAUNCHER" << 'LAUNCHER_STATIC'
456
+ #!/usr/bin/env bash
457
+ cd __DIR__ || exit 1
458
+ printf '\033]2;__TEAM__-lead\033\\'
459
+ rm -f "$0"
460
+ claude __CLAUDE_FLAGS__
461
+ echo ""
462
+ echo "Claude exited. Type 'claude' to restart, or 'exit' to close."
463
+ exec "$SHELL" -l
464
+ LAUNCHER_STATIC
465
+ CLAUDE_FLAGS=""
466
+ if [[ "$SKIP_PERMISSIONS" == true ]]; then
467
+ CLAUDE_FLAGS="--dangerously-skip-permissions"
468
+ fi
469
+ # Use awk for safe string replacement — sed breaks on & | \ in paths
470
+ awk -v dir="$PROJECT_DIR" -v team="$TEAM_NAME" -v flags="$CLAUDE_FLAGS" \
471
+ '{gsub(/__DIR__/, dir); gsub(/__TEAM__/, team); gsub(/__CLAUDE_FLAGS__/, flags); print}' \
472
+ "$LAUNCHER" > "${LAUNCHER}.tmp" && mv "${LAUNCHER}.tmp" "$LAUNCHER"
473
+ chmod +x "$LAUNCHER"
474
+
475
+ if ! tmux new-window -n "$WINDOW_NAME" -c "$PROJECT_DIR" \
476
+ "bash --login ${LAUNCHER}"; then
477
+ error "Failed to create tmux window '${WINDOW_NAME}'"
478
+ exit 1
479
+ fi
480
+
481
+ else
482
+ # --no-launch: create window with a regular shell
483
+ tmux new-window -n "$WINDOW_NAME" -c "$PROJECT_DIR"
484
+ info "Window ready. Launch Claude manually: ${DIM}claude${RESET}"
485
+ fi
486
+
487
+ # Apply dark theme after a brief delay to ensure the shell has started.
488
+ # Without this, select-pane -P can race with shell initialization and
489
+ # the styling may not apply to the final pane state.
490
+ {
491
+ sleep 0.3
492
+ tmux select-pane -t "$WINDOW_NAME" -P 'bg=#1a1a2e,fg=#e4e4e7' 2>/dev/null || true
493
+ } &
494
+
495
+ elif [[ -f "$ADAPTER_FILE" ]] && type -t spawn_agent &>/dev/null; then
496
+ # ─── Non-tmux adapter session (iterm2, wezterm, etc.) ──────────────────
497
+ info "Creating team session: ${CYAN}${BOLD}${TEAM_NAME}${RESET} ${DIM}(${TERMINAL_ADAPTER})${RESET}"
498
+
499
+ # Spawn leader only — Claude Code handles agent pane creation
500
+ spawn_agent "${TEAM_NAME}-lead" "#{pane_current_path}" ""
501
+
502
+ else
503
+ error "Terminal adapter '${TERMINAL_ADAPTER}' not available."
504
+ exit 1
505
+ fi
506
+
507
+ # ─── Summary ────────────────────────────────────────────────────────────────
508
+
509
+ echo ""
510
+ success "Team session ${CYAN}${BOLD}${TEAM_NAME}${RESET} launched!"
511
+
512
+ if [[ ${#TEMPLATE_AGENTS[@]} -gt 0 ]]; then
513
+ echo ""
514
+ echo -e "${BOLD}Team from template ${PURPLE}${TEMPLATE_NAME}${RESET}${BOLD}:${RESET}"
515
+ echo -e " ${CYAN}${BOLD}lead${RESET} ${DIM}— Team coordinator (Claude Code)${RESET}"
516
+ for agent_entry in "${TEMPLATE_AGENTS[@]}"; do
517
+ IFS='|' read -r aname arole afocus <<< "$agent_entry"
518
+ echo -e " ${PURPLE}${BOLD}${aname}${RESET} ${DIM}— ${arole}${RESET}"
519
+ done
520
+ if [[ -n "$GOAL" ]]; then
521
+ echo ""
522
+ echo -e "${BOLD}Goal:${RESET} ${GOAL}"
523
+ fi
524
+ fi
525
+
526
+ if [[ "$AUTO_LAUNCH" == true ]]; then
527
+ echo ""
528
+ echo -e "${GREEN}${BOLD}Claude Code is starting in window ${DIM}${WINDOW_NAME}${RESET}"
529
+ echo -e "${DIM}Claude will create the team, spawn agents in their own panes, and begin work.${RESET}"
530
+ WIN_NUM="$(tmux list-windows -F '#I #W' 2>/dev/null | grep "$WINDOW_NAME" | cut -d' ' -f1)" || WIN_NUM="?"
531
+ echo -e "${DIM}Switch to it: prefix + ${WIN_NUM}${RESET}"
532
+ else
533
+ echo ""
534
+ echo -e "${BOLD}Next steps:${RESET}"
535
+ WIN_NUM="$(tmux list-windows -F '#I #W' 2>/dev/null | grep "$WINDOW_NAME" | cut -d' ' -f1)" || WIN_NUM="?"
536
+ echo -e " ${CYAN}1.${RESET} Switch to window ${DIM}${WINDOW_NAME}${RESET} ${DIM}(prefix + ${WIN_NUM})${RESET}"
537
+ echo -e " ${CYAN}2.${RESET} Start ${DIM}claude${RESET} and ask it to create a team"
538
+ fi
539
+
540
+ echo ""
541
+ echo -e "${DIM}Keybinding: prefix + T re-runs this command${RESET}"