mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-06 05:35:46 +02:00
merge: integrate origin/main (v1.1.3.0) — /checkpoint → /context-save + /context-restore rename
Main shipped v1.1.3.0 fixing Claude Code's native /checkpoint alias shadowing gstack's skill. The old /checkpoint directory is gone, replaced by context-save/ and context-restore/. Storage path (~/.gstack/projects/$SLUG/checkpoints/) is unchanged, so existing saved contexts still load. Conflicts: - VERSION / package.json: kept 1.2.0.0 (above main's 1.1.3.0) - CHANGELOG: preserved 1.2.0.0 at top, inserted 1.1.3.0 below - scripts/resolvers/preamble.ts: same pattern as prior merges — main's side edited the monolithic file inline; I kept the submodule composition root intact (main's inline changes don't apply to this shape) Ported my continuous-checkpoint and context-health submodule prose to reference the new skill names: - generate-continuous-checkpoint.ts: "/checkpoint resume" → "/context-restore" - generate-context-health.ts: "/checkpoint" → "/context-save" Also updated user-facing prose in: - CHANGELOG.md (1.2.0.0 entry): "/checkpoint resume" → "/context-restore (formerly /checkpoint resume pre-v1.1.3)" - README.md Continuous checkpoint section: same rename Storage paths in generate-context-recovery.ts (`$_PROJ/checkpoints/`) left untouched — per main's v1.1.3.0 notes, the storage directory name stays `checkpoints/` to preserve backward-compat with saved files. Touchfiles.ts auto-merged cleanly — main's context-save-writes-file and context-restore-loads-latest replaced my old checkpoint-save-resume entry. Regenerated SKILL.md files. Ship golden fixtures refreshed. 423 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,371 @@
|
||||
---
|
||||
name: context-save
|
||||
preamble-tier: 2
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Save working context. Captures git state, decisions made, and remaining work
|
||||
so any future session can pick up without losing a beat.
|
||||
Use when asked to "save progress", "save state", "context save", or
|
||||
"save my work". Pair with /context-restore to resume later.
|
||||
Formerly /checkpoint — renamed because Claude Code treats /checkpoint as a
|
||||
native rewind alias in current environments, which was shadowing this skill.
|
||||
(gstack)
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Write
|
||||
- Glob
|
||||
- Grep
|
||||
- AskUserQuestion
|
||||
triggers:
|
||||
- save progress
|
||||
- save state
|
||||
- save my work
|
||||
- context save
|
||||
---
|
||||
|
||||
{{PREAMBLE}}
|
||||
|
||||
# /context-save — Save Working Context
|
||||
|
||||
You are a **Staff Engineer who keeps meticulous session notes**. Your job is to
|
||||
capture the full working context — what's being done, what decisions were made,
|
||||
what's left — so that any future session (even on a different branch or workspace)
|
||||
can resume without losing a beat via `/context-restore`.
|
||||
|
||||
**HARD GATE:** Do NOT implement code changes. This skill captures state only.
|
||||
|
||||
---
|
||||
|
||||
## Detect command
|
||||
|
||||
Parse the user's input to determine the mode:
|
||||
|
||||
- `/context-save` or `/context-save <title>` → **Save**
|
||||
- `/context-save list` → **List**
|
||||
|
||||
If the user provides a title after the command (e.g., `/context-save auth refactor`),
|
||||
use it as the title. Otherwise, infer a title from the current work.
|
||||
|
||||
If the user types `/context-save resume` or `/context-save restore`, tell them:
|
||||
"Use `/context-restore` instead — save and restore are separate skills now."
|
||||
|
||||
---
|
||||
|
||||
## Save flow
|
||||
|
||||
### Step 1: Gather state
|
||||
|
||||
```bash
|
||||
{{SLUG_SETUP}}
|
||||
```
|
||||
|
||||
Collect the current working state:
|
||||
|
||||
```bash
|
||||
echo "=== BRANCH ==="
|
||||
git rev-parse --abbrev-ref HEAD 2>/dev/null
|
||||
echo "=== STATUS ==="
|
||||
git status --short 2>/dev/null
|
||||
echo "=== DIFF STAT ==="
|
||||
git diff --stat 2>/dev/null
|
||||
echo "=== STAGED DIFF STAT ==="
|
||||
git diff --cached --stat 2>/dev/null
|
||||
echo "=== RECENT LOG ==="
|
||||
git log --oneline -10 2>/dev/null
|
||||
```
|
||||
|
||||
### Step 2: Summarize context
|
||||
|
||||
Using the gathered state plus your conversation history, produce a summary covering:
|
||||
|
||||
1. **What's being worked on** — the high-level goal or feature
|
||||
2. **Decisions made** — architectural choices, trade-offs, approaches chosen and why
|
||||
3. **Remaining work** — concrete next steps, in priority order
|
||||
4. **Notes** — anything a future session needs to know (gotchas, blocked items,
|
||||
open questions, things that were tried and didn't work)
|
||||
|
||||
If the user provided a title, use it. Otherwise, infer a concise title (3-6 words)
|
||||
from the work being done.
|
||||
|
||||
### Step 3: Compute session duration
|
||||
|
||||
Try to determine how long this session has been active:
|
||||
|
||||
```bash
|
||||
if [ -n "$_TEL_START" ]; then
|
||||
START_EPOCH="$_TEL_START"
|
||||
elif [ -n "$PPID" ]; then
|
||||
START_EPOCH=$(ps -o lstart= -p $PPID 2>/dev/null | xargs -I{} date -jf "%c" "{}" "+%s" 2>/dev/null || echo "")
|
||||
fi
|
||||
if [ -n "$START_EPOCH" ]; then
|
||||
NOW=$(date +%s)
|
||||
DURATION=$((NOW - START_EPOCH))
|
||||
echo "SESSION_DURATION_S=$DURATION"
|
||||
else
|
||||
echo "SESSION_DURATION_S=unknown"
|
||||
fi
|
||||
```
|
||||
|
||||
If the duration cannot be determined, omit the `session_duration_s` field from the
|
||||
saved file.
|
||||
|
||||
### Step 4: Write saved-context file
|
||||
|
||||
Compute the path in bash (NOT in the LLM prompt) so user-supplied titles can't
|
||||
inject shell metacharacters into any subsequent command. The sanitizer is an
|
||||
allowlist: only `a-z 0-9 - .` survive.
|
||||
|
||||
```bash
|
||||
{{SLUG_SETUP}}
|
||||
CHECKPOINT_DIR="${GSTACK_HOME:-$HOME/.gstack}/projects/$SLUG/checkpoints"
|
||||
mkdir -p "$CHECKPOINT_DIR"
|
||||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
# Bash-side title sanitize. Pass the raw title as $1 when running this block.
|
||||
# Example: TITLE_RAW="wintermute progress" bash -c '...'
|
||||
RAW="${TITLE_RAW:-untitled}"
|
||||
# Lowercase, collapse whitespace to hyphens, strip to allowlist, cap length.
|
||||
TITLE_SLUG=$(printf '%s' "$RAW" | tr '[:upper:]' '[:lower:]' | tr -s ' \t' '-' | tr -cd 'a-z0-9.-' | cut -c1-60)
|
||||
TITLE_SLUG="${TITLE_SLUG:-untitled}"
|
||||
# Collision-safe filename: if ${TIMESTAMP}-${SLUG}.md already exists (same-second
|
||||
# double save with same title), append a short random suffix. Filenames are
|
||||
# append-only — never overwrite.
|
||||
FILE="${CHECKPOINT_DIR}/${TIMESTAMP}-${TITLE_SLUG}.md"
|
||||
if [ -e "$FILE" ]; then
|
||||
SUFFIX=$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom 2>/dev/null | head -c 4 || printf '%04x' "$$")
|
||||
FILE="${CHECKPOINT_DIR}/${TIMESTAMP}-${TITLE_SLUG}-${SUFFIX}.md"
|
||||
fi
|
||||
echo "CHECKPOINT_DIR=$CHECKPOINT_DIR"
|
||||
echo "TIMESTAMP=$TIMESTAMP"
|
||||
echo "FILE=$FILE"
|
||||
```
|
||||
|
||||
The on-disk directory name is `checkpoints/` (not `contexts/`) — this is a legacy
|
||||
path kept so existing saved files remain loadable. Users never see it.
|
||||
|
||||
Write the file to the `$FILE` path printed above (use the exact string — do not
|
||||
reconstruct it in the LLM layer).
|
||||
|
||||
The file format:
|
||||
|
||||
```markdown
|
||||
---
|
||||
status: in-progress
|
||||
branch: {current branch name}
|
||||
timestamp: {ISO-8601 timestamp, e.g. 2026-04-18T14:30:00-07:00}
|
||||
session_duration_s: {computed duration, omit if unknown}
|
||||
files_modified:
|
||||
- path/to/file1
|
||||
- path/to/file2
|
||||
---
|
||||
|
||||
## Working on: {title}
|
||||
|
||||
### Summary
|
||||
|
||||
{1-3 sentences describing the high-level goal and current progress}
|
||||
|
||||
### Decisions Made
|
||||
|
||||
{Bulleted list of architectural choices, trade-offs, and reasoning}
|
||||
|
||||
### Remaining Work
|
||||
|
||||
{Numbered list of concrete next steps, in priority order}
|
||||
|
||||
### Notes
|
||||
|
||||
{Gotchas, blocked items, open questions, things tried that didn't work}
|
||||
```
|
||||
|
||||
The `files_modified` list comes from `git status --short` (both staged and unstaged
|
||||
modified files). Use relative paths from the repo root.
|
||||
|
||||
After writing, confirm to the user:
|
||||
|
||||
```
|
||||
CONTEXT SAVED
|
||||
════════════════════════════════════════
|
||||
Title: {title}
|
||||
Branch: {branch}
|
||||
File: {path to saved file}
|
||||
Modified: {N} files
|
||||
Duration: {duration or "unknown"}
|
||||
════════════════════════════════════════
|
||||
|
||||
Restore later with /context-restore.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<<<<<<< HEAD:checkpoint/SKILL.md.tmpl
|
||||
## Resume flow
|
||||
|
||||
### Step 1: Find checkpoints
|
||||
|
||||
```bash
|
||||
{{SLUG_SETUP}}
|
||||
CHECKPOINT_DIR="$HOME/.gstack/projects/$SLUG/checkpoints"
|
||||
if [ -d "$CHECKPOINT_DIR" ]; then
|
||||
find "$CHECKPOINT_DIR" -maxdepth 1 -name "*.md" -type f 2>/dev/null | xargs ls -1t 2>/dev/null | head -20
|
||||
else
|
||||
echo "NO_CHECKPOINTS"
|
||||
fi
|
||||
```
|
||||
|
||||
List checkpoints from **all branches** (checkpoint files contain the branch name
|
||||
in their frontmatter, so all files in the directory are candidates). This enables
|
||||
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
|
||||
matching file. Otherwise, load the **most recent** checkpoint.
|
||||
|
||||
Read the checkpoint file and present a summary:
|
||||
|
||||
```
|
||||
RESUMING CHECKPOINT
|
||||
════════════════════════════════════════
|
||||
Title: {title}
|
||||
Branch: {branch from checkpoint}
|
||||
Saved: {timestamp, human-readable}
|
||||
Duration: Last session was {formatted duration} (if available)
|
||||
Status: {status}
|
||||
════════════════════════════════════════
|
||||
|
||||
### Summary
|
||||
{summary from checkpoint}
|
||||
|
||||
### Remaining Work
|
||||
{remaining work items from checkpoint}
|
||||
|
||||
### Notes
|
||||
{notes from checkpoint}
|
||||
```
|
||||
|
||||
If the current branch differs from the checkpoint's branch, note this:
|
||||
"This checkpoint was saved on branch `{branch}`. You are currently on
|
||||
`{current branch}`. You may want to switch branches before continuing."
|
||||
|
||||
### Step 3: Offer next steps
|
||||
|
||||
After presenting the checkpoint, ask via AskUserQuestion:
|
||||
|
||||
- A) Continue working on the remaining items
|
||||
- B) Show the full checkpoint file
|
||||
- C) Just needed the context, thanks
|
||||
|
||||
If A, summarize the first remaining work item and suggest starting there.
|
||||
|
||||
---
|
||||
|
||||
=======
|
||||
>>>>>>> origin/main:context-save/SKILL.md.tmpl
|
||||
## List flow
|
||||
|
||||
### Step 1: Gather saved contexts
|
||||
|
||||
```bash
|
||||
{{SLUG_SETUP}}
|
||||
CHECKPOINT_DIR="${GSTACK_HOME:-$HOME/.gstack}/projects/$SLUG/checkpoints"
|
||||
if [ -d "$CHECKPOINT_DIR" ]; then
|
||||
echo "CHECKPOINT_DIR=$CHECKPOINT_DIR"
|
||||
# Use find + sort instead of ls -1t: filename YYYYMMDD-HHMMSS prefix is the
|
||||
# canonical order (stable across copies/rsync; mtime is not), and empty-result
|
||||
# behavior is clean (no files → no output, no "lists cwd" fallback).
|
||||
find "$CHECKPOINT_DIR" -maxdepth 1 -name "*.md" -type f 2>/dev/null | sort -r
|
||||
else
|
||||
echo "NO_CHECKPOINTS"
|
||||
fi
|
||||
```
|
||||
|
||||
### Step 2: Display table
|
||||
|
||||
**Default behavior:** Show saved contexts for the **current branch** only.
|
||||
|
||||
If the user passes `--all` (e.g., `/context-save list --all`), show contexts
|
||||
from **all branches**.
|
||||
|
||||
Read the frontmatter of each file to extract `status`, `branch`, and
|
||||
`timestamp`. Parse the title from the filename (the part after the timestamp).
|
||||
|
||||
Present as a table:
|
||||
|
||||
```
|
||||
SAVED CONTEXTS ({branch} branch)
|
||||
════════════════════════════════════════
|
||||
# Date Title Status
|
||||
─ ────────── ─────────────────────── ───────────
|
||||
1 2026-04-18 auth-refactor in-progress
|
||||
2 2026-04-17 api-pagination completed
|
||||
3 2026-04-15 db-migration-setup in-progress
|
||||
════════════════════════════════════════
|
||||
```
|
||||
|
||||
If `--all` is used, add a Branch column:
|
||||
|
||||
```
|
||||
SAVED CONTEXTS (all branches)
|
||||
════════════════════════════════════════
|
||||
# Date Title Branch Status
|
||||
─ ────────── ─────────────────────── ────────────────── ───────────
|
||||
1 2026-04-18 auth-refactor feat/auth in-progress
|
||||
2 2026-04-17 api-pagination main completed
|
||||
3 2026-04-15 db-migration-setup feat/db-migration in-progress
|
||||
════════════════════════════════════════
|
||||
```
|
||||
|
||||
If there are no saved contexts, tell the user: "No saved contexts yet. Run
|
||||
`/context-save` to save your current working state."
|
||||
|
||||
---
|
||||
|
||||
## Important Rules
|
||||
|
||||
- **Never modify code.** This skill only reads state and writes the context file.
|
||||
- **Always include the branch name** in frontmatter — critical for cross-branch
|
||||
`/context-restore`.
|
||||
- **Saved files are append-only.** Never overwrite or delete existing files. Each
|
||||
save creates a new file.
|
||||
- **Infer, don't interrogate.** Use git state and conversation context to fill in
|
||||
the file. Only use AskUserQuestion if the title genuinely cannot be inferred.
|
||||
- **This is a gstack skill, not a Claude Code built-in.** When the user types
|
||||
`/context-save`, invoke this skill via the Skill tool. The old `/checkpoint`
|
||||
name collided with Claude Code's native `/rewind` alias — the rename fixed that.
|
||||
Reference in New Issue
Block a user