context-save — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited context-save (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
eval "$("${VIBESTACK_HOME:-$HOME/.vibestack}/bin/vibe-slug" 2>/dev/null)" 2>/dev/null || SLUG="unknown"
_LEARN_FILE="${VIBESTACK_HOME:-$HOME/.vibestack}/projects/${SLUG:-unknown}/learnings.jsonl"
if [ -f "$_LEARN_FILE" ]; then
_LEARN_COUNT=$(wc -l < "$_LEARN_FILE" 2>/dev/null | tr -d ' ')
echo "LEARNINGS: $_LEARN_COUNT entries loaded"
if [ "$_LEARN_COUNT" -gt 5 ] 2>/dev/null; then
"${VIBESTACK_HOME:-$HOME/.vibestack}/bin/vibe-learnings-search" --limit 5 2>/dev/null || true
fi
else
echo "LEARNINGS: none yet"
fi{{include lib/snippets/session-host.md}}
{{include lib/snippets/decision-brief.md}}
{{include lib/snippets/working-protocols.md}}
{{include lib/snippets/state-protocols.md}}
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.
Parse the user's input to determine the mode:
/context-save or /context-save <title> → Save/context-save list → ListIf 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."
eval "$("${VIBESTACK_HOME:-$HOME/.vibestack}/bin/vibe-slug" 2>/dev/null)" && mkdir -p "${VIBESTACK_HOME:-$HOME/.vibestack}/projects/$SLUG"Collect the current working state:
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/nullUsing the gathered state plus your conversation history, produce a summary covering:
how this session knows it (see the provenance table below)
open questions, things that were tried and didn't work)
Remaining Work provenance. A resumed session cannot tell a command you ran from one you guessed unless the item says so. Every item starts with the status Open. — never with a completion token such as [done] or [executed] — and ends with the marker from the first row that applies:
| # | The item is... | Marker | Use when this session... |
|---|---|---|---|
| 1 | a writing step (migration, sync, insert, import, deploy, a dialog that writes), with or without a concrete path | (target state checked) | inspected the target's schema or state (for example its unique key) |
(code read) | read the writer but not the target | ||
(path assumed) | inferred the write without reading either | ||
| 2 | otherwise, names a concrete path: a runnable command, CLI flag or switch, config key or value, or file or directory path | (path run) | executed it and observed the outcome; state the outcome in the item, before the marker (exit 0, failed: <reason>) |
(path read) | read the code or file but did not execute it | ||
(path assumed) | inferred it without running or reading it, however confident | ||
| 3 | anything else | none | (Open. alone) |
Writing steps get their own markers because whether a write is safe to repeat depends on the target's state, not on the code that performs it. Examples: Open. Run the suite with CONFIG=staging, exit 0. (path run) and Open. Switch B reads config key Y. (path assumed).
If the user provided a title, use it. Otherwise, infer a concise title (3-6 words) from the work being done.
Try to determine how long this session has been active:
if [ -n "$_TEL_START" ]; then
START_EPOCH="$_TEL_START"
elif [ -n "$PPID" ]; then
_LSTART=$(ps -o lstart= -p "$PPID" 2>/dev/null)
# BSD date (macOS) parses with -jf, GNU date (Linux) with -d. Never parse an
# empty start: GNU `date -d ""` means today at midnight.
[ -n "$_LSTART" ] && START_EPOCH=$(date -jf "%c" "$_LSTART" "+%s" 2>/dev/null || date -d "$_LSTART" "+%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"
fiIf the duration cannot be determined, omit the session_duration_s field from the saved 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.
eval "$("${VIBESTACK_HOME:-$HOME/.vibestack}/bin/vibe-slug" 2>/dev/null)" && mkdir -p "${VIBESTACK_HOME:-$HOME/.vibestack}/projects/$SLUG"
CHECKPOINT_DIR="${VIBESTACK_HOME:-$HOME/.vibestack}/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:
---
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. Each item starts with `Open.` and ends with its provenance marker from Step 2, or no marker under row 3.}
### 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.
Then stamp the project identity into the frontmatter. Do not write these two fields yourself: the bin writes remote: (the origin with any credentials removed, or none) and project_root: (the repository root), which /context-restore uses to refuse another project's checkpoint. Substitute the exact FILE= path printed in Step 4:
"${VIBESTACK_HOME:-$HOME/.vibestack}/bin/vibe-slug" --stamp-checkpoint "{FILE}" \
&& grep -E '^(remote|project_root):' "{FILE}"If the stamp fails, say so in the confirmation below — the save still stands, but restore will treat it as an unstamped checkpoint.
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.eval "$("${VIBESTACK_HOME:-$HOME/.vibestack}/bin/vibe-slug" 2>/dev/null)" && mkdir -p "${VIBESTACK_HOME:-$HOME/.vibestack}/projects/$SLUG"
CHECKPOINT_DIR="${VIBESTACK_HOME:-$HOME/.vibestack}/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"
fiDefault 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."
/context-restore.
vibe-slug --stamp-checkpoint, runfrom inside the project — never hand-write remote: or project_root:.
save creates a new file.
the file. Only use AskUserQuestion if the title genuinely cannot be inferred.
/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.
a git-state snapshot to ~/.vibestack/projects/<slug>/compact-snapshots/ before every compaction. It holds git state and the transcript path only, never decisions, so /context-save stays the way to record decisions. Other runtimes have no compaction hook in this pack.
Run this step before finishing. A saved context file is read once, by whoever restores it; a learning outlives it and surfaces in every later session's preamble. If the session genuinely surfaced none, say "no durable learnings this session" rather than inventing one.
{{include lib/snippets/capture-learnings.md}}
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.