shipwright-cli 1.7.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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +926 -0
  3. package/claude-code/CLAUDE.md.shipwright +125 -0
  4. package/claude-code/hooks/notify-idle.sh +35 -0
  5. package/claude-code/hooks/pre-compact-save.sh +57 -0
  6. package/claude-code/hooks/task-completed.sh +170 -0
  7. package/claude-code/hooks/teammate-idle.sh +68 -0
  8. package/claude-code/settings.json.template +184 -0
  9. package/completions/_shipwright +140 -0
  10. package/completions/shipwright.bash +89 -0
  11. package/completions/shipwright.fish +107 -0
  12. package/docs/KNOWN-ISSUES.md +199 -0
  13. package/docs/TIPS.md +331 -0
  14. package/docs/definition-of-done.example.md +16 -0
  15. package/docs/patterns/README.md +139 -0
  16. package/docs/patterns/audit-loop.md +149 -0
  17. package/docs/patterns/bug-hunt.md +183 -0
  18. package/docs/patterns/feature-implementation.md +159 -0
  19. package/docs/patterns/refactoring.md +183 -0
  20. package/docs/patterns/research-exploration.md +144 -0
  21. package/docs/patterns/test-generation.md +173 -0
  22. package/package.json +49 -0
  23. package/scripts/adapters/docker-deploy.sh +50 -0
  24. package/scripts/adapters/fly-deploy.sh +41 -0
  25. package/scripts/adapters/iterm2-adapter.sh +122 -0
  26. package/scripts/adapters/railway-deploy.sh +34 -0
  27. package/scripts/adapters/tmux-adapter.sh +87 -0
  28. package/scripts/adapters/vercel-deploy.sh +35 -0
  29. package/scripts/adapters/wezterm-adapter.sh +103 -0
  30. package/scripts/cct +242 -0
  31. package/scripts/cct-cleanup.sh +172 -0
  32. package/scripts/cct-cost.sh +590 -0
  33. package/scripts/cct-daemon.sh +3189 -0
  34. package/scripts/cct-doctor.sh +328 -0
  35. package/scripts/cct-fix.sh +478 -0
  36. package/scripts/cct-fleet.sh +904 -0
  37. package/scripts/cct-init.sh +282 -0
  38. package/scripts/cct-logs.sh +273 -0
  39. package/scripts/cct-loop.sh +1332 -0
  40. package/scripts/cct-memory.sh +1148 -0
  41. package/scripts/cct-pipeline.sh +3844 -0
  42. package/scripts/cct-prep.sh +1352 -0
  43. package/scripts/cct-ps.sh +168 -0
  44. package/scripts/cct-reaper.sh +390 -0
  45. package/scripts/cct-session.sh +284 -0
  46. package/scripts/cct-status.sh +169 -0
  47. package/scripts/cct-templates.sh +242 -0
  48. package/scripts/cct-upgrade.sh +422 -0
  49. package/scripts/cct-worktree.sh +405 -0
  50. package/scripts/postinstall.mjs +96 -0
  51. package/templates/pipelines/autonomous.json +71 -0
  52. package/templates/pipelines/cost-aware.json +95 -0
  53. package/templates/pipelines/deployed.json +79 -0
  54. package/templates/pipelines/enterprise.json +114 -0
  55. package/templates/pipelines/fast.json +63 -0
  56. package/templates/pipelines/full.json +104 -0
  57. package/templates/pipelines/hotfix.json +63 -0
  58. package/templates/pipelines/standard.json +91 -0
  59. package/tmux/claude-teams-overlay.conf +109 -0
  60. package/tmux/templates/architecture.json +19 -0
  61. package/tmux/templates/bug-fix.json +24 -0
  62. package/tmux/templates/code-review.json +24 -0
  63. package/tmux/templates/devops.json +19 -0
  64. package/tmux/templates/documentation.json +19 -0
  65. package/tmux/templates/exploration.json +19 -0
  66. package/tmux/templates/feature-dev.json +24 -0
  67. package/tmux/templates/full-stack.json +24 -0
  68. package/tmux/templates/migration.json +24 -0
  69. package/tmux/templates/refactor.json +19 -0
  70. package/tmux/templates/security-audit.json +24 -0
  71. package/tmux/templates/testing.json +24 -0
  72. package/tmux/tmux.conf +167 -0
@@ -0,0 +1,284 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ cct-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
+ set -euo pipefail
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+
15
+ # ─── Colors ──────────────────────────────────────────────────────────────────
16
+ CYAN='\033[38;2;0;212;255m'
17
+ PURPLE='\033[38;2;124;58;237m'
18
+ GREEN='\033[38;2;74;222;128m'
19
+ YELLOW='\033[38;2;250;204;21m'
20
+ RED='\033[38;2;248;113;113m'
21
+ DIM='\033[2m'
22
+ BOLD='\033[1m'
23
+ RESET='\033[0m'
24
+
25
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
26
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
27
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
28
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
29
+
30
+ # ─── Parse Arguments ────────────────────────────────────────────────────────
31
+
32
+ TEAM_NAME=""
33
+ TEMPLATE_NAME=""
34
+ TERMINAL_ADAPTER=""
35
+
36
+ while [[ $# -gt 0 ]]; do
37
+ case "$1" in
38
+ --template|-t)
39
+ TEMPLATE_NAME="${2:-}"
40
+ [[ -z "$TEMPLATE_NAME" ]] && { error "Missing template name after --template"; exit 1; }
41
+ shift 2
42
+ ;;
43
+ --terminal)
44
+ TERMINAL_ADAPTER="${2:-}"
45
+ [[ -z "$TERMINAL_ADAPTER" ]] && { error "Missing adapter name after --terminal"; exit 1; }
46
+ shift 2
47
+ ;;
48
+ --help|-h)
49
+ echo -e "${CYAN}${BOLD}shipwright session${RESET} — Create a new team session"
50
+ echo ""
51
+ echo -e "${BOLD}USAGE${RESET}"
52
+ echo -e " shipwright session [name] [--template <name>] [--terminal <adapter>]"
53
+ echo ""
54
+ echo -e "${BOLD}OPTIONS${RESET}"
55
+ echo -e " ${CYAN}--template, -t${RESET} <name> Use a team template (see: shipwright templates list)"
56
+ echo -e " ${CYAN}--terminal${RESET} <adapter> Terminal adapter: tmux (default), iterm2, wezterm"
57
+ echo ""
58
+ echo -e "${BOLD}EXAMPLES${RESET}"
59
+ echo -e " ${DIM}shipwright session refactor${RESET}"
60
+ echo -e " ${DIM}shipwright session my-feature --template feature-dev${RESET}"
61
+ echo -e " ${DIM}shipwright session my-feature --terminal iterm2${RESET}"
62
+ exit 0
63
+ ;;
64
+ -*)
65
+ error "Unknown option: $1"
66
+ exit 1
67
+ ;;
68
+ *)
69
+ # Positional: team name
70
+ [[ -z "$TEAM_NAME" ]] && TEAM_NAME="$1" || { error "Unexpected argument: $1"; exit 1; }
71
+ shift
72
+ ;;
73
+ esac
74
+ done
75
+
76
+ TEAM_NAME="${TEAM_NAME:-team-$(date +%s)}"
77
+ WINDOW_NAME="claude-${TEAM_NAME}"
78
+
79
+ # ─── Template Loading ───────────────────────────────────────────────────────
80
+
81
+ TEMPLATE_FILE=""
82
+ TEMPLATE_LAYOUT=""
83
+ TEMPLATE_LAYOUT_STYLE=""
84
+ TEMPLATE_MAIN_PANE_PERCENT=""
85
+ TEMPLATE_DESC=""
86
+ TEMPLATE_AGENTS=() # Populated as "name|role|focus" entries
87
+
88
+ if [[ -n "$TEMPLATE_NAME" ]]; then
89
+ # Search for template: user dir first, then repo dir
90
+ USER_TEMPLATES_DIR="${HOME}/.claude-teams/templates"
91
+ REPO_TEMPLATES_DIR="$(cd "$SCRIPT_DIR/../tmux/templates" 2>/dev/null && pwd)" || REPO_TEMPLATES_DIR=""
92
+
93
+ TEMPLATE_NAME="${TEMPLATE_NAME%.json}"
94
+
95
+ if [[ -f "$USER_TEMPLATES_DIR/${TEMPLATE_NAME}.json" ]]; then
96
+ TEMPLATE_FILE="$USER_TEMPLATES_DIR/${TEMPLATE_NAME}.json"
97
+ elif [[ -n "$REPO_TEMPLATES_DIR" && -f "$REPO_TEMPLATES_DIR/${TEMPLATE_NAME}.json" ]]; then
98
+ TEMPLATE_FILE="$REPO_TEMPLATES_DIR/${TEMPLATE_NAME}.json"
99
+ else
100
+ error "Template '${TEMPLATE_NAME}' not found."
101
+ echo -e " Run ${DIM}shipwright templates list${RESET} to see available templates."
102
+ exit 1
103
+ fi
104
+
105
+ info "Loading template: ${PURPLE}${BOLD}${TEMPLATE_NAME}${RESET}"
106
+
107
+ # Parse template — single jq call extracts all fields + agents in one pass
108
+ if command -v jq &>/dev/null; then
109
+ # Single jq call: outputs metadata lines then agent lines
110
+ # Format: META<tab>field<tab>value for metadata, AGENT<tab>name|role|focus for agents
111
+ while IFS=$'\t' read -r tag key value; do
112
+ case "$tag" in
113
+ META)
114
+ case "$key" in
115
+ description) TEMPLATE_DESC="$value" ;;
116
+ layout) TEMPLATE_LAYOUT="$value" ;;
117
+ layout_style) TEMPLATE_LAYOUT_STYLE="$value" ;;
118
+ main_pane_percent) TEMPLATE_MAIN_PANE_PERCENT="$value" ;;
119
+ esac
120
+ ;;
121
+ AGENT) [[ -n "$key" ]] && TEMPLATE_AGENTS+=("$key") ;;
122
+ esac
123
+ done < <(jq -r '
124
+ "META\tdescription\t\(.description // "")",
125
+ "META\tlayout\t\(.layout // "tiled")",
126
+ "META\tlayout_style\t\(.layout_style // "")",
127
+ "META\tmain_pane_percent\t\(.main_pane_percent // "")",
128
+ (.agents // [] | .[] | "AGENT\t\(.name)|\(.role // "")|\(.focus // "")\t")
129
+ ' "$TEMPLATE_FILE")
130
+ else
131
+ error "jq is required for template parsing."
132
+ echo -e " ${DIM}brew install jq${RESET}"
133
+ exit 1
134
+ fi
135
+
136
+ echo -e " ${DIM}${TEMPLATE_DESC}${RESET}"
137
+ echo -e " ${DIM}Agents: ${#TEMPLATE_AGENTS[@]} Layout: ${TEMPLATE_LAYOUT}${RESET}"
138
+ fi
139
+
140
+ # ─── Resolve Terminal Adapter ───────────────────────────────────────────────
141
+
142
+ # Auto-detect if not specified
143
+ if [[ -z "$TERMINAL_ADAPTER" ]]; then
144
+ TERMINAL_ADAPTER="tmux"
145
+ fi
146
+
147
+ ADAPTER_FILE="$SCRIPT_DIR/adapters/${TERMINAL_ADAPTER}-adapter.sh"
148
+ if [[ -f "$ADAPTER_FILE" ]]; then
149
+ # shellcheck source=/dev/null
150
+ source "$ADAPTER_FILE"
151
+ else
152
+ # Default to inline tmux behavior (backwards compatible)
153
+ if [[ "$TERMINAL_ADAPTER" != "tmux" ]]; then
154
+ error "Terminal adapter '${TERMINAL_ADAPTER}' not found."
155
+ echo -e " Available: tmux (default), iterm2, wezterm"
156
+ echo -e " Adapter dir: ${DIM}${SCRIPT_DIR}/adapters/${RESET}"
157
+ exit 1
158
+ fi
159
+ fi
160
+
161
+ # ─── Create Session (tmux default path) ─────────────────────────────────────
162
+
163
+ if [[ "$TERMINAL_ADAPTER" == "tmux" && ! -f "$ADAPTER_FILE" ]]; then
164
+ # Inline tmux path — original behavior (adapter not required for tmux)
165
+
166
+ # Check if a window with this name already exists
167
+ if tmux list-windows -F '#W' 2>/dev/null | grep -qx "$WINDOW_NAME"; then
168
+ warn "Window '${WINDOW_NAME}' already exists. Switching to it."
169
+ tmux select-window -t "$WINDOW_NAME"
170
+ exit 0
171
+ fi
172
+
173
+ info "Creating team session: ${CYAN}${BOLD}${TEAM_NAME}${RESET}"
174
+
175
+ # Create a new window (not split-window — avoids race condition #23615)
176
+ tmux new-window -n "$WINDOW_NAME" -c "#{pane_current_path}"
177
+
178
+ # Force dark theme on the new pane (belt-and-suspenders with overlay hooks)
179
+ tmux select-pane -t "$WINDOW_NAME" -P 'bg=#1a1a2e,fg=#e4e4e7'
180
+
181
+ # Set the pane title so the overlay shows the team name
182
+ tmux send-keys -t "$WINDOW_NAME" "printf '\\033]2;${TEAM_NAME}-lead\\033\\\\'" Enter
183
+
184
+ sleep 0.2
185
+ tmux send-keys -t "$WINDOW_NAME" "clear" Enter
186
+
187
+ # ─── Template: Create Agent Panes ────────────────────────────────────────
188
+ if [[ ${#TEMPLATE_AGENTS[@]} -gt 0 ]]; then
189
+ info "Scaffolding ${#TEMPLATE_AGENTS[@]} agent panes..."
190
+
191
+ for agent_entry in "${TEMPLATE_AGENTS[@]}"; do
192
+ IFS='|' read -r aname arole afocus <<< "$agent_entry"
193
+
194
+ # Split the window to create a new pane
195
+ tmux split-window -t "$WINDOW_NAME" -c "#{pane_current_path}"
196
+ sleep 0.1
197
+
198
+ # Force dark theme on agent pane
199
+ tmux select-pane -t "$WINDOW_NAME" -P 'bg=#1a1a2e,fg=#e4e4e7'
200
+
201
+ # Set the pane title to the agent name
202
+ tmux send-keys -t "$WINDOW_NAME" "printf '\\033]2;${TEAM_NAME}-${aname}\\033\\\\'" Enter
203
+ sleep 0.1
204
+ tmux send-keys -t "$WINDOW_NAME" "clear" Enter
205
+ done
206
+
207
+ # Apply the layout from the template (layout_style takes precedence over layout)
208
+ if [[ -n "$TEMPLATE_LAYOUT_STYLE" ]]; then
209
+ tmux select-layout -t "$WINDOW_NAME" "$TEMPLATE_LAYOUT_STYLE" 2>/dev/null || \
210
+ tmux select-layout -t "$WINDOW_NAME" "${TEMPLATE_LAYOUT:-tiled}" 2>/dev/null || true
211
+ else
212
+ tmux select-layout -t "$WINDOW_NAME" "${TEMPLATE_LAYOUT:-tiled}" 2>/dev/null || true
213
+ fi
214
+
215
+ # Resize leader pane to desired percentage
216
+ if [[ -n "$TEMPLATE_MAIN_PANE_PERCENT" && -n "$TEMPLATE_LAYOUT_STYLE" ]]; then
217
+ case "$TEMPLATE_LAYOUT_STYLE" in
218
+ main-horizontal) tmux resize-pane -t "$WINDOW_NAME.0" -x "${TEMPLATE_MAIN_PANE_PERCENT}%" 2>/dev/null ;;
219
+ main-vertical) tmux resize-pane -t "$WINDOW_NAME.0" -y "${TEMPLATE_MAIN_PANE_PERCENT}%" 2>/dev/null ;;
220
+ esac
221
+ fi
222
+
223
+ # Select the first pane (leader)
224
+ tmux select-pane -t "$WINDOW_NAME.0"
225
+ fi
226
+
227
+ else
228
+ # ─── Adapter-based session creation ──────────────────────────────────────
229
+
230
+ if type -t spawn_agent &>/dev/null; then
231
+ info "Creating team session: ${CYAN}${BOLD}${TEAM_NAME}${RESET} ${DIM}(${TERMINAL_ADAPTER})${RESET}"
232
+
233
+ # Spawn leader
234
+ spawn_agent "${TEAM_NAME}-lead" "#{pane_current_path}" ""
235
+
236
+ # Spawn template agents if provided
237
+ if [[ ${#TEMPLATE_AGENTS[@]} -gt 0 ]]; then
238
+ info "Scaffolding ${#TEMPLATE_AGENTS[@]} agents..."
239
+ for agent_entry in "${TEMPLATE_AGENTS[@]}"; do
240
+ IFS='|' read -r aname arole afocus <<< "$agent_entry"
241
+ spawn_agent "${TEAM_NAME}-${aname}" "#{pane_current_path}" ""
242
+ done
243
+ fi
244
+ else
245
+ error "Adapter '${TERMINAL_ADAPTER}' loaded but spawn_agent() not found."
246
+ exit 1
247
+ fi
248
+ fi
249
+
250
+ # ─── Summary ────────────────────────────────────────────────────────────────
251
+
252
+ echo ""
253
+ success "Team session ${CYAN}${BOLD}${TEAM_NAME}${RESET} ready!"
254
+
255
+ if [[ ${#TEMPLATE_AGENTS[@]} -gt 0 ]]; then
256
+ echo ""
257
+ echo -e "${BOLD}Team from template ${PURPLE}${TEMPLATE_NAME}${RESET}${BOLD}:${RESET}"
258
+ echo -e " ${CYAN}${BOLD}lead${RESET} ${DIM}— Team coordinator${RESET}"
259
+ for agent_entry in "${TEMPLATE_AGENTS[@]}"; do
260
+ IFS='|' read -r aname arole afocus <<< "$agent_entry"
261
+ echo -e " ${PURPLE}${BOLD}${aname}${RESET} ${DIM}— ${arole}${RESET}"
262
+ done
263
+ echo ""
264
+ echo -e "${BOLD}Next steps:${RESET}"
265
+ echo -e " ${CYAN}1.${RESET} Switch to window ${DIM}${WINDOW_NAME}${RESET}"
266
+ echo -e " ${CYAN}2.${RESET} Start ${DIM}claude${RESET} in the lead pane (top-left)"
267
+ echo -e " ${CYAN}3.${RESET} Ask Claude to use the team — agents are ready in their panes"
268
+ else
269
+ echo ""
270
+ echo -e "${BOLD}Next steps:${RESET}"
271
+ echo -e " ${CYAN}1.${RESET} Switch to window ${DIM}${WINDOW_NAME}${RESET} ${DIM}(prefix + $(tmux list-windows -F '#I #W' | grep "$WINDOW_NAME" | cut -d' ' -f1))${RESET}"
272
+ echo -e " ${CYAN}2.${RESET} Start Claude Code:"
273
+ echo -e " ${DIM}claude${RESET}"
274
+ echo -e " ${CYAN}3.${RESET} Ask Claude to create a team:"
275
+ echo -e " ${DIM}\"Create a team with 2 agents to refactor the auth module\"${RESET}"
276
+ fi
277
+
278
+ echo ""
279
+ echo -e "${PURPLE}${BOLD}Tip:${RESET} For file isolation between agents, use git worktrees:"
280
+ echo -e " ${DIM}git worktree add ../project-${TEAM_NAME} -b ${TEAM_NAME}${RESET}"
281
+ echo -e " Then launch Claude inside the worktree directory."
282
+ echo ""
283
+ echo -e "${DIM}Settings: ~/.claude/settings.json (see settings.json.template)${RESET}"
284
+ echo -e "${DIM}Keybinding: prefix + T re-runs this command${RESET}"
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ cct-status.sh — Dashboard showing Claude Code team status ║
4
+ # ║ ║
5
+ # ║ Shows running teams, agent windows, and task progress. ║
6
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
7
+ set -euo pipefail
8
+
9
+ # ─── Colors ──────────────────────────────────────────────────────────────────
10
+ CYAN='\033[38;2;0;212;255m'
11
+ PURPLE='\033[38;2;124;58;237m'
12
+ BLUE='\033[38;2;0;102;255m'
13
+ GREEN='\033[38;2;74;222;128m'
14
+ YELLOW='\033[38;2;250;204;21m'
15
+ RED='\033[38;2;248;113;113m'
16
+ DIM='\033[2m'
17
+ BOLD='\033[1m'
18
+ RESET='\033[0m'
19
+
20
+ # ─── Header ──────────────────────────────────────────────────────────────────
21
+
22
+ echo ""
23
+ echo -e "${CYAN}${BOLD} Claude Code Teams — Status Dashboard${RESET}"
24
+ echo -e "${DIM} $(date '+%Y-%m-%d %H:%M:%S')${RESET}"
25
+ echo -e "${DIM} ══════════════════════════════════════════${RESET}"
26
+ echo ""
27
+
28
+ # ─── 1. Tmux Windows ────────────────────────────────────────────────────────
29
+
30
+ echo -e "${PURPLE}${BOLD} TMUX WINDOWS${RESET}"
31
+ echo -e "${DIM} ──────────────────────────────────────────${RESET}"
32
+
33
+ # Get all windows, highlight Claude-related ones
34
+ HAS_CLAUDE_WINDOWS=false
35
+ while IFS= read -r line; do
36
+ session_window="$(echo "$line" | cut -d'|' -f1)"
37
+ window_name="$(echo "$line" | cut -d'|' -f2)"
38
+ pane_count="$(echo "$line" | cut -d'|' -f3)"
39
+ active="$(echo "$line" | cut -d'|' -f4)"
40
+
41
+ if echo "$window_name" | grep -qi "claude"; then
42
+ HAS_CLAUDE_WINDOWS=true
43
+ if [[ "$active" == "1" ]]; then
44
+ status_icon="${GREEN}●${RESET}"
45
+ status_label="${GREEN}active${RESET}"
46
+ else
47
+ status_icon="${YELLOW}●${RESET}"
48
+ status_label="${YELLOW}idle${RESET}"
49
+ fi
50
+ echo -e " ${status_icon} ${BOLD}${window_name}${RESET} ${DIM}${session_window}${RESET} panes:${pane_count} ${status_label}"
51
+ fi
52
+ done < <(tmux list-windows -a -F '#{session_name}:#{window_index}|#{window_name}|#{window_panes}|#{window_active}' 2>/dev/null || true)
53
+
54
+ if ! $HAS_CLAUDE_WINDOWS; then
55
+ echo -e " ${DIM}No Claude team windows found.${RESET}"
56
+ echo -e " ${DIM}Start one with: ${CYAN}shipwright session <name>${RESET}"
57
+ fi
58
+
59
+ # ─── 2. Team Configurations ─────────────────────────────────────────────────
60
+
61
+ echo ""
62
+ echo -e "${PURPLE}${BOLD} TEAM CONFIGS${RESET} ${DIM}~/.claude/teams/${RESET}"
63
+ echo -e "${DIM} ──────────────────────────────────────────${RESET}"
64
+
65
+ TEAMS_DIR="${HOME}/.claude/teams"
66
+ HAS_TEAMS=false
67
+
68
+ if [[ -d "$TEAMS_DIR" ]]; then
69
+ while IFS= read -r team_dir; do
70
+ [[ -z "$team_dir" ]] && continue
71
+ HAS_TEAMS=true
72
+ team_name="$(basename "$team_dir")"
73
+
74
+ # Try to read config.json for member info
75
+ config_file="${team_dir}/config.json"
76
+ if [[ -f "$config_file" ]]; then
77
+ # Count members from JSON (look for "name" keys in members array)
78
+ member_count=$(grep -c '"name"' "$config_file" 2>/dev/null || true)
79
+ member_count="${member_count:-0}"
80
+ # Extract member names
81
+ member_names=$(grep '"name"' "$config_file" 2>/dev/null | sed 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' | tr '\n' ', ' | sed 's/,$//' | sed 's/,/, /g')
82
+
83
+ echo -e " ${GREEN}●${RESET} ${BOLD}${team_name}${RESET} ${DIM}members:${member_count}${RESET}"
84
+ if [[ -n "$member_names" ]]; then
85
+ echo -e " ${DIM}└─ ${member_names}${RESET}"
86
+ fi
87
+ else
88
+ # Directory exists but no config — possibly orphaned
89
+ echo -e " ${RED}●${RESET} ${BOLD}${team_name}${RESET} ${DIM}(no config — possibly orphaned)${RESET}"
90
+ fi
91
+ done < <(find "$TEAMS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort)
92
+ fi
93
+
94
+ if ! $HAS_TEAMS; then
95
+ echo -e " ${DIM}No team configs found.${RESET}"
96
+ fi
97
+
98
+ # ─── 3. Task Lists ──────────────────────────────────────────────────────────
99
+
100
+ echo ""
101
+ echo -e "${PURPLE}${BOLD} TASK LISTS${RESET} ${DIM}~/.claude/tasks/${RESET}"
102
+ echo -e "${DIM} ──────────────────────────────────────────${RESET}"
103
+
104
+ TASKS_DIR="${HOME}/.claude/tasks"
105
+ HAS_TASKS=false
106
+
107
+ if [[ -d "$TASKS_DIR" ]]; then
108
+ while IFS= read -r task_dir; do
109
+ [[ -z "$task_dir" ]] && continue
110
+ HAS_TASKS=true
111
+ task_team="$(basename "$task_dir")"
112
+
113
+ # Count tasks by status
114
+ total=0
115
+ completed=0
116
+ in_progress=0
117
+ pending=0
118
+
119
+ while IFS= read -r task_file; do
120
+ [[ -z "$task_file" ]] && continue
121
+ total=$((total + 1))
122
+ status=$(grep -o '"status"[[:space:]]*:[[:space:]]*"[^"]*"' "$task_file" 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
123
+ case "$status" in
124
+ completed) completed=$((completed + 1)) ;;
125
+ in_progress) in_progress=$((in_progress + 1)) ;;
126
+ pending) pending=$((pending + 1)) ;;
127
+ esac
128
+ done < <(find "$task_dir" -type f -name '*.json' 2>/dev/null)
129
+
130
+ # Build progress bar
131
+ if [[ $total -gt 0 ]]; then
132
+ pct=$((completed * 100 / total))
133
+ bar_width=20
134
+ filled=$((pct * bar_width / 100))
135
+ empty=$((bar_width - filled))
136
+ bar="${GREEN}"
137
+ for ((i=0; i<filled; i++)); do bar+="█"; done
138
+ bar+="${DIM}"
139
+ for ((i=0; i<empty; i++)); do bar+="░"; done
140
+ bar+="${RESET}"
141
+
142
+ echo -e " ${BLUE}●${RESET} ${BOLD}${task_team}${RESET} ${bar} ${pct}% ${DIM}(${completed}/${total} done)${RESET}"
143
+
144
+ # Show breakdown if there are active tasks
145
+ details=""
146
+ [[ $in_progress -gt 0 ]] && details+="${GREEN}${in_progress} active${RESET} "
147
+ [[ $pending -gt 0 ]] && details+="${YELLOW}${pending} pending${RESET} "
148
+ [[ $completed -gt 0 ]] && details+="${DIM}${completed} done${RESET}"
149
+ [[ -n "$details" ]] && echo -e " ${DIM}└─${RESET} ${details}"
150
+ else
151
+ echo -e " ${DIM}●${RESET} ${BOLD}${task_team}${RESET} ${DIM}(no tasks)${RESET}"
152
+ fi
153
+ done < <(find "$TASKS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort)
154
+ fi
155
+
156
+ if ! $HAS_TASKS; then
157
+ echo -e " ${DIM}No task lists found.${RESET}"
158
+ fi
159
+
160
+ # ─── Footer ──────────────────────────────────────────────────────────────────
161
+
162
+ echo ""
163
+ echo -e "${DIM} ──────────────────────────────────────────${RESET}"
164
+ if $HAS_CLAUDE_WINDOWS || $HAS_TEAMS || $HAS_TASKS; then
165
+ echo -e " ${DIM}Clean up:${RESET} ${CYAN}shipwright cleanup${RESET} ${DIM}|${RESET} ${DIM}New session:${RESET} ${CYAN}shipwright session <name>${RESET}"
166
+ else
167
+ echo -e " ${DIM}No active teams. Start one:${RESET} ${CYAN}shipwright session <name>${RESET}"
168
+ fi
169
+ echo ""