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.
- package/LICENSE +21 -0
- package/README.md +926 -0
- package/claude-code/CLAUDE.md.shipwright +125 -0
- package/claude-code/hooks/notify-idle.sh +35 -0
- package/claude-code/hooks/pre-compact-save.sh +57 -0
- package/claude-code/hooks/task-completed.sh +170 -0
- package/claude-code/hooks/teammate-idle.sh +68 -0
- package/claude-code/settings.json.template +184 -0
- package/completions/_shipwright +140 -0
- package/completions/shipwright.bash +89 -0
- package/completions/shipwright.fish +107 -0
- package/docs/KNOWN-ISSUES.md +199 -0
- package/docs/TIPS.md +331 -0
- package/docs/definition-of-done.example.md +16 -0
- package/docs/patterns/README.md +139 -0
- package/docs/patterns/audit-loop.md +149 -0
- package/docs/patterns/bug-hunt.md +183 -0
- package/docs/patterns/feature-implementation.md +159 -0
- package/docs/patterns/refactoring.md +183 -0
- package/docs/patterns/research-exploration.md +144 -0
- package/docs/patterns/test-generation.md +173 -0
- package/package.json +49 -0
- package/scripts/adapters/docker-deploy.sh +50 -0
- package/scripts/adapters/fly-deploy.sh +41 -0
- package/scripts/adapters/iterm2-adapter.sh +122 -0
- package/scripts/adapters/railway-deploy.sh +34 -0
- package/scripts/adapters/tmux-adapter.sh +87 -0
- package/scripts/adapters/vercel-deploy.sh +35 -0
- package/scripts/adapters/wezterm-adapter.sh +103 -0
- package/scripts/cct +242 -0
- package/scripts/cct-cleanup.sh +172 -0
- package/scripts/cct-cost.sh +590 -0
- package/scripts/cct-daemon.sh +3189 -0
- package/scripts/cct-doctor.sh +328 -0
- package/scripts/cct-fix.sh +478 -0
- package/scripts/cct-fleet.sh +904 -0
- package/scripts/cct-init.sh +282 -0
- package/scripts/cct-logs.sh +273 -0
- package/scripts/cct-loop.sh +1332 -0
- package/scripts/cct-memory.sh +1148 -0
- package/scripts/cct-pipeline.sh +3844 -0
- package/scripts/cct-prep.sh +1352 -0
- package/scripts/cct-ps.sh +168 -0
- package/scripts/cct-reaper.sh +390 -0
- package/scripts/cct-session.sh +284 -0
- package/scripts/cct-status.sh +169 -0
- package/scripts/cct-templates.sh +242 -0
- package/scripts/cct-upgrade.sh +422 -0
- package/scripts/cct-worktree.sh +405 -0
- package/scripts/postinstall.mjs +96 -0
- package/templates/pipelines/autonomous.json +71 -0
- package/templates/pipelines/cost-aware.json +95 -0
- package/templates/pipelines/deployed.json +79 -0
- package/templates/pipelines/enterprise.json +114 -0
- package/templates/pipelines/fast.json +63 -0
- package/templates/pipelines/full.json +104 -0
- package/templates/pipelines/hotfix.json +63 -0
- package/templates/pipelines/standard.json +91 -0
- package/tmux/claude-teams-overlay.conf +109 -0
- package/tmux/templates/architecture.json +19 -0
- package/tmux/templates/bug-fix.json +24 -0
- package/tmux/templates/code-review.json +24 -0
- package/tmux/templates/devops.json +19 -0
- package/tmux/templates/documentation.json +19 -0
- package/tmux/templates/exploration.json +19 -0
- package/tmux/templates/feature-dev.json +24 -0
- package/tmux/templates/full-stack.json +24 -0
- package/tmux/templates/migration.json +24 -0
- package/tmux/templates/refactor.json +19 -0
- package/tmux/templates/security-audit.json +24 -0
- package/tmux/templates/testing.json +24 -0
- 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
|