Files
gstack/context-restore/SKILL.md.tmpl
Garry Tan 3df8ea8695 security: harden migration + context-save after adversarial review
Adversarial review (Claude + Codex, both high confidence) identified 6
critical production-harm findings in the /ship pre-landing pass.
All folded in.

Migration v1.0.1.0.sh hardening:
- Add explicit `[ -z "${HOME:-}" ]` guard. HOME="" survives set -u and
  expands paths to /.claude/skills/... which could hit absolute paths
  under root/containers/sudo-without-H.
- Add python3 fallback inside resolve_real() (was missing; broken
  symlinks silently defeated ownership check).
- Ownership-guard Shape 2 (~/.claude/skills/gstack/checkpoint/). Was
  unconditional rm -rf. Now: if symlink, check target resolves inside
  gstack; if regular dir, check realpath resolves inside gstack. A
  user's hand-edited customization or a symlink pointing outside gstack
  is preserved with a notice.
- Use `rm --` and `rm -r --` consistently to resist hostile basenames.
- Use `find -type f -not -name .DS_Store -not -name ._*` instead of
  `ls -A | grep`. macOS sidecars no longer mask a legit prefix-mode
  install. Strip sidecars explicitly before removing the dir.

context-save/SKILL.md.tmpl:
- Sanitize title in bash, not LLM prose. Allowlist [a-z0-9.-], cap 60
  chars, default to "untitled". Closes a prompt-injection surface where
  `/context-save $(rm -rf ~)` could propagate into subsequent commands.
- Collision-safe filename. If ${TIMESTAMP}-${SLUG}.md already exists
  (same-second double-save with same title), append a 4-char random
  suffix. The skill contract says "saved files are append-only" — this
  enforces it. Silent overwrite was a data-loss bug.

context-restore/SKILL.md.tmpl:
- Cap `find ... | sort -r` at 20 entries via `| head -20`. A user with
  10k+ saved files no longer blows the context window just to pick one.
  /context-save list still handles the full-history listing path.

test/skill-e2e-autoplan-dual-voice.test.ts:
- Filter transcript to tool_use / tool_result / assistant entries
  before matching, so prompt-text mentions of "plan-ceo-review" don't
  force the reachedPhase1 assertion to pass. Phase-1 assertion now
  requires completion markers ("Phase 1 complete", "Phase 2 started"),
  not mere name occurrence.
- claudeVoiceFired now requires JSON evidence of an Agent tool_use
  (name:"Agent" or subagent_type field), not the literal string
  "Agent(" which could appear anywhere.
- codexVoiceFired now requires a Bash tool_use with a `codex exec/review`
  command string, not prompt-text mentions.

All SKILL.md files regenerated. Golden fixtures updated. bun test: 0
failures across 80+ targeted tests and the full suite.

Review source: /ship Step 11 adversarial pass (claude subagent + codex
exec). Same findings independently surfaced by both reviewers — this is
cross-model high confidence.
2026-04-18 23:28:12 +08:00

154 lines
5.1 KiB
Cheetah

---
name: context-restore
preamble-tier: 2
version: 1.0.0
description: |
Restore working context saved earlier by /context-save. Loads the most recent
saved state (across all branches by default) so you can pick up where you
left off — even across Conductor workspace handoffs.
Use when asked to "resume", "restore context", "where was I", or
"pick up where I left off". Pair with /context-save.
Formerly /checkpoint resume — renamed because Claude Code treats /checkpoint
as a native rewind alias in current environments. (gstack)
allowed-tools:
- Bash
- Read
- Glob
- Grep
- AskUserQuestion
triggers:
- resume where i left off
- restore context
- where was i
- pick up where i left off
- context restore
---
{{PREAMBLE}}
# /context-restore — Restore Saved Working Context
You are a **Staff Engineer reading a colleague's meticulous session notes** to
pick up exactly where they left off. Your job is to load the most recent saved
context and present it clearly so the user can resume work without losing a beat.
**HARD GATE:** Do NOT implement code changes. This skill only reads saved
context files and presents the summary.
**Default: load the most recent saved context across ALL branches.** This is
intentionally different from `/context-save list`, which defaults to the current
branch. `/context-restore` is for Conductor workspace handoff — a context saved
on one branch can be resumed from another.
**Do NOT filter the candidate set by current branch.** The `list` flow does
that; `/context-restore` does not.
---
## Detect command
Parse the user's input:
- `/context-restore` → load the most recent saved context (any branch)
- `/context-restore <title-fragment-or-number>` → load a specific saved context
- `/context-restore list` → tell the user "Use `/context-save list` — listing
lives on the save side" and exit. No mode detection here.
---
## Restore flow
### Step 1: Find saved contexts
```bash
{{SLUG_SETUP}}
CHECKPOINT_DIR="$HOME/.gstack/projects/$SLUG/checkpoints"
if [ ! -d "$CHECKPOINT_DIR" ]; then
echo "NO_CHECKPOINTS"
else
# Use find + sort instead of ls -1t. Two reasons:
# 1. Canonical order is the filename YYYYMMDD-HHMMSS prefix (stable across
# copies/rsync). Filesystem mtime drifts and is not authoritative.
# 2. On macOS, `find ... | xargs ls -1t` with zero results falls back to
# listing cwd. `sort -r` on empty input cleanly returns nothing.
# Cap at 20 most recent: a user with 10k saved files shouldn't blow the
# context window just listing them. /context-save list handles pagination.
FILES=$(find "$CHECKPOINT_DIR" -maxdepth 1 -name "*.md" -type f 2>/dev/null | sort -r | head -20)
if [ -z "$FILES" ]; then
echo "NO_CHECKPOINTS"
else
echo "$FILES"
fi
fi
```
**Candidates include every `.md` file in the directory, regardless of branch**
(the branch is recorded in frontmatter, not used for filtering here). This
enables Conductor workspace handoff.
### Step 2: Load the right file
- If the user specified a title fragment or number: find the matching file among
the candidates.
- Otherwise: load the **first file returned by the `sort -r` above** — that is
the newest `YYYYMMDD-HHMMSS` prefix, which is the canonical "most recent."
Read the chosen file and present a summary:
```
RESUMING CONTEXT
════════════════════════════════════════
Title: {title}
Branch: {branch from frontmatter}
Saved: {timestamp, human-readable}
Duration: Last session was {formatted duration} (if available)
Status: {status}
════════════════════════════════════════
### Summary
{summary from saved file}
### Remaining Work
{remaining work items}
### Notes
{notes}
```
If the current branch differs from the saved context's branch, note this:
"This context 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, ask via AskUserQuestion:
- A) Continue working on the remaining items
- B) Show the full saved file
- C) Just needed the context, thanks
If A, summarize the first remaining work item and suggest starting there.
---
## If no saved contexts exist
If Step 1 printed `NO_CHECKPOINTS`, tell the user:
"No saved contexts yet. Run `/context-save` first to save your current working
state, then `/context-restore` will find it."
---
## Important Rules
- **Never modify code.** This skill only reads saved files and presents them.
- **Always search across all branches by default.** Cross-branch resume is the
whole point. Only filter by branch if the user explicitly asks via a
title-fragment match that happens to be branch-specific.
- **"Most recent" means the filename `YYYYMMDD-HHMMSS` prefix**, not
`ls -1t` (filesystem mtime). Filenames are stable across file-system
operations; mtime is not.
- **This is a gstack skill, not a Claude Code built-in.** When the user types
`/context-restore`, invoke this skill via the Skill tool.