--- 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 ` → **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.