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.
- package/.claude/agents/code-reviewer.md +90 -0
- package/.claude/agents/devops-engineer.md +142 -0
- package/.claude/agents/pipeline-agent.md +80 -0
- package/.claude/agents/shell-script-specialist.md +150 -0
- package/.claude/agents/test-specialist.md +196 -0
- package/.claude/hooks/post-tool-use.sh +45 -0
- package/.claude/hooks/pre-tool-use.sh +25 -0
- package/.claude/hooks/session-started.sh +37 -0
- package/README.md +212 -814
- package/claude-code/CLAUDE.md.shipwright +54 -0
- package/claude-code/hooks/notify-idle.sh +2 -2
- package/claude-code/hooks/session-start.sh +24 -0
- package/claude-code/hooks/task-completed.sh +6 -2
- package/claude-code/settings.json.template +12 -0
- package/dashboard/public/app.js +4422 -0
- package/dashboard/public/index.html +816 -0
- package/dashboard/public/styles.css +4755 -0
- package/dashboard/server.ts +4315 -0
- package/docs/KNOWN-ISSUES.md +18 -10
- package/docs/TIPS.md +38 -26
- package/docs/patterns/README.md +33 -23
- package/package.json +9 -5
- package/scripts/adapters/iterm2-adapter.sh +1 -1
- package/scripts/adapters/tmux-adapter.sh +52 -23
- package/scripts/adapters/wezterm-adapter.sh +26 -14
- package/scripts/lib/compat.sh +200 -0
- package/scripts/lib/helpers.sh +72 -0
- package/scripts/postinstall.mjs +72 -13
- package/scripts/{cct → sw} +118 -22
- package/scripts/sw-adversarial.sh +274 -0
- package/scripts/sw-architecture-enforcer.sh +330 -0
- package/scripts/sw-checkpoint.sh +468 -0
- package/scripts/sw-cleanup.sh +359 -0
- package/scripts/sw-connect.sh +619 -0
- package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
- package/scripts/sw-daemon.sh +5574 -0
- package/scripts/sw-dashboard.sh +477 -0
- package/scripts/sw-developer-simulation.sh +252 -0
- package/scripts/sw-docs.sh +635 -0
- package/scripts/sw-doctor.sh +907 -0
- package/scripts/{cct-fix.sh → sw-fix.sh} +10 -6
- package/scripts/{cct-fleet.sh → sw-fleet.sh} +498 -22
- package/scripts/sw-github-checks.sh +521 -0
- package/scripts/sw-github-deploy.sh +533 -0
- package/scripts/sw-github-graphql.sh +972 -0
- package/scripts/sw-heartbeat.sh +293 -0
- package/scripts/{cct-init.sh → sw-init.sh} +144 -11
- package/scripts/sw-intelligence.sh +1196 -0
- package/scripts/sw-jira.sh +643 -0
- package/scripts/sw-launchd.sh +364 -0
- package/scripts/sw-linear.sh +648 -0
- package/scripts/{cct-logs.sh → sw-logs.sh} +72 -2
- package/scripts/sw-loop.sh +2217 -0
- package/scripts/{cct-memory.sh → sw-memory.sh} +514 -36
- package/scripts/sw-patrol-meta.sh +417 -0
- package/scripts/sw-pipeline-composer.sh +455 -0
- package/scripts/sw-pipeline-vitals.sh +1096 -0
- package/scripts/sw-pipeline.sh +7593 -0
- package/scripts/sw-predictive.sh +820 -0
- package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
- package/scripts/{cct-ps.sh → sw-ps.sh} +9 -6
- package/scripts/{cct-reaper.sh → sw-reaper.sh} +10 -6
- package/scripts/sw-remote.sh +687 -0
- package/scripts/sw-self-optimize.sh +1048 -0
- package/scripts/sw-session.sh +541 -0
- package/scripts/sw-setup.sh +234 -0
- package/scripts/sw-status.sh +796 -0
- package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
- package/scripts/sw-tmux.sh +591 -0
- package/scripts/sw-tracker-jira.sh +277 -0
- package/scripts/sw-tracker-linear.sh +292 -0
- package/scripts/sw-tracker.sh +409 -0
- package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
- package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
- package/templates/pipelines/autonomous.json +35 -6
- package/templates/pipelines/cost-aware.json +21 -0
- package/templates/pipelines/deployed.json +40 -6
- package/templates/pipelines/enterprise.json +16 -2
- package/templates/pipelines/fast.json +19 -0
- package/templates/pipelines/full.json +28 -2
- package/templates/pipelines/hotfix.json +19 -0
- package/templates/pipelines/standard.json +31 -0
- package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
- package/tmux/templates/accessibility.json +34 -0
- package/tmux/templates/api-design.json +35 -0
- package/tmux/templates/architecture.json +1 -0
- package/tmux/templates/bug-fix.json +9 -0
- package/tmux/templates/code-review.json +1 -0
- package/tmux/templates/compliance.json +36 -0
- package/tmux/templates/data-pipeline.json +36 -0
- package/tmux/templates/debt-paydown.json +34 -0
- package/tmux/templates/devops.json +1 -0
- package/tmux/templates/documentation.json +1 -0
- package/tmux/templates/exploration.json +1 -0
- package/tmux/templates/feature-dev.json +1 -0
- package/tmux/templates/full-stack.json +8 -0
- package/tmux/templates/i18n.json +34 -0
- package/tmux/templates/incident-response.json +36 -0
- package/tmux/templates/migration.json +1 -0
- package/tmux/templates/observability.json +35 -0
- package/tmux/templates/onboarding.json +33 -0
- package/tmux/templates/performance.json +35 -0
- package/tmux/templates/refactor.json +1 -0
- package/tmux/templates/release.json +35 -0
- package/tmux/templates/security-audit.json +8 -0
- package/tmux/templates/spike.json +34 -0
- package/tmux/templates/testing.json +1 -0
- package/tmux/tmux.conf +98 -9
- package/scripts/cct-cleanup.sh +0 -172
- package/scripts/cct-daemon.sh +0 -3189
- package/scripts/cct-doctor.sh +0 -414
- package/scripts/cct-loop.sh +0 -1332
- package/scripts/cct-pipeline.sh +0 -3844
- package/scripts/cct-session.sh +0 -284
- package/scripts/cct-status.sh +0 -169
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ sw-checkpoint.sh — Save and restore agent state mid-stage ║
|
|
4
|
+
# ║ ║
|
|
5
|
+
# ║ Checkpoints capture enough state to resume a pipeline stage without ║
|
|
6
|
+
# ║ restarting from scratch. ║
|
|
7
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
10
|
+
|
|
11
|
+
VERSION="1.10.0"
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
|
|
14
|
+
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
15
|
+
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
16
|
+
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
17
|
+
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
18
|
+
GREEN='\033[38;2;74;222;128m' # success
|
|
19
|
+
YELLOW='\033[38;2;250;204;21m' # warning
|
|
20
|
+
RED='\033[38;2;248;113;113m' # error
|
|
21
|
+
DIM='\033[2m'
|
|
22
|
+
BOLD='\033[1m'
|
|
23
|
+
RESET='\033[0m'
|
|
24
|
+
|
|
25
|
+
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
26
|
+
# shellcheck source=lib/compat.sh
|
|
27
|
+
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
28
|
+
# ─── Output Helpers ─────────────────────────────────────────────────────────
|
|
29
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
30
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
31
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
32
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
33
|
+
|
|
34
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
35
|
+
|
|
36
|
+
# ─── Checkpoint Directory ───────────────────────────────────────────────────
|
|
37
|
+
CHECKPOINT_DIR=".claude/pipeline-artifacts/checkpoints"
|
|
38
|
+
|
|
39
|
+
ensure_checkpoint_dir() {
|
|
40
|
+
mkdir -p "$CHECKPOINT_DIR"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
checkpoint_file() {
|
|
44
|
+
local stage="$1"
|
|
45
|
+
echo "${CHECKPOINT_DIR}/${stage}-checkpoint.json"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ─── Save ────────────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
cmd_save() {
|
|
51
|
+
local stage=""
|
|
52
|
+
local iteration=""
|
|
53
|
+
local git_sha=""
|
|
54
|
+
local files_modified=""
|
|
55
|
+
local tests_passing="false"
|
|
56
|
+
local loop_state=""
|
|
57
|
+
|
|
58
|
+
while [[ $# -gt 0 ]]; do
|
|
59
|
+
case "$1" in
|
|
60
|
+
--stage)
|
|
61
|
+
stage="${2:-}"
|
|
62
|
+
shift 2
|
|
63
|
+
;;
|
|
64
|
+
--stage=*)
|
|
65
|
+
stage="${1#--stage=}"
|
|
66
|
+
shift
|
|
67
|
+
;;
|
|
68
|
+
--iteration)
|
|
69
|
+
iteration="${2:-}"
|
|
70
|
+
shift 2
|
|
71
|
+
;;
|
|
72
|
+
--iteration=*)
|
|
73
|
+
iteration="${1#--iteration=}"
|
|
74
|
+
shift
|
|
75
|
+
;;
|
|
76
|
+
--git-sha)
|
|
77
|
+
git_sha="${2:-}"
|
|
78
|
+
shift 2
|
|
79
|
+
;;
|
|
80
|
+
--git-sha=*)
|
|
81
|
+
git_sha="${1#--git-sha=}"
|
|
82
|
+
shift
|
|
83
|
+
;;
|
|
84
|
+
--files-modified)
|
|
85
|
+
files_modified="${2:-}"
|
|
86
|
+
shift 2
|
|
87
|
+
;;
|
|
88
|
+
--files-modified=*)
|
|
89
|
+
files_modified="${1#--files-modified=}"
|
|
90
|
+
shift
|
|
91
|
+
;;
|
|
92
|
+
--tests-passing)
|
|
93
|
+
tests_passing="true"
|
|
94
|
+
shift
|
|
95
|
+
;;
|
|
96
|
+
--loop-state)
|
|
97
|
+
loop_state="${2:-}"
|
|
98
|
+
shift 2
|
|
99
|
+
;;
|
|
100
|
+
--loop-state=*)
|
|
101
|
+
loop_state="${1#--loop-state=}"
|
|
102
|
+
shift
|
|
103
|
+
;;
|
|
104
|
+
--help|-h)
|
|
105
|
+
show_help
|
|
106
|
+
return 0
|
|
107
|
+
;;
|
|
108
|
+
*)
|
|
109
|
+
error "Unknown option: $1"
|
|
110
|
+
return 1
|
|
111
|
+
;;
|
|
112
|
+
esac
|
|
113
|
+
done
|
|
114
|
+
|
|
115
|
+
if [[ -z "$stage" ]]; then
|
|
116
|
+
error "Missing required --stage"
|
|
117
|
+
echo ""
|
|
118
|
+
show_help
|
|
119
|
+
return 1
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Default git sha from HEAD if not provided
|
|
123
|
+
if [[ -z "$git_sha" ]]; then
|
|
124
|
+
git_sha="$(git rev-parse HEAD 2>/dev/null || echo "unknown")"
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
ensure_checkpoint_dir
|
|
128
|
+
|
|
129
|
+
# Build files_modified JSON array from comma-separated string
|
|
130
|
+
local files_json="[]"
|
|
131
|
+
if [[ -n "$files_modified" ]]; then
|
|
132
|
+
files_json="$(echo "$files_modified" | tr ',' '\n' | jq -R . | jq -s .)"
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# Build checkpoint JSON with jq for proper escaping
|
|
136
|
+
local tmp_file
|
|
137
|
+
tmp_file="$(mktemp)"
|
|
138
|
+
|
|
139
|
+
jq -n \
|
|
140
|
+
--arg stage "$stage" \
|
|
141
|
+
--arg iteration "${iteration:-0}" \
|
|
142
|
+
--argjson files_modified "$files_json" \
|
|
143
|
+
--arg tests_passing "$tests_passing" \
|
|
144
|
+
--arg git_sha "$git_sha" \
|
|
145
|
+
--arg loop_state "${loop_state:-}" \
|
|
146
|
+
--arg created_at "$(now_iso)" \
|
|
147
|
+
'{
|
|
148
|
+
stage: $stage,
|
|
149
|
+
iteration: ($iteration | tonumber),
|
|
150
|
+
files_modified: $files_modified,
|
|
151
|
+
tests_passing: ($tests_passing == "true"),
|
|
152
|
+
git_sha: $git_sha,
|
|
153
|
+
loop_state: $loop_state,
|
|
154
|
+
created_at: $created_at
|
|
155
|
+
}' > "$tmp_file"
|
|
156
|
+
|
|
157
|
+
# Atomic write
|
|
158
|
+
local target
|
|
159
|
+
target="$(checkpoint_file "$stage")"
|
|
160
|
+
mv "$tmp_file" "$target"
|
|
161
|
+
|
|
162
|
+
success "Checkpoint saved for stage ${BOLD}${stage}${RESET} (iteration ${iteration:-0})"
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# ─── Restore ─────────────────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
cmd_restore() {
|
|
168
|
+
local stage=""
|
|
169
|
+
|
|
170
|
+
while [[ $# -gt 0 ]]; do
|
|
171
|
+
case "$1" in
|
|
172
|
+
--stage)
|
|
173
|
+
stage="${2:-}"
|
|
174
|
+
shift 2
|
|
175
|
+
;;
|
|
176
|
+
--stage=*)
|
|
177
|
+
stage="${1#--stage=}"
|
|
178
|
+
shift
|
|
179
|
+
;;
|
|
180
|
+
--help|-h)
|
|
181
|
+
show_help
|
|
182
|
+
return 0
|
|
183
|
+
;;
|
|
184
|
+
*)
|
|
185
|
+
error "Unknown option: $1"
|
|
186
|
+
return 1
|
|
187
|
+
;;
|
|
188
|
+
esac
|
|
189
|
+
done
|
|
190
|
+
|
|
191
|
+
if [[ -z "$stage" ]]; then
|
|
192
|
+
error "Missing required --stage"
|
|
193
|
+
return 1
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
local target
|
|
197
|
+
target="$(checkpoint_file "$stage")"
|
|
198
|
+
|
|
199
|
+
if [[ ! -f "$target" ]]; then
|
|
200
|
+
return 1
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
if ! jq empty "$target" 2>/dev/null; then
|
|
204
|
+
warn "Corrupt checkpoint for stage: $(basename "$target")"
|
|
205
|
+
return 1
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
cat "$target"
|
|
209
|
+
return 0
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# ─── List ────────────────────────────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
cmd_list() {
|
|
215
|
+
echo ""
|
|
216
|
+
echo -e "${CYAN}${BOLD} Checkpoints${RESET}"
|
|
217
|
+
echo -e "${DIM} ══════════════════════════════════════════${RESET}"
|
|
218
|
+
echo ""
|
|
219
|
+
|
|
220
|
+
if [[ ! -d "$CHECKPOINT_DIR" ]]; then
|
|
221
|
+
echo -e " ${DIM}No checkpoints found.${RESET}"
|
|
222
|
+
echo ""
|
|
223
|
+
return 0
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
local count=0
|
|
227
|
+
local file
|
|
228
|
+
for file in "${CHECKPOINT_DIR}"/*-checkpoint.json; do
|
|
229
|
+
# Handle no matches (glob returns literal pattern)
|
|
230
|
+
[[ -f "$file" ]] || continue
|
|
231
|
+
count=$((count + 1))
|
|
232
|
+
|
|
233
|
+
local stage iteration git_sha tests_passing loop_state created_at
|
|
234
|
+
stage="$(jq -r '.stage' "$file")"
|
|
235
|
+
iteration="$(jq -r '.iteration' "$file")"
|
|
236
|
+
git_sha="$(jq -r '.git_sha' "$file")"
|
|
237
|
+
tests_passing="$(jq -r '.tests_passing' "$file")"
|
|
238
|
+
loop_state="$(jq -r '.loop_state' "$file")"
|
|
239
|
+
created_at="$(jq -r '.created_at' "$file")"
|
|
240
|
+
|
|
241
|
+
# Format tests indicator
|
|
242
|
+
local tests_icon
|
|
243
|
+
if [[ "$tests_passing" == "true" ]]; then
|
|
244
|
+
tests_icon="${GREEN}✓${RESET}"
|
|
245
|
+
else
|
|
246
|
+
tests_icon="${RED}✗${RESET}"
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
# Format loop state
|
|
250
|
+
local state_display=""
|
|
251
|
+
if [[ -n "$loop_state" && "$loop_state" != "null" ]]; then
|
|
252
|
+
state_display=" ${DIM}state:${RESET}${loop_state}"
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
echo -e " ${CYAN}●${RESET} ${BOLD}${stage}${RESET} iter:${iteration} tests:${tests_icon} ${DIM}sha:${git_sha:0:7}${RESET}${state_display}"
|
|
256
|
+
echo -e " ${DIM}${created_at}${RESET}"
|
|
257
|
+
done
|
|
258
|
+
|
|
259
|
+
if [[ "$count" -eq 0 ]]; then
|
|
260
|
+
echo -e " ${DIM}No checkpoints found.${RESET}"
|
|
261
|
+
else
|
|
262
|
+
echo ""
|
|
263
|
+
echo -e " ${DIM}${count} checkpoint(s)${RESET}"
|
|
264
|
+
fi
|
|
265
|
+
echo ""
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# ─── Clear ───────────────────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
cmd_clear() {
|
|
271
|
+
local stage=""
|
|
272
|
+
local clear_all=false
|
|
273
|
+
|
|
274
|
+
while [[ $# -gt 0 ]]; do
|
|
275
|
+
case "$1" in
|
|
276
|
+
--stage)
|
|
277
|
+
stage="${2:-}"
|
|
278
|
+
shift 2
|
|
279
|
+
;;
|
|
280
|
+
--stage=*)
|
|
281
|
+
stage="${1#--stage=}"
|
|
282
|
+
shift
|
|
283
|
+
;;
|
|
284
|
+
--all)
|
|
285
|
+
clear_all=true
|
|
286
|
+
shift
|
|
287
|
+
;;
|
|
288
|
+
--help|-h)
|
|
289
|
+
show_help
|
|
290
|
+
return 0
|
|
291
|
+
;;
|
|
292
|
+
*)
|
|
293
|
+
error "Unknown option: $1"
|
|
294
|
+
return 1
|
|
295
|
+
;;
|
|
296
|
+
esac
|
|
297
|
+
done
|
|
298
|
+
|
|
299
|
+
if [[ "$clear_all" == "true" ]]; then
|
|
300
|
+
if [[ -d "$CHECKPOINT_DIR" ]]; then
|
|
301
|
+
local count=0
|
|
302
|
+
local file
|
|
303
|
+
for file in "${CHECKPOINT_DIR}"/*-checkpoint.json; do
|
|
304
|
+
[[ -f "$file" ]] || continue
|
|
305
|
+
rm -f "$file"
|
|
306
|
+
count=$((count + 1))
|
|
307
|
+
done
|
|
308
|
+
success "Cleared ${count} checkpoint(s)"
|
|
309
|
+
else
|
|
310
|
+
info "No checkpoints to clear"
|
|
311
|
+
fi
|
|
312
|
+
return 0
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
if [[ -z "$stage" ]]; then
|
|
316
|
+
error "Missing --stage or --all"
|
|
317
|
+
return 1
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
local target
|
|
321
|
+
target="$(checkpoint_file "$stage")"
|
|
322
|
+
|
|
323
|
+
if [[ -f "$target" ]]; then
|
|
324
|
+
rm -f "$target"
|
|
325
|
+
success "Cleared checkpoint for stage ${BOLD}${stage}${RESET}"
|
|
326
|
+
else
|
|
327
|
+
warn "No checkpoint found for stage ${BOLD}${stage}${RESET}"
|
|
328
|
+
fi
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
# ─── Expire ──────────────────────────────────────────────────────────────────
|
|
332
|
+
|
|
333
|
+
cmd_expire() {
|
|
334
|
+
local max_hours=24
|
|
335
|
+
|
|
336
|
+
while [[ $# -gt 0 ]]; do
|
|
337
|
+
case "$1" in
|
|
338
|
+
--hours)
|
|
339
|
+
max_hours="${2:-24}"
|
|
340
|
+
shift 2
|
|
341
|
+
;;
|
|
342
|
+
--hours=*)
|
|
343
|
+
max_hours="${1#--hours=}"
|
|
344
|
+
shift
|
|
345
|
+
;;
|
|
346
|
+
--help|-h)
|
|
347
|
+
show_help
|
|
348
|
+
return 0
|
|
349
|
+
;;
|
|
350
|
+
*)
|
|
351
|
+
error "Unknown option: $1"
|
|
352
|
+
return 1
|
|
353
|
+
;;
|
|
354
|
+
esac
|
|
355
|
+
done
|
|
356
|
+
|
|
357
|
+
if [[ ! -d "$CHECKPOINT_DIR" ]]; then
|
|
358
|
+
return 0
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
local max_secs=$((max_hours * 3600))
|
|
362
|
+
local now_e
|
|
363
|
+
now_e=$(date +%s)
|
|
364
|
+
local expired=0
|
|
365
|
+
|
|
366
|
+
local file
|
|
367
|
+
for file in "${CHECKPOINT_DIR}"/*-checkpoint.json; do
|
|
368
|
+
[[ -f "$file" ]] || continue
|
|
369
|
+
|
|
370
|
+
# Check created_at from checkpoint JSON
|
|
371
|
+
local created_at
|
|
372
|
+
created_at=$(jq -r '.created_at // empty' "$file" 2>/dev/null || true)
|
|
373
|
+
|
|
374
|
+
if [[ -n "$created_at" ]]; then
|
|
375
|
+
# Parse ISO date to epoch
|
|
376
|
+
local file_epoch
|
|
377
|
+
file_epoch=$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$created_at" +%s 2>/dev/null \
|
|
378
|
+
|| date -d "$created_at" +%s 2>/dev/null \
|
|
379
|
+
|| echo "0")
|
|
380
|
+
if [[ "$file_epoch" -gt 0 && $((now_e - file_epoch)) -gt $max_secs ]]; then
|
|
381
|
+
local stage_name
|
|
382
|
+
stage_name=$(jq -r '.stage // "unknown"' "$file" 2>/dev/null || echo "unknown")
|
|
383
|
+
rm -f "$file"
|
|
384
|
+
expired=$((expired + 1))
|
|
385
|
+
info "Expired: ${stage_name} checkpoint (${max_hours}h+ old)"
|
|
386
|
+
fi
|
|
387
|
+
else
|
|
388
|
+
# Fallback: check file mtime
|
|
389
|
+
local mtime
|
|
390
|
+
mtime=$(stat -f '%m' "$file" 2>/dev/null || stat -c '%Y' "$file" 2>/dev/null || echo "0")
|
|
391
|
+
if [[ "$mtime" -gt 0 && $((now_e - mtime)) -gt $max_secs ]]; then
|
|
392
|
+
rm -f "$file"
|
|
393
|
+
expired=$((expired + 1))
|
|
394
|
+
fi
|
|
395
|
+
fi
|
|
396
|
+
done
|
|
397
|
+
|
|
398
|
+
if [[ "$expired" -gt 0 ]]; then
|
|
399
|
+
success "Expired ${expired} checkpoint(s) older than ${max_hours}h"
|
|
400
|
+
fi
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
# ─── Help ────────────────────────────────────────────────────────────────────
|
|
404
|
+
|
|
405
|
+
show_help() {
|
|
406
|
+
echo -e "${CYAN}${BOLD}shipwright checkpoint${RESET} ${DIM}v${VERSION}${RESET} — Save and restore agent state mid-stage"
|
|
407
|
+
echo ""
|
|
408
|
+
echo -e "${BOLD}USAGE${RESET}"
|
|
409
|
+
echo -e " ${CYAN}shipwright checkpoint${RESET} <command> [options]"
|
|
410
|
+
echo ""
|
|
411
|
+
echo -e "${BOLD}COMMANDS${RESET}"
|
|
412
|
+
echo -e " ${CYAN}save${RESET} Save a checkpoint for a stage"
|
|
413
|
+
echo -e " ${CYAN}restore${RESET} Restore a checkpoint (prints JSON to stdout)"
|
|
414
|
+
echo -e " ${CYAN}list${RESET} Show all available checkpoints"
|
|
415
|
+
echo -e " ${CYAN}clear${RESET} Remove checkpoint(s)"
|
|
416
|
+
echo -e " ${CYAN}expire${RESET} Remove checkpoints older than N hours"
|
|
417
|
+
echo ""
|
|
418
|
+
echo -e "${BOLD}SAVE OPTIONS${RESET}"
|
|
419
|
+
echo -e " ${CYAN}--stage${RESET} <name> Stage name (required)"
|
|
420
|
+
echo -e " ${CYAN}--iteration${RESET} <n> Current iteration number"
|
|
421
|
+
echo -e " ${CYAN}--git-sha${RESET} <sha> Git commit SHA (default: HEAD)"
|
|
422
|
+
echo -e " ${CYAN}--files-modified${RESET} \"f1,f2\" Comma-separated list of modified files"
|
|
423
|
+
echo -e " ${CYAN}--tests-passing${RESET} Mark tests as passing"
|
|
424
|
+
echo -e " ${CYAN}--loop-state${RESET} <state> Loop state (running, paused, etc.)"
|
|
425
|
+
echo ""
|
|
426
|
+
echo -e "${BOLD}RESTORE OPTIONS${RESET}"
|
|
427
|
+
echo -e " ${CYAN}--stage${RESET} <name> Stage to restore (required)"
|
|
428
|
+
echo ""
|
|
429
|
+
echo -e "${BOLD}CLEAR OPTIONS${RESET}"
|
|
430
|
+
echo -e " ${CYAN}--stage${RESET} <name> Stage to clear"
|
|
431
|
+
echo -e " ${CYAN}--all${RESET} Clear all checkpoints"
|
|
432
|
+
echo ""
|
|
433
|
+
echo -e "${BOLD}EXPIRE OPTIONS${RESET}"
|
|
434
|
+
echo -e " ${CYAN}--hours${RESET} <n> Max age in hours (default: 24)"
|
|
435
|
+
echo ""
|
|
436
|
+
echo -e "${BOLD}EXAMPLES${RESET}"
|
|
437
|
+
echo -e " ${DIM}shipwright checkpoint save --stage build --iteration 5${RESET}"
|
|
438
|
+
echo -e " ${DIM}shipwright checkpoint save --stage build --iteration 3 --tests-passing --files-modified \"src/auth.ts,src/middleware.ts\"${RESET}"
|
|
439
|
+
echo -e " ${DIM}shipwright checkpoint restore --stage build${RESET}"
|
|
440
|
+
echo -e " ${DIM}shipwright checkpoint list${RESET}"
|
|
441
|
+
echo -e " ${DIM}shipwright checkpoint clear --stage build${RESET}"
|
|
442
|
+
echo -e " ${DIM}shipwright checkpoint clear --all${RESET}"
|
|
443
|
+
echo -e " ${DIM}shipwright checkpoint expire --hours 48${RESET}"
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
# ─── Command Router ─────────────────────────────────────────────────────────
|
|
447
|
+
|
|
448
|
+
main() {
|
|
449
|
+
local cmd="${1:-help}"
|
|
450
|
+
shift 2>/dev/null || true
|
|
451
|
+
|
|
452
|
+
case "$cmd" in
|
|
453
|
+
save) cmd_save "$@" ;;
|
|
454
|
+
restore) cmd_restore "$@" ;;
|
|
455
|
+
list) cmd_list ;;
|
|
456
|
+
clear) cmd_clear "$@" ;;
|
|
457
|
+
expire) cmd_expire "$@" ;;
|
|
458
|
+
help|--help|-h) show_help ;;
|
|
459
|
+
*)
|
|
460
|
+
error "Unknown command: ${cmd}"
|
|
461
|
+
echo ""
|
|
462
|
+
show_help
|
|
463
|
+
exit 1
|
|
464
|
+
;;
|
|
465
|
+
esac
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
main "$@"
|