feat: continuous checkpoint mode with non-destructive WIP squash

Adds opt-in auto-commit during long sessions so work survives Claude
Code crashes, Conductor workspace handoffs, and context switches.
Local-only by default — pushing requires explicit opt-in.

Codex review caught multiple landmines that would have shipped:
1. checkpoint_push=true default would push WIP commits to shared
   branches, trigger CI/deploys, expose secrets. Now default false.
2. Plan's original /ship squash (git reset --soft to merge base) was
   destructive — uncommitted ALL branch commits, not just WIP, and
   caused non-fast-forward pushes. Redesigned: rebase --autosquash
   scoped to WIP commits only, with explicit fallback for WIP-only
   branches and STOP-and-ask for conflicts.
3. gstack-config get returned empty for missing keys with exit 0,
   ignoring the annotated defaults in the header comments. Fixed:
   get now falls back to a lookup_default() table that is the
   canonical source for defaults.
4. Telemetry default mismatched: header said 'anonymous' but runtime
   treated empty as 'off'. Aligned: default is 'off' everywhere.
5. /checkpoint resume only read markdown checkpoint files, not the
   WIP commit [gstack-context] bodies the plan referenced. Wired up
   parsing of [gstack-context] blocks from WIP commits as a second
   recovery trail alongside the markdown checkpoints.

Changes:
- bin/gstack-config: add checkpoint_mode (default explicit) and
  checkpoint_push (default false) to CONFIG_HEADER. Add lookup_default()
  as canonical default source. get() falls back to defaults when key
  absent. list now shows value + source (set/default). New 'defaults'
  subcommand to inspect the table.
- scripts/resolvers/preamble.ts: preamble bash reads _CHECKPOINT_MODE
  and _CHECKPOINT_PUSH, prints CHECKPOINT_MODE: and CHECKPOINT_PUSH: so
  the mode is visible. New generateContinuousCheckpoint() section in
  T2+ tier describes WIP commit format with [gstack-context] body and
  the rules (never git add -A, never commit broken tests, push only
  if opted in). Example deliberately shows a clean-state context so
  it doesn't contradict the rules.
- ship/SKILL.md.tmpl: new Step 5.75 WIP Commit Squash. Detects WIP
  count, exports [gstack-context] blocks before squash (as backup),
  uses rebase --autosquash for mixed branches and soft-reset only when
  VERIFIED WIP-only. Explicit anti-footgun rules against blind soft-
  reset. Aborts with BLOCKED status on conflict instead of destroying
  non-WIP commits.
- checkpoint/SKILL.md.tmpl: new Step 1.5 to parse [gstack-context]
  blocks from WIP commits via git log --grep="^WIP:". Merges with
  markdown checkpoint for fuller session recovery.
- Golden ship fixtures regenerated (ship is T4, preamble change shows up).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-04-17 06:05:25 +08:00
co-authored by Claude Opus 4.7
parent 6c8cf6774f
commit fe34349966
39 changed files with 2065 additions and 8 deletions
+35
View File
@@ -189,6 +189,41 @@ in their frontmatter, so all files in the directory are candidates). This enable
Conductor workspace handoff — a checkpoint saved on one branch can be resumed from
another.
### Step 1.5: Check for WIP commit context (continuous checkpoint mode)
If `CHECKPOINT_MODE` was `"continuous"` during prior work, the branch may have
`WIP:` commits with structured `[gstack-context]` blocks in their bodies. These
are a second recovery trail alongside the markdown checkpoint files.
```bash
_BRANCH=$(git branch --show-current 2>/dev/null)
# Detect if this branch has any WIP commits against the nearest remote ancestor
_BASE=$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD origin/master 2>/dev/null)
if [ -n "$_BASE" ]; then
WIP_COMMITS=$(git log "$_BASE"..HEAD --grep="^WIP:" --format="%H" 2>/dev/null | head -20)
if [ -n "$WIP_COMMITS" ]; then
echo "WIP_COMMITS_FOUND"
# Extract [gstack-context] blocks from each WIP commit body
for SHA in $WIP_COMMITS; do
echo "--- commit $SHA ---"
git log -1 "$SHA" --format="%s%n%n%b" 2>/dev/null | \
awk '/\[gstack-context\]/,/\[\/gstack-context\]/ { print }'
done
else
echo "NO_WIP_COMMITS"
fi
fi
```
If `WIP_COMMITS_FOUND`: Read the extracted `[gstack-context]` blocks. Each block
represents a logical unit of prior work with Decisions/Remaining/Tried/Skill.
Merge these with the markdown checkpoint file to reconstruct session state. The
git history shows the chronological arc; the markdown checkpoint shows the
intentional save points. Both matter.
**Important:** Do NOT delete WIP commits during resume. They remain the recovery
trail until /ship squashes them into clean commits during PR creation.
### Step 2: Load checkpoint
If the user specified a checkpoint (by number, title fragment, or date), find the