merge: origin/main v1.1.1.0 into garrytan/fix-checkpoints

Main shipped the /ship VERSION/package.json drift-detection fix as
v1.1.1.0 — exact collision with our branch's existing version. Bumped
ours to 1.1.2.0.

Resolved conflicts:
- VERSION: 1.1.1.0 → 1.1.2.0
- package.json: 1.1.1.0 → 1.1.2.0
- CHANGELOG.md: moved our /checkpoint → /context-save entry up one
  header level to [1.1.2.0] and kept main's /ship drift-fix entry
  at [1.1.1.0]. Sequence now: 1.1.2.0 → 1.1.1.0 → 1.1.0.0 → 1.0.0.0.
- Migration renamed v1.1.1.0.sh → v1.1.2.0.sh (version string inside
  and test path reference both updated).

Also bumped the /context-save + /context-restore CHANGELOG entry to
credit the adversarial-review hardening wave (HOME guard, realpath
fallback, title sanitize, collision-safe filenames, context-restore
head cap, autoplan test regex tightening) as contributor-facing notes
— previous entry didn't reflect the security work that landed after
the initial ship.

No overlap between main's /ship Step 12 logic and this branch's work.
SKILL.md files regenerated via bun run gen:skill-docs --host all.
Golden fixtures updated.

bun test: 0 failures across 80+ targeted tests and the full suite.
Migration ownership guard: 7/7 pass (~85ms).
This commit is contained in:
Garry Tan
2026-04-19 00:02:07 +08:00
12 changed files with 749 additions and 53 deletions
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Migration: v1.1.1.0 — Remove stale /checkpoint skill installs
# Migration: v1.1.2.0 — Remove stale /checkpoint skill installs
#
# Claude Code ships /checkpoint as a native alias for /rewind, which was
# shadowing the gstack checkpoint skill. The skill has been split into
@@ -25,7 +25,7 @@ set -euo pipefail
# CI runners) survives and produces dangerous absolute paths like
# "/.claude/skills/...". Abort cleanly.
if [ -z "${HOME:-}" ]; then
echo " [v1.1.1.0] HOME is unset or empty — skipping migration." >&2
echo " [v1.1.2.0] HOME is unset or empty — skipping migration." >&2
exit 0
fi
@@ -75,10 +75,10 @@ if [ -L "$OLD_TOPLEVEL" ]; then
target_real=$(resolve_real "$OLD_TOPLEVEL")
if [ -n "$GSTACK_ROOT_REAL" ] && path_inside "$target_real" "$GSTACK_ROOT_REAL"; then
rm -- "$OLD_TOPLEVEL"
echo " [v1.1.1.0] Removed stale /checkpoint symlink (was shadowing Claude Code's /rewind alias)."
echo " [v1.1.2.0] Removed stale /checkpoint symlink (was shadowing Claude Code's /rewind alias)."
removed_any=1
else
echo " [v1.1.1.0] Leaving $OLD_TOPLEVEL alone — symlink target is outside gstack (or unresolvable)."
echo " [v1.1.2.0] Leaving $OLD_TOPLEVEL alone — symlink target is outside gstack (or unresolvable)."
fi
elif [ -d "$OLD_TOPLEVEL" ]; then
# Regular directory. Only remove if it contains exactly one file named
@@ -92,13 +92,13 @@ elif [ -d "$OLD_TOPLEVEL" ]; then
# Strip macOS sidecars first (not user content), then remove the dir.
find "$OLD_TOPLEVEL" -maxdepth 1 \( -name '.DS_Store' -o -name '._*' \) -type f -delete 2>/dev/null || true
rm -r -- "$OLD_TOPLEVEL"
echo " [v1.1.1.0] Removed stale /checkpoint install directory (gstack prefix-mode)."
echo " [v1.1.2.0] Removed stale /checkpoint install directory (gstack prefix-mode)."
removed_any=1
else
echo " [v1.1.1.0] Leaving $OLD_TOPLEVEL alone — SKILL.md symlink target is outside gstack."
echo " [v1.1.2.0] Leaving $OLD_TOPLEVEL alone — SKILL.md symlink target is outside gstack."
fi
else
echo " [v1.1.1.0] Leaving $OLD_TOPLEVEL alone — not a gstack-owned install (has custom content)."
echo " [v1.1.2.0] Leaving $OLD_TOPLEVEL alone — not a gstack-owned install (has custom content)."
fi
fi
# Missing → no-op (idempotency).
@@ -111,10 +111,10 @@ if [ -L "$OLD_NAMESPACED" ]; then
target_real=$(resolve_real "$OLD_NAMESPACED")
if [ -n "$GSTACK_ROOT_REAL" ] && path_inside "$target_real" "$GSTACK_ROOT_REAL"; then
rm -- "$OLD_NAMESPACED"
echo " [v1.1.1.0] Removed stale ~/.claude/skills/gstack/checkpoint symlink."
echo " [v1.1.2.0] Removed stale ~/.claude/skills/gstack/checkpoint symlink."
removed_any=1
else
echo " [v1.1.1.0] Leaving $OLD_NAMESPACED alone — symlink target is outside gstack."
echo " [v1.1.2.0] Leaving $OLD_NAMESPACED alone — symlink target is outside gstack."
fi
elif [ -d "$OLD_NAMESPACED" ]; then
# Regular directory. This is the gstack-prefix install location. Check that
@@ -123,15 +123,15 @@ elif [ -d "$OLD_NAMESPACED" ]; then
target_real=$(resolve_real "$OLD_NAMESPACED")
if [ -n "$GSTACK_ROOT_REAL" ] && path_inside "$target_real" "$GSTACK_ROOT_REAL"; then
rm -rf -- "$OLD_NAMESPACED"
echo " [v1.1.1.0] Removed stale ~/.claude/skills/gstack/checkpoint/ (replaced by context-save + context-restore)."
echo " [v1.1.2.0] Removed stale ~/.claude/skills/gstack/checkpoint/ (replaced by context-save + context-restore)."
removed_any=1
else
echo " [v1.1.1.0] Leaving $OLD_NAMESPACED alone — resolves outside gstack."
echo " [v1.1.2.0] Leaving $OLD_NAMESPACED alone — resolves outside gstack."
fi
fi
if [ "$removed_any" = "1" ]; then
echo " [v1.1.1.0] /checkpoint is now Claude Code's native /rewind alias. Use /context-save to save state and /context-restore to resume."
echo " [v1.1.2.0] /checkpoint is now Claude Code's native /rewind alias. Use /context-save to save state and /context-restore to resume."
fi
exit 0