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,359 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ sw-cleanup.sh — Clean up orphaned Claude team sessions & artifacts ║
4
+ # ║ ║
5
+ # ║ Default: dry-run (shows what would be cleaned). ║
6
+ # ║ Use --force to actually kill sessions and remove files. ║
7
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
8
+ VERSION="1.10.0"
9
+ set -euo pipefail
10
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
+
12
+ # ─── Colors ──────────────────────────────────────────────────────────────────
13
+ CYAN='\033[38;2;0;212;255m'
14
+ PURPLE='\033[38;2;124;58;237m'
15
+ GREEN='\033[38;2;74;222;128m'
16
+ YELLOW='\033[38;2;250;204;21m'
17
+ RED='\033[38;2;248;113;113m'
18
+ DIM='\033[2m'
19
+ BOLD='\033[1m'
20
+ RESET='\033[0m'
21
+
22
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
23
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
24
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
25
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
26
+
27
+ # ─── Parse Args ──────────────────────────────────────────────────────────────
28
+
29
+ FORCE=false
30
+ for arg in "$@"; do
31
+ case "$arg" in
32
+ --force|-f) FORCE=true ;;
33
+ --help|-h)
34
+ echo -e "${CYAN}${BOLD}shipwright cleanup${RESET} — Clean up orphaned sessions and artifacts"
35
+ echo ""
36
+ echo -e "${BOLD}USAGE${RESET}"
37
+ echo -e " shipwright cleanup ${DIM}# Dry-run: show what would be cleaned${RESET}"
38
+ echo -e " shipwright cleanup --force ${DIM}# Actually kill sessions and remove files${RESET}"
39
+ exit 0
40
+ ;;
41
+ *)
42
+ error "Unknown option: ${arg}"
43
+ exit 1
44
+ ;;
45
+ esac
46
+ done
47
+
48
+ # ─── Track cleanup stats ────────────────────────────────────────────────────
49
+
50
+ WINDOWS_FOUND=0
51
+ WINDOWS_KILLED=0
52
+ TEAM_DIRS_FOUND=0
53
+ TEAM_DIRS_REMOVED=0
54
+ TASK_DIRS_FOUND=0
55
+ TASK_DIRS_REMOVED=0
56
+ ARTIFACTS_FOUND=0
57
+ ARTIFACTS_REMOVED=0
58
+ CHECKPOINTS_FOUND=0
59
+ CHECKPOINTS_REMOVED=0
60
+ HEARTBEATS_FOUND=0
61
+ HEARTBEATS_REMOVED=0
62
+ BRANCHES_FOUND=0
63
+ BRANCHES_REMOVED=0
64
+ STATE_RESET=0
65
+
66
+ # ─── 1. Find orphaned tmux windows ──────────────────────────────────────────
67
+
68
+ echo ""
69
+ if $FORCE; then
70
+ info "Cleaning up Claude team sessions ${RED}${BOLD}(FORCE MODE)${RESET}"
71
+ else
72
+ info "Scanning for orphaned Claude team sessions ${DIM}(dry-run)${RESET}"
73
+ fi
74
+ echo ""
75
+
76
+ echo -e "${BOLD}Tmux Windows${RESET}"
77
+ echo -e "${DIM}────────────────────────────────────────${RESET}"
78
+
79
+ # Look for windows with "claude" in the name across all sessions
80
+ CLAUDE_WINDOWS=()
81
+ while IFS= read -r line; do
82
+ [[ -n "$line" ]] && CLAUDE_WINDOWS+=("$line")
83
+ done < <(tmux list-windows -a -F '#{session_name}:#{window_index} #{window_name}' 2>/dev/null | grep -i "claude" || true)
84
+
85
+ if [[ ${#CLAUDE_WINDOWS[@]} -eq 0 ]]; then
86
+ echo -e " ${DIM}No Claude team windows found.${RESET}"
87
+ else
88
+ for win in "${CLAUDE_WINDOWS[@]}"; do
89
+ WINDOWS_FOUND=$((WINDOWS_FOUND + 1))
90
+ local_target="$(echo "$win" | cut -d' ' -f1)"
91
+ local_name="$(echo "$win" | cut -d' ' -f2-)"
92
+
93
+ if $FORCE; then
94
+ tmux kill-window -t "$local_target" 2>/dev/null && {
95
+ echo -e " ${RED}✗${RESET} Killed: ${local_name} ${DIM}(${local_target})${RESET}"
96
+ WINDOWS_KILLED=$((WINDOWS_KILLED + 1))
97
+ } || {
98
+ warn " Could not kill: ${local_name} (${local_target})"
99
+ }
100
+ else
101
+ echo -e " ${YELLOW}○${RESET} Would kill: ${local_name} ${DIM}(${local_target})${RESET}"
102
+ fi
103
+ done
104
+ fi
105
+
106
+ # ─── 2. Clean up ~/.claude/teams/ ───────────────────────────────────────────
107
+
108
+ echo ""
109
+ echo -e "${BOLD}Team Configs${RESET} ${DIM}~/.claude/teams/${RESET}"
110
+ echo -e "${DIM}────────────────────────────────────────${RESET}"
111
+
112
+ TEAMS_DIR="${HOME}/.claude/teams"
113
+ if [[ -d "$TEAMS_DIR" ]]; then
114
+ while IFS= read -r team_dir; do
115
+ [[ -z "$team_dir" ]] && continue
116
+ TEAM_DIRS_FOUND=$((TEAM_DIRS_FOUND + 1))
117
+ team_name="$(basename "$team_dir")"
118
+
119
+ if $FORCE; then
120
+ rm -rf "$team_dir" && {
121
+ echo -e " ${RED}✗${RESET} Removed: ${team_name}/"
122
+ TEAM_DIRS_REMOVED=$((TEAM_DIRS_REMOVED + 1))
123
+ }
124
+ else
125
+ # Count files inside
126
+ file_count=$(find "$team_dir" -type f 2>/dev/null | wc -l | tr -d ' ')
127
+ echo -e " ${YELLOW}○${RESET} Would remove: ${team_name}/ ${DIM}(${file_count} files)${RESET}"
128
+ fi
129
+ done < <(find "$TEAMS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
130
+ else
131
+ echo -e " ${DIM}No team configs found.${RESET}"
132
+ fi
133
+
134
+ # ─── 3. Clean up ~/.claude/tasks/ ───────────────────────────────────────────
135
+
136
+ echo ""
137
+ echo -e "${BOLD}Task Lists${RESET} ${DIM}~/.claude/tasks/${RESET}"
138
+ echo -e "${DIM}────────────────────────────────────────${RESET}"
139
+
140
+ TASKS_DIR="${HOME}/.claude/tasks"
141
+ if [[ -d "$TASKS_DIR" ]]; then
142
+ while IFS= read -r task_dir; do
143
+ [[ -z "$task_dir" ]] && continue
144
+ TASK_DIRS_FOUND=$((TASK_DIRS_FOUND + 1))
145
+ task_name="$(basename "$task_dir")"
146
+
147
+ if $FORCE; then
148
+ rm -rf "$task_dir" && {
149
+ echo -e " ${RED}✗${RESET} Removed: ${task_name}/"
150
+ TASK_DIRS_REMOVED=$((TASK_DIRS_REMOVED + 1))
151
+ }
152
+ else
153
+ task_count=$(find "$task_dir" -type f -name '*.json' 2>/dev/null | wc -l | tr -d ' ')
154
+ echo -e " ${YELLOW}○${RESET} Would remove: ${task_name}/ ${DIM}(${task_count} tasks)${RESET}"
155
+ fi
156
+ done < <(find "$TASKS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
157
+ else
158
+ echo -e " ${DIM}No task directories found.${RESET}"
159
+ fi
160
+
161
+ # ─── 4. Pipeline Artifacts ──────────────────────────────────────────────────
162
+
163
+ echo ""
164
+ echo -e "${BOLD}Pipeline Artifacts${RESET} ${DIM}.claude/pipeline-artifacts/${RESET}"
165
+ echo -e "${DIM}────────────────────────────────────────${RESET}"
166
+
167
+ PIPELINE_ARTIFACTS=".claude/pipeline-artifacts"
168
+ if [[ -d "$PIPELINE_ARTIFACTS" ]]; then
169
+ artifact_file_count=$(find "$PIPELINE_ARTIFACTS" -type f 2>/dev/null | wc -l | tr -d ' ')
170
+ if [[ "${artifact_file_count:-0}" -gt 0 ]]; then
171
+ ARTIFACTS_FOUND=$((artifact_file_count))
172
+
173
+ # Calculate total size
174
+ artifact_size=$(du -sh "$PIPELINE_ARTIFACTS" 2>/dev/null | cut -f1 || echo "unknown")
175
+
176
+ if $FORCE; then
177
+ rm -rf "$PIPELINE_ARTIFACTS"
178
+ mkdir -p "$PIPELINE_ARTIFACTS"
179
+ ARTIFACTS_REMOVED=$((artifact_file_count))
180
+ echo -e " ${RED}✗${RESET} Cleaned ${artifact_file_count} files (${artifact_size})"
181
+ else
182
+ echo -e " ${YELLOW}○${RESET} Would clean: ${artifact_file_count} files (${artifact_size})"
183
+ fi
184
+ else
185
+ echo -e " ${DIM}No pipeline artifacts found.${RESET}"
186
+ fi
187
+ else
188
+ echo -e " ${DIM}No pipeline artifacts directory.${RESET}"
189
+ fi
190
+
191
+ # ─── 5. Checkpoints ────────────────────────────────────────────────────────
192
+
193
+ echo ""
194
+ echo -e "${BOLD}Checkpoints${RESET} ${DIM}.claude/pipeline-artifacts/checkpoints/${RESET}"
195
+ echo -e "${DIM}────────────────────────────────────────${RESET}"
196
+
197
+ CHECKPOINT_DIR=".claude/pipeline-artifacts/checkpoints"
198
+ if [[ -d "$CHECKPOINT_DIR" ]]; then
199
+ cp_file_count=0
200
+ for cp_file in "${CHECKPOINT_DIR}"/*-checkpoint.json; do
201
+ [[ -f "$cp_file" ]] || continue
202
+ cp_file_count=$((cp_file_count + 1))
203
+ done
204
+
205
+ if [[ "$cp_file_count" -gt 0 ]]; then
206
+ CHECKPOINTS_FOUND=$cp_file_count
207
+
208
+ if $FORCE; then
209
+ rm -f "${CHECKPOINT_DIR}"/*-checkpoint.json
210
+ CHECKPOINTS_REMOVED=$cp_file_count
211
+ echo -e " ${RED}✗${RESET} Removed ${cp_file_count} checkpoint(s)"
212
+ else
213
+ echo -e " ${YELLOW}○${RESET} Would remove: ${cp_file_count} checkpoint(s)"
214
+ fi
215
+ else
216
+ echo -e " ${DIM}No checkpoints found.${RESET}"
217
+ fi
218
+ else
219
+ echo -e " ${DIM}No checkpoint directory.${RESET}"
220
+ fi
221
+
222
+ # ─── 6. Pipeline State ─────────────────────────────────────────────────────
223
+
224
+ echo ""
225
+ echo -e "${BOLD}Pipeline State${RESET} ${DIM}.claude/pipeline-state.md${RESET}"
226
+ echo -e "${DIM}────────────────────────────────────────${RESET}"
227
+
228
+ PIPELINE_STATE=".claude/pipeline-state.md"
229
+ if [[ -f "$PIPELINE_STATE" ]]; then
230
+ state_status=$(sed -n 's/^status: *//p' "$PIPELINE_STATE" | head -1 || true)
231
+ state_issue=$(sed -n 's/^issue: *//p' "$PIPELINE_STATE" | head -1 || true)
232
+
233
+ case "${state_status:-}" in
234
+ complete|failed|idle|"")
235
+ if $FORCE; then
236
+ rm -f "$PIPELINE_STATE"
237
+ STATE_RESET=1
238
+ echo -e " ${RED}✗${RESET} Removed stale state (was: ${state_status:-empty}${state_issue:+, issue #$state_issue})"
239
+ else
240
+ echo -e " ${YELLOW}○${RESET} Would remove: status=${state_status:-empty}${state_issue:+, issue #$state_issue}"
241
+ fi
242
+ ;;
243
+ running|paused|interrupted)
244
+ echo -e " ${CYAN}●${RESET} Active pipeline: status=${state_status}${state_issue:+, issue #$state_issue} ${DIM}(skipping)${RESET}"
245
+ ;;
246
+ *)
247
+ echo -e " ${DIM}Unknown state: ${state_status}${RESET}"
248
+ ;;
249
+ esac
250
+ else
251
+ echo -e " ${DIM}No pipeline state file.${RESET}"
252
+ fi
253
+
254
+ # ─── 7. Stale Heartbeats ───────────────────────────────────────────────────
255
+
256
+ echo ""
257
+ echo -e "${BOLD}Heartbeats${RESET} ${DIM}~/.shipwright/heartbeats/${RESET}"
258
+ echo -e "${DIM}────────────────────────────────────────${RESET}"
259
+
260
+ HEARTBEAT_DIR="${HOME}/.shipwright/heartbeats"
261
+ if [[ -d "$HEARTBEAT_DIR" ]]; then
262
+ now_e=$(date +%s)
263
+ stale_threshold=3600 # 1 hour
264
+
265
+ while IFS= read -r hb_file; do
266
+ [[ -f "$hb_file" ]] || continue
267
+ hb_mtime=$(stat -f '%m' "$hb_file" 2>/dev/null || stat -c '%Y' "$hb_file" 2>/dev/null || echo "0")
268
+ if [[ $((now_e - hb_mtime)) -gt $stale_threshold ]]; then
269
+ HEARTBEATS_FOUND=$((HEARTBEATS_FOUND + 1))
270
+ hb_name=$(basename "$hb_file" .json)
271
+
272
+ if $FORCE; then
273
+ rm -f "$hb_file"
274
+ HEARTBEATS_REMOVED=$((HEARTBEATS_REMOVED + 1))
275
+ echo -e " ${RED}✗${RESET} Removed: ${hb_name} ${DIM}(stale >1h)${RESET}"
276
+ else
277
+ age_min=$(( (now_e - hb_mtime) / 60 ))
278
+ echo -e " ${YELLOW}○${RESET} Would remove: ${hb_name} ${DIM}(${age_min}m old)${RESET}"
279
+ fi
280
+ fi
281
+ done < <(find "$HEARTBEAT_DIR" -name '*.json' -type f 2>/dev/null)
282
+
283
+ if [[ "$HEARTBEATS_FOUND" -eq 0 ]]; then
284
+ echo -e " ${DIM}No stale heartbeats.${RESET}"
285
+ fi
286
+ else
287
+ echo -e " ${DIM}No heartbeat directory.${RESET}"
288
+ fi
289
+
290
+ # ─── 8. Orphaned pipeline/* branches ───────────────────────────────────────
291
+
292
+ echo ""
293
+ echo -e "${BOLD}Orphaned Branches${RESET} ${DIM}pipeline/* and daemon/*${RESET}"
294
+ echo -e "${DIM}────────────────────────────────────────${RESET}"
295
+
296
+ if command -v git &>/dev/null && git rev-parse --is-inside-work-tree &>/dev/null; then
297
+ # Collect active worktree paths
298
+ active_worktrees=""
299
+ while IFS= read -r wt_line; do
300
+ active_worktrees="${active_worktrees} ${wt_line}"
301
+ done < <(git worktree list --porcelain 2>/dev/null | grep '^worktree ' | sed 's/^worktree //')
302
+
303
+ while IFS= read -r branch; do
304
+ [[ -z "$branch" ]] && continue
305
+ branch="${branch## }" # trim leading spaces
306
+ # Check if this branch has an active worktree
307
+ has_worktree=false
308
+ for wt in $active_worktrees; do
309
+ if echo "$wt" | grep -q "${branch##*/}" 2>/dev/null; then
310
+ has_worktree=true
311
+ break
312
+ fi
313
+ done
314
+
315
+ if [[ "$has_worktree" == "false" ]]; then
316
+ BRANCHES_FOUND=$((BRANCHES_FOUND + 1))
317
+ if $FORCE; then
318
+ git branch -D "$branch" 2>/dev/null || true
319
+ BRANCHES_REMOVED=$((BRANCHES_REMOVED + 1))
320
+ echo -e " ${RED}✗${RESET} Deleted: ${branch}"
321
+ else
322
+ echo -e " ${YELLOW}○${RESET} Would delete: ${branch}"
323
+ fi
324
+ fi
325
+ done < <(git branch --list 'pipeline/*' --list 'daemon/*' 2>/dev/null)
326
+
327
+ if [[ "$BRANCHES_FOUND" -eq 0 ]]; then
328
+ echo -e " ${DIM}No orphaned branches.${RESET}"
329
+ fi
330
+ else
331
+ echo -e " ${DIM}Not in a git repository.${RESET}"
332
+ fi
333
+
334
+ # ─── Summary ─────────────────────────────────────────────────────────────────
335
+
336
+ echo ""
337
+ echo -e "${DIM}────────────────────────────────────────${RESET}"
338
+
339
+ TOTAL_FOUND=$((WINDOWS_FOUND + TEAM_DIRS_FOUND + TASK_DIRS_FOUND + ARTIFACTS_FOUND + CHECKPOINTS_FOUND + HEARTBEATS_FOUND + BRANCHES_FOUND + STATE_RESET))
340
+
341
+ if $FORCE; then
342
+ TOTAL_CLEANED=$((WINDOWS_KILLED + TEAM_DIRS_REMOVED + TASK_DIRS_REMOVED + ARTIFACTS_REMOVED + CHECKPOINTS_REMOVED + HEARTBEATS_REMOVED + BRANCHES_REMOVED + STATE_RESET))
343
+ if [[ $TOTAL_CLEANED -gt 0 ]]; then
344
+ success "Cleaned ${TOTAL_CLEANED} items"
345
+ echo -e " ${DIM}windows: ${WINDOWS_KILLED}, teams: ${TEAM_DIRS_REMOVED}, tasks: ${TASK_DIRS_REMOVED}${RESET}"
346
+ echo -e " ${DIM}artifacts: ${ARTIFACTS_REMOVED}, checkpoints: ${CHECKPOINTS_REMOVED}, heartbeats: ${HEARTBEATS_REMOVED}${RESET}"
347
+ echo -e " ${DIM}branches: ${BRANCHES_REMOVED}, state: ${STATE_RESET}${RESET}"
348
+ else
349
+ success "Nothing to clean up."
350
+ fi
351
+ else
352
+ if [[ $TOTAL_FOUND -gt 0 ]]; then
353
+ warn "Found ${TOTAL_FOUND} items to clean. Run with ${BOLD}--force${RESET} to remove them:"
354
+ echo -e " ${DIM}shipwright cleanup --force${RESET}"
355
+ else
356
+ success "Everything is clean. No orphaned sessions or artifacts found."
357
+ fi
358
+ fi
359
+ echo ""