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,242 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ cct-templates.sh — Browse and inspect team templates ║
4
+ # ║ ║
5
+ # ║ Templates define reusable agent team configurations (roles, layout, ║
6
+ # ║ focus areas) that shipwright session --template can use to scaffold teams. ║
7
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
8
+ set -euo pipefail
9
+
10
+ # ─── Colors ──────────────────────────────────────────────────────────────────
11
+ CYAN='\033[38;2;0;212;255m'
12
+ PURPLE='\033[38;2;124;58;237m'
13
+ BLUE='\033[38;2;0;102;255m'
14
+ GREEN='\033[38;2;74;222;128m'
15
+ YELLOW='\033[38;2;250;204;21m'
16
+ RED='\033[38;2;248;113;113m'
17
+ DIM='\033[2m'
18
+ BOLD='\033[1m'
19
+ RESET='\033[0m'
20
+
21
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
22
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
23
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
24
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
25
+
26
+ # ─── Template Discovery ─────────────────────────────────────────────────────
27
+
28
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
29
+ REPO_TEMPLATES_DIR="$(cd "$SCRIPT_DIR/../tmux/templates" 2>/dev/null && pwd)" || REPO_TEMPLATES_DIR=""
30
+ USER_TEMPLATES_DIR="${HOME}/.claude-teams/templates"
31
+
32
+ # Find all template directories (user dir takes priority)
33
+ find_template_dirs() {
34
+ local dirs=()
35
+ [[ -d "$USER_TEMPLATES_DIR" ]] && dirs+=("$USER_TEMPLATES_DIR")
36
+ [[ -n "$REPO_TEMPLATES_DIR" && -d "$REPO_TEMPLATES_DIR" ]] && dirs+=("$REPO_TEMPLATES_DIR")
37
+ printf '%s\n' "${dirs[@]}"
38
+ }
39
+
40
+ # Find a specific template file by name (user templates override repo)
41
+ find_template() {
42
+ local name="$1"
43
+ # Strip .json extension if provided
44
+ name="${name%.json}"
45
+
46
+ if [[ -f "$USER_TEMPLATES_DIR/${name}.json" ]]; then
47
+ echo "$USER_TEMPLATES_DIR/${name}.json"
48
+ return 0
49
+ fi
50
+ if [[ -n "$REPO_TEMPLATES_DIR" && -f "$REPO_TEMPLATES_DIR/${name}.json" ]]; then
51
+ echo "$REPO_TEMPLATES_DIR/${name}.json"
52
+ return 0
53
+ fi
54
+ return 1
55
+ }
56
+
57
+ # ─── JSON Parsing (jq preferred, grep fallback) ──────────────────────────────
58
+
59
+ # Extract a top-level string field from JSON
60
+ json_field() {
61
+ local file="$1" field="$2"
62
+ if command -v jq &>/dev/null; then
63
+ jq -r ".${field} // \"\"" "$file" 2>/dev/null
64
+ else
65
+ grep -o "\"${field}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "$file" | head -1 | sed 's/.*: *"//;s/"$//'
66
+ fi
67
+ }
68
+
69
+ # Extract agent count from JSON
70
+ json_agent_count() {
71
+ local file="$1"
72
+ if command -v jq &>/dev/null; then
73
+ jq -r '.agents // [] | length' "$file" 2>/dev/null
74
+ else
75
+ grep -c '"name"' "$file" 2>/dev/null | head -1
76
+ fi
77
+ }
78
+
79
+ # Print agent details from a template
80
+ print_agents() {
81
+ local file="$1"
82
+ if command -v jq &>/dev/null; then
83
+ jq -r '.agents // [] | .[] | "\(.name // "?")|\(.role // "")|\(.focus // "")"' "$file" 2>/dev/null
84
+ else
85
+ # Best-effort grep fallback for simple cases
86
+ grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' "$file" | sed 's/.*"name"[[:space:]]*:[[:space:]]*"//;s/"$//'
87
+ fi
88
+ }
89
+
90
+ # ─── Commands ────────────────────────────────────────────────────────────────
91
+
92
+ cmd_list() {
93
+ echo ""
94
+ echo -e "${CYAN}${BOLD} Team Templates${RESET}"
95
+ echo -e " ${DIM}─────────────────────────────────────────────${RESET}"
96
+ echo ""
97
+
98
+ local found=0
99
+
100
+ while IFS= read -r dir; do
101
+ [[ -z "$dir" ]] && continue
102
+ for file in "$dir"/*.json; do
103
+ [[ -f "$file" ]] || continue
104
+ found=1
105
+
106
+ local name description agent_count layout layout_style display_layout source
107
+ name="$(json_field "$file" "name")"
108
+ description="$(json_field "$file" "description")"
109
+ agent_count="$(json_agent_count "$file")"
110
+ layout="$(json_field "$file" "layout")"
111
+ layout_style="$(json_field "$file" "layout_style")"
112
+ display_layout="${layout_style:-$layout}"
113
+
114
+ # Tag user-created vs built-in
115
+ if [[ "$dir" == "$USER_TEMPLATES_DIR" ]]; then
116
+ source="${PURPLE}custom${RESET}"
117
+ else
118
+ source="${DIM}built-in${RESET}"
119
+ fi
120
+
121
+ echo -e " ${CYAN}${BOLD}${name}${RESET} ${DIM}(${agent_count} agents, ${display_layout})${RESET} [${source}]"
122
+ echo -e " ${description}"
123
+ echo ""
124
+ done
125
+ done < <(find_template_dirs)
126
+
127
+ if [[ "$found" -eq 0 ]]; then
128
+ warn "No templates found."
129
+ echo -e " Templates are loaded from:"
130
+ echo -e " ${DIM}${USER_TEMPLATES_DIR}/${RESET} ${DIM}(custom)${RESET}"
131
+ [[ -n "$REPO_TEMPLATES_DIR" ]] && echo -e " ${DIM}${REPO_TEMPLATES_DIR}/${RESET} ${DIM}(built-in)${RESET}"
132
+ echo ""
133
+ return 1
134
+ fi
135
+
136
+ echo -e " ${DIM}Usage: shipwright session my-feature --template <name>${RESET}"
137
+ echo -e " ${DIM}Details: shipwright templates show <name>${RESET}"
138
+ echo ""
139
+ }
140
+
141
+ cmd_show() {
142
+ local name="${1:-}"
143
+ if [[ -z "$name" ]]; then
144
+ error "Template name required."
145
+ echo -e " Usage: ${DIM}shipwright templates show <name>${RESET}"
146
+ exit 1
147
+ fi
148
+
149
+ local file
150
+ if ! file="$(find_template "$name")"; then
151
+ error "Template '${name}' not found."
152
+ echo ""
153
+ echo -e " Available templates:"
154
+ while IFS= read -r dir; do
155
+ [[ -z "$dir" ]] && continue
156
+ for f in "$dir"/*.json; do
157
+ [[ -f "$f" ]] || continue
158
+ local tname
159
+ tname="$(json_field "$f" "name")"
160
+ echo -e " ${CYAN}${tname}${RESET}"
161
+ done
162
+ done < <(find_template_dirs)
163
+ echo ""
164
+ exit 1
165
+ fi
166
+
167
+ local description layout layout_style main_pane_pct
168
+ description="$(json_field "$file" "description")"
169
+ layout="$(json_field "$file" "layout")"
170
+ layout_style="$(json_field "$file" "layout_style")"
171
+ main_pane_pct="$(json_field "$file" "main_pane_percent")"
172
+
173
+ echo ""
174
+ echo -e " ${CYAN}${BOLD}Template: ${name}${RESET}"
175
+ echo -e " ${DIM}─────────────────────────────────────────────${RESET}"
176
+ echo -e " ${description}"
177
+ if [[ -n "$layout_style" ]]; then
178
+ echo -e " Layout: ${BOLD}${layout_style}${RESET} ${DIM}(leader pane ${main_pane_pct:-65}%)${RESET}"
179
+ else
180
+ echo -e " Layout: ${BOLD}${layout}${RESET}"
181
+ fi
182
+ echo ""
183
+ echo -e " ${BOLD}Agents:${RESET}"
184
+
185
+ while IFS='|' read -r aname arole afocus; do
186
+ [[ -z "$aname" ]] && continue
187
+ echo -e " ${PURPLE}${BOLD}${aname}${RESET}"
188
+ echo -e " Role: ${arole}"
189
+ echo -e " Focus: ${DIM}${afocus}${RESET}"
190
+ echo ""
191
+ done < <(print_agents "$file")
192
+
193
+ echo -e " ${DIM}Use: shipwright session my-feature --template ${name}${RESET}"
194
+ echo ""
195
+ }
196
+
197
+ show_help() {
198
+ echo ""
199
+ echo -e "${CYAN}${BOLD} shipwright templates${RESET} — Browse and inspect team templates"
200
+ echo ""
201
+ echo -e " ${BOLD}USAGE${RESET}"
202
+ echo -e " ${CYAN}shipwright templates${RESET} list List available templates"
203
+ echo -e " ${CYAN}shipwright templates${RESET} show <name> Show template details"
204
+ echo ""
205
+ echo -e " ${BOLD}TEMPLATE LOCATIONS${RESET}"
206
+ echo -e " ${DIM}~/.claude-teams/templates/${RESET} Custom templates ${DIM}(takes priority)${RESET}"
207
+ [[ -n "$REPO_TEMPLATES_DIR" ]] && echo -e " ${DIM}${REPO_TEMPLATES_DIR}/${RESET} Built-in templates"
208
+ echo ""
209
+ echo -e " ${BOLD}CREATING TEMPLATES${RESET}"
210
+ echo -e " Drop a JSON file in ${DIM}~/.claude-teams/templates/${RESET}:"
211
+ echo ""
212
+ echo -e " ${DIM}{"
213
+ echo -e " \"name\": \"my-template\","
214
+ echo -e " \"description\": \"What this team does\","
215
+ echo -e " \"agents\": ["
216
+ echo -e " {\"name\": \"agent-1\", \"role\": \"Does X\", \"focus\": \"src/\"},"
217
+ echo -e " {\"name\": \"agent-2\", \"role\": \"Does Y\", \"focus\": \"tests/\"}"
218
+ echo -e " ],"
219
+ echo -e " \"layout\": \"tiled\""
220
+ echo -e " }${RESET}"
221
+ echo ""
222
+ }
223
+
224
+ # ─── Main ────────────────────────────────────────────────────────────────────
225
+
226
+ main() {
227
+ local subcmd="${1:-list}"
228
+ shift 2>/dev/null || true
229
+
230
+ case "$subcmd" in
231
+ list|ls) cmd_list ;;
232
+ show|info) cmd_show "$@" ;;
233
+ help|--help) show_help ;;
234
+ *)
235
+ error "Unknown subcommand: ${subcmd}"
236
+ show_help
237
+ exit 1
238
+ ;;
239
+ esac
240
+ }
241
+
242
+ main "$@"
@@ -0,0 +1,422 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ cct upgrade — Detect and apply updates from the repo ║
4
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ MANIFEST_DIR="$HOME/.claude-teams"
9
+ MANIFEST="$MANIFEST_DIR/manifest.json"
10
+
11
+ # ─── Colors ────────────────────────────────────────────────────────────────
12
+ CYAN='\033[38;2;0;212;255m'
13
+ PURPLE='\033[38;2;124;58;237m'
14
+ BLUE='\033[38;2;0;102;255m'
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 flags ───────────────────────────────────────────────────────────
28
+ APPLY=false
29
+ REPO_OVERRIDE=""
30
+
31
+ for arg in "$@"; do
32
+ case "$arg" in
33
+ --apply) APPLY=true ;;
34
+ --repo-path) shift_next=true ;;
35
+ --repo-path=*) REPO_OVERRIDE="${arg#--repo-path=}" ;;
36
+ *)
37
+ if [[ "${shift_next:-}" == "true" ]]; then
38
+ REPO_OVERRIDE="$arg"
39
+ shift_next=false
40
+ fi
41
+ ;;
42
+ esac
43
+ done
44
+
45
+ # ─── Locate repo ──────────────────────────────────────────────────────────
46
+ find_repo() {
47
+ # 1. Explicit override
48
+ if [[ -n "$REPO_OVERRIDE" ]]; then
49
+ echo "$REPO_OVERRIDE"
50
+ return
51
+ fi
52
+
53
+ # 2. Environment variable
54
+ if [[ -n "${CCT_REPO_PATH:-}" ]]; then
55
+ echo "$CCT_REPO_PATH"
56
+ return
57
+ fi
58
+
59
+ # 3. Walk up from script dir
60
+ local dir="$SCRIPT_DIR"
61
+ while [[ "$dir" != "/" ]]; do
62
+ if [[ -f "$dir/install.sh" && -d "$dir/scripts" && -f "$dir/scripts/cct" ]]; then
63
+ echo "$dir"
64
+ return
65
+ fi
66
+ dir="$(dirname "$dir")"
67
+ done
68
+
69
+ # 4. Check manifest for stored repo_path
70
+ if [[ -f "$MANIFEST" ]]; then
71
+ local stored
72
+ stored="$(jq -r '.repo_path // ""' "$MANIFEST" 2>/dev/null || true)"
73
+ if [[ -n "$stored" && -d "$stored" ]]; then
74
+ echo "$stored"
75
+ return
76
+ fi
77
+ fi
78
+
79
+ return 1
80
+ }
81
+
82
+ REPO_PATH="$(find_repo)" || {
83
+ error "Cannot locate the Shipwright repo."
84
+ echo ""
85
+ echo -e " Try one of:"
86
+ echo -e " ${DIM}export CCT_REPO_PATH=/path/to/shipwright${RESET}"
87
+ echo -e " ${DIM}shipwright upgrade --repo-path /path/to/shipwright${RESET}"
88
+ exit 1
89
+ }
90
+
91
+ # ─── File registry ─────────────────────────────────────────────────────────
92
+ # Each entry: "key|src_relative|dest_absolute|protected|executable"
93
+ BIN_DIR="$HOME/.local/bin"
94
+
95
+ FILES=(
96
+ "tmux.conf|tmux/tmux.conf|$HOME/.tmux.conf|false|false"
97
+ "claude-teams-overlay.conf|tmux/claude-teams-overlay.conf|$HOME/.tmux/claude-teams-overlay.conf|false|false"
98
+ "settings.json.template|claude-code/settings.json.template|$HOME/.claude/settings.json.template|false|false"
99
+ "settings.json||$HOME/.claude/settings.json|true|false"
100
+ "cct|scripts/cct|$BIN_DIR/cct|false|true"
101
+ "cct-session.sh|scripts/cct-session.sh|$BIN_DIR/cct-session.sh|false|true"
102
+ "cct-status.sh|scripts/cct-status.sh|$BIN_DIR/cct-status.sh|false|true"
103
+ "cct-cleanup.sh|scripts/cct-cleanup.sh|$BIN_DIR/cct-cleanup.sh|false|true"
104
+ "cct-upgrade.sh|scripts/cct-upgrade.sh|$BIN_DIR/cct-upgrade.sh|false|true"
105
+ "cct-doctor.sh|scripts/cct-doctor.sh|$BIN_DIR/cct-doctor.sh|false|true"
106
+ "cct-logs.sh|scripts/cct-logs.sh|$BIN_DIR/cct-logs.sh|false|true"
107
+ "cct-ps.sh|scripts/cct-ps.sh|$BIN_DIR/cct-ps.sh|false|true"
108
+ "cct-templates.sh|scripts/cct-templates.sh|$BIN_DIR/cct-templates.sh|false|true"
109
+ "cct-loop.sh|scripts/cct-loop.sh|$BIN_DIR/cct-loop.sh|false|true"
110
+ "cct-pipeline.sh|scripts/cct-pipeline.sh|$BIN_DIR/cct-pipeline.sh|false|true"
111
+ "cct-pipeline-test.sh|scripts/cct-pipeline-test.sh|$BIN_DIR/cct-pipeline-test.sh|false|true"
112
+ "cct-worktree.sh|scripts/cct-worktree.sh|$BIN_DIR/cct-worktree.sh|false|true"
113
+ "cct-init.sh|scripts/cct-init.sh|$BIN_DIR/cct-init.sh|false|true"
114
+ "cct-prep.sh|scripts/cct-prep.sh|$BIN_DIR/cct-prep.sh|false|true"
115
+ "cct-daemon.sh|scripts/cct-daemon.sh|$BIN_DIR/cct-daemon.sh|false|true"
116
+ "cct-daemon-test.sh|scripts/cct-daemon-test.sh|$BIN_DIR/cct-daemon-test.sh|false|true"
117
+ "cct-prep-test.sh|scripts/cct-prep-test.sh|$BIN_DIR/cct-prep-test.sh|false|true"
118
+ "cct-memory.sh|scripts/cct-memory.sh|$BIN_DIR/cct-memory.sh|false|true"
119
+ "cct-memory-test.sh|scripts/cct-memory-test.sh|$BIN_DIR/cct-memory-test.sh|false|true"
120
+ "cct-cost.sh|scripts/cct-cost.sh|$BIN_DIR/cct-cost.sh|false|true"
121
+ "cct-fleet.sh|scripts/cct-fleet.sh|$BIN_DIR/cct-fleet.sh|false|true"
122
+ "cct-fleet-test.sh|scripts/cct-fleet-test.sh|$BIN_DIR/cct-fleet-test.sh|false|true"
123
+ "cct-fix.sh|scripts/cct-fix.sh|$BIN_DIR/cct-fix.sh|false|true"
124
+ "cct-fix-test.sh|scripts/cct-fix-test.sh|$BIN_DIR/cct-fix-test.sh|false|true"
125
+ "cct-reaper.sh|scripts/cct-reaper.sh|$BIN_DIR/cct-reaper.sh|false|true"
126
+ "CLAUDE.md.shipwright|claude-code/CLAUDE.md.shipwright|$HOME/.claude/CLAUDE.md|true|false"
127
+ "teammate-idle.sh|claude-code/hooks/teammate-idle.sh|$HOME/.claude/hooks/teammate-idle.sh|false|true"
128
+ "task-completed.sh|claude-code/hooks/task-completed.sh|$HOME/.claude/hooks/task-completed.sh|false|true"
129
+ "notify-idle.sh|claude-code/hooks/notify-idle.sh|$HOME/.claude/hooks/notify-idle.sh|false|true"
130
+ "pre-compact-save.sh|claude-code/hooks/pre-compact-save.sh|$HOME/.claude/hooks/pre-compact-save.sh|false|true"
131
+ "feature-dev.json|tmux/templates/feature-dev.json|$HOME/.claude-teams/templates/feature-dev.json|false|false"
132
+ "code-review.json|tmux/templates/code-review.json|$HOME/.claude-teams/templates/code-review.json|false|false"
133
+ "refactor.json|tmux/templates/refactor.json|$HOME/.claude-teams/templates/refactor.json|false|false"
134
+ "exploration.json|tmux/templates/exploration.json|$HOME/.claude-teams/templates/exploration.json|false|false"
135
+ "definition-of-done.example.md|docs/definition-of-done.example.md|$HOME/.claude-teams/templates/definition-of-done.example.md|false|false"
136
+ "pipeline-standard.json|templates/pipelines/standard.json|$HOME/.claude-teams/pipelines/standard.json|false|false"
137
+ "pipeline-fast.json|templates/pipelines/fast.json|$HOME/.claude-teams/pipelines/fast.json|false|false"
138
+ "pipeline-full.json|templates/pipelines/full.json|$HOME/.claude-teams/pipelines/full.json|false|false"
139
+ "pipeline-hotfix.json|templates/pipelines/hotfix.json|$HOME/.claude-teams/pipelines/hotfix.json|false|false"
140
+ "pipeline-autonomous.json|templates/pipelines/autonomous.json|$HOME/.claude-teams/pipelines/autonomous.json|false|false"
141
+ "pipeline-cost-aware.json|templates/pipelines/cost-aware.json|$HOME/.claude-teams/pipelines/cost-aware.json|false|false"
142
+ "pipeline-enterprise.json|templates/pipelines/enterprise.json|$HOME/.claude-teams/pipelines/enterprise.json|false|false"
143
+ )
144
+
145
+ # ─── Checksum helper ──────────────────────────────────────────────────────
146
+ file_checksum() {
147
+ local file="$1"
148
+ if [[ -f "$file" ]]; then
149
+ md5 -q "$file" 2>/dev/null || md5sum "$file" 2>/dev/null | awk '{print $1}'
150
+ else
151
+ echo ""
152
+ fi
153
+ }
154
+
155
+ # ─── Manifest helpers ─────────────────────────────────────────────────────
156
+ read_manifest_checksum() {
157
+ local key="$1"
158
+ if [[ ! -f "$MANIFEST" ]]; then
159
+ echo ""
160
+ return
161
+ fi
162
+ jq -r ".files[\"$key\"].checksum // \"\"" "$MANIFEST" 2>/dev/null || echo ""
163
+ }
164
+
165
+ write_manifest() {
166
+ mkdir -p "$MANIFEST_DIR"
167
+ local json='{\n "schema": 1,\n "version": "1.1.0",\n "installed_at": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",\n "repo_path": "'"$REPO_PATH"'",\n "files": {'
168
+ local first=true
169
+
170
+ for entry in "${FILES[@]}"; do
171
+ IFS='|' read -r key src dest protected executable <<< "$entry"
172
+ # Only include files that exist at dest
173
+ if [[ ! -f "$dest" ]]; then
174
+ continue
175
+ fi
176
+ local cksum
177
+ cksum="$(file_checksum "$dest")"
178
+
179
+ if ! $first; then json+=','; fi
180
+ first=false
181
+
182
+ json+='\n "'"$key"'": {'
183
+ if [[ -n "$src" ]]; then
184
+ json+='\n "src": "'"$src"'",'
185
+ fi
186
+ json+='\n "dest": "'"$dest"'",'
187
+ json+='\n "checksum": "'"$cksum"'",'
188
+ json+='\n "protected": '"$protected"','
189
+ json+='\n "executable": '"$executable"
190
+ json+='\n }'
191
+ done
192
+
193
+ json+='\n }\n}'
194
+ echo -e "$json" > "$MANIFEST"
195
+ }
196
+
197
+ # ─── Bootstrap manifest if missing ────────────────────────────────────────
198
+ bootstrap_manifest() {
199
+ echo ""
200
+ warn "No upgrade manifest found at $MANIFEST"
201
+ info "Bootstrapping from currently installed files..."
202
+ echo ""
203
+
204
+ local found=0
205
+ for entry in "${FILES[@]}"; do
206
+ IFS='|' read -r key _ dest _ _ <<< "$entry"
207
+ if [[ -f "$dest" ]]; then
208
+ echo -e " ${GREEN}✓${RESET} ${DIM}$key${RESET} → $dest"
209
+ ((found++))
210
+ fi
211
+ done
212
+
213
+ echo ""
214
+ if [[ $found -eq 0 ]]; then
215
+ error "No installed files found. Run install.sh first."
216
+ exit 1
217
+ fi
218
+
219
+ info "Found $found installed files. Writing manifest..."
220
+ write_manifest
221
+ success "Manifest created at $MANIFEST"
222
+ echo ""
223
+ }
224
+
225
+ # ─── Main logic ────────────────────────────────────────────────────────────
226
+ echo ""
227
+ echo -e "${CYAN}${BOLD}shipwright${RESET} ${DIM}v1.3.0${RESET} — ${BOLD}$(if $APPLY; then echo "Applying Upgrade"; else echo "Upgrade Check"; fi)${RESET}"
228
+ echo -e "${CYAN}═══════════════════════════════════════════════${RESET}"
229
+ echo ""
230
+ echo -e "Comparing installed files against repo at:"
231
+ echo -e " ${DIM}$REPO_PATH${RESET}"
232
+
233
+ # Bootstrap if needed
234
+ if [[ ! -f "$MANIFEST" ]]; then
235
+ bootstrap_manifest
236
+ fi
237
+
238
+ # ─── Diff detection ───────────────────────────────────────────────────────
239
+ declare -a UPGRADEABLE=()
240
+ declare -a UP_TO_DATE=()
241
+ declare -a CONFLICTS=()
242
+ declare -a MISSING=()
243
+ declare -a PROTECTED=()
244
+
245
+ for entry in "${FILES[@]}"; do
246
+ IFS='|' read -r key src dest protected executable <<< "$entry"
247
+
248
+ # Protected files — never auto-upgrade
249
+ if [[ "$protected" == "true" ]]; then
250
+ PROTECTED+=("$key|$dest")
251
+ continue
252
+ fi
253
+
254
+ # No source in repo means it's user-only (shouldn't happen for non-protected, but guard)
255
+ if [[ -z "$src" ]]; then
256
+ continue
257
+ fi
258
+
259
+ local_src="$REPO_PATH/$src"
260
+
261
+ # Source file missing from repo (shouldn't happen unless repo is incomplete)
262
+ if [[ ! -f "$local_src" ]]; then
263
+ continue
264
+ fi
265
+
266
+ repo_hash="$(file_checksum "$local_src")"
267
+ manifest_hash="$(read_manifest_checksum "$key")"
268
+ installed_hash="$(file_checksum "$dest")"
269
+
270
+ if [[ -z "$installed_hash" ]]; then
271
+ # File missing on disk
272
+ MISSING+=("$key|$src|$dest|$executable")
273
+ elif [[ "$repo_hash" == "$manifest_hash" ]]; then
274
+ # Repo hasn't changed since last install/upgrade
275
+ UP_TO_DATE+=("$key")
276
+ elif [[ "$installed_hash" == "$manifest_hash" ]]; then
277
+ # Repo changed, user hasn't touched it → safe to upgrade
278
+ UPGRADEABLE+=("$key|$src|$dest|$executable")
279
+ else
280
+ # Both repo and user changed → conflict
281
+ CONFLICTS+=("$key|$src|$dest")
282
+ fi
283
+ done
284
+
285
+ # ─── Display results ──────────────────────────────────────────────────────
286
+ echo ""
287
+
288
+ if [[ ${#UPGRADEABLE[@]} -gt 0 ]]; then
289
+ echo -e "${GREEN}${BOLD}UPGRADEABLE${RESET} ${DIM}(repo has newer version):${RESET}"
290
+ for item in "${UPGRADEABLE[@]}"; do
291
+ IFS='|' read -r key src dest _ <<< "$item"
292
+ printf " ${GREEN}✓${RESET} %-28s ${DIM}%s → %s${RESET}\n" "$key" "$src" "$dest"
293
+ done
294
+ echo ""
295
+ fi
296
+
297
+ if [[ ${#MISSING[@]} -gt 0 ]]; then
298
+ echo -e "${YELLOW}${BOLD}MISSING${RESET} ${DIM}(not found on disk — will reinstall):${RESET}"
299
+ for item in "${MISSING[@]}"; do
300
+ IFS='|' read -r key src dest _ <<< "$item"
301
+ printf " ${YELLOW}?${RESET} %-28s ${DIM}%s → %s${RESET}\n" "$key" "$src" "$dest"
302
+ done
303
+ echo ""
304
+ fi
305
+
306
+ if [[ ${#CONFLICTS[@]} -gt 0 ]]; then
307
+ echo -e "${RED}${BOLD}CONFLICTS${RESET} ${DIM}(both repo and local file changed — skipped):${RESET}"
308
+ for item in "${CONFLICTS[@]}"; do
309
+ IFS='|' read -r key src dest <<< "$item"
310
+ printf " ${RED}!${RESET} %-28s ${DIM}review manually: diff %s %s${RESET}\n" "$key" "$REPO_PATH/$src" "$dest"
311
+ done
312
+ echo ""
313
+ fi
314
+
315
+ if [[ ${#UP_TO_DATE[@]} -gt 0 ]]; then
316
+ echo -e "${DIM}UP TO DATE:${RESET}"
317
+ for key in "${UP_TO_DATE[@]}"; do
318
+ echo -e " ${DIM}○ $key${RESET}"
319
+ done
320
+ echo ""
321
+ fi
322
+
323
+ if [[ ${#PROTECTED[@]} -gt 0 ]]; then
324
+ echo -e "${PURPLE}${BOLD}PROTECTED${RESET} ${DIM}(never auto-upgraded):${RESET}"
325
+ for item in "${PROTECTED[@]}"; do
326
+ IFS='|' read -r key dest <<< "$item"
327
+ echo -e " ${PURPLE}✗${RESET} ${key} ${DIM}— user config, review template for new options${RESET}"
328
+ done
329
+ echo ""
330
+ fi
331
+
332
+ # ─── Summary ──────────────────────────────────────────────────────────────
333
+ total_actionable=$(( ${#UPGRADEABLE[@]} + ${#MISSING[@]} ))
334
+ echo -e "${BOLD}SUMMARY:${RESET} ${GREEN}$total_actionable upgradeable${RESET}, ${#UP_TO_DATE[@]} up to date, ${#CONFLICTS[@]} conflicts, ${#PROTECTED[@]} protected"
335
+ echo ""
336
+
337
+ if [[ $total_actionable -eq 0 && ${#CONFLICTS[@]} -eq 0 ]]; then
338
+ success "Everything is up to date!"
339
+ exit 0
340
+ fi
341
+
342
+ # ─── Apply ────────────────────────────────────────────────────────────────
343
+ if ! $APPLY; then
344
+ if [[ $total_actionable -gt 0 ]]; then
345
+ echo -e "Run with ${BOLD}--apply${RESET} to upgrade:"
346
+ echo -e " ${DIM}shipwright upgrade --apply${RESET}"
347
+ echo ""
348
+ fi
349
+ if [[ ${#CONFLICTS[@]} -gt 0 ]]; then
350
+ warn "Conflicting files must be resolved manually."
351
+ fi
352
+ exit 0
353
+ fi
354
+
355
+ # Apply upgrades
356
+ CCT_SELF_UPGRADED=false
357
+
358
+ echo -e "${BOLD}Applying...${RESET}"
359
+ echo ""
360
+
361
+ apply_file() {
362
+ local key="$1" src="$2" dest="$3" executable="$4"
363
+ local src_full="$REPO_PATH/$src"
364
+
365
+ # Create parent directory if needed
366
+ local dest_dir
367
+ dest_dir="$(dirname "$dest")"
368
+ mkdir -p "$dest_dir"
369
+
370
+ # Backup existing file
371
+ if [[ -f "$dest" ]]; then
372
+ cp "$dest" "${dest}.pre-upgrade.bak"
373
+ echo -e " ${DIM}Backed up: $dest → ${dest}.pre-upgrade.bak${RESET}"
374
+ fi
375
+
376
+ # Copy new version
377
+ cp "$src_full" "$dest"
378
+ if [[ "$executable" == "true" ]]; then
379
+ chmod +x "$dest"
380
+ fi
381
+
382
+ echo -e " ${GREEN}✓${RESET} Updated: ${BOLD}$key${RESET}"
383
+ }
384
+
385
+ # Process upgradeable files
386
+ if [[ ${#UPGRADEABLE[@]} -gt 0 ]]; then
387
+ for item in "${UPGRADEABLE[@]}"; do
388
+ IFS='|' read -r key src dest executable <<< "$item"
389
+ apply_file "$key" "$src" "$dest" "$executable"
390
+ if [[ "$key" == cct* ]]; then
391
+ CCT_SELF_UPGRADED=true
392
+ fi
393
+ done
394
+ fi
395
+
396
+ # Process missing files (reinstall)
397
+ if [[ ${#MISSING[@]} -gt 0 ]]; then
398
+ for item in "${MISSING[@]}"; do
399
+ IFS='|' read -r key src dest executable <<< "$item"
400
+ apply_file "$key" "$src" "$dest" "$executable"
401
+ done
402
+ fi
403
+
404
+ echo ""
405
+
406
+ # Rebuild manifest with current checksums for all installed files
407
+ write_manifest
408
+ success "Manifest updated: $MANIFEST"
409
+
410
+ # Self-upgrade warning
411
+ if $CCT_SELF_UPGRADED; then
412
+ echo ""
413
+ echo -e "${YELLOW}${BOLD}⚠${RESET} The Shipwright CLI itself was upgraded."
414
+ echo -e " Your current command completed, but re-run to use the new version."
415
+ fi
416
+ echo ""
417
+
418
+ # Tip for tmux users
419
+ if [[ ${#UPGRADEABLE[@]} -gt 0 ]] && printf '%s\n' "${UPGRADEABLE[@]}" | grep -q "tmux\|overlay"; then
420
+ info "Tip: Reload tmux config with: ${DIM}prefix + r${RESET}"
421
+ echo ""
422
+ fi