mirror of https://github.com/garrytan/gstack.git
282 lines
10 KiB
Cheetah
282 lines
10 KiB
Cheetah
---
|
||
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.
|
||
|
||
**EVGENIY LANGUAGE CONTRACT:** When the primary reader is Evgeniy, write the
|
||
entire human-facing checkpoint and confirmation in Russian: title, headings,
|
||
summary, decisions, risks, verification results, remaining work, notes, table
|
||
labels, and next steps. Keep English only for exact commands, paths, filenames,
|
||
code/API/schema fields, model/tool names, identifiers, immutable status literals,
|
||
and source quotes. Frontmatter keys remain machine-readable. Before confirming a
|
||
save, scan the checkpoint for leftover English prose and translate it.
|
||
|
||
---
|
||
|
||
## 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}}
|
||
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"
|
||
CHECKPOINT_DIR="$GSTACK_STATE_ROOT/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
|
||
---
|
||
|
||
## Текущая работа: {title}
|
||
|
||
### Кратко
|
||
|
||
{1-3 предложения с описанием общей цели и текущего прогресса}
|
||
|
||
### Принятые решения
|
||
|
||
{Маркированный список архитектурных решений, компромиссов и причин}
|
||
|
||
### Что осталось
|
||
|
||
{Нумерованный список конкретных следующих шагов в порядке приоритета}
|
||
|
||
### Примечания
|
||
|
||
{Подводные камни, блокеры, открытые вопросы и неудачные попытки}
|
||
```
|
||
|
||
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:
|
||
|
||
```
|
||
КОНТЕКСТ СОХРАНЁН
|
||
════════════════════════════════════════
|
||
Название: {title}
|
||
Ветка: {branch}
|
||
Файл: {path to saved file}
|
||
Изменено: {N} файлов
|
||
Длительность: {duration или "неизвестно"}
|
||
════════════════════════════════════════
|
||
|
||
Позже восстановите контекст командой /context-restore.
|
||
```
|
||
|
||
---
|
||
|
||
## List flow
|
||
|
||
### Step 1: Gather saved contexts
|
||
|
||
```bash
|
||
{{SLUG_SETUP}}
|
||
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"
|
||
CHECKPOINT_DIR="$GSTACK_STATE_ROOT/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:
|
||
|
||
```
|
||
СОХРАНЁННЫЕ КОНТЕКСТЫ (ветка {branch})
|
||
════════════════════════════════════════
|
||
# Дата Название Статус
|
||
─ ────────── ─────────────────────── ───────────
|
||
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:
|
||
|
||
```
|
||
СОХРАНЁННЫЕ КОНТЕКСТЫ (все ветки)
|
||
════════════════════════════════════════
|
||
# Дата Название Ветка Статус
|
||
─ ────────── ─────────────────────── ────────────────── ───────────
|
||
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 Evgeniy: "Сохранённых контекстов пока нет.
|
||
Запустите `/context-save`, чтобы сохранить текущее рабочее состояние."
|
||
|
||
---
|
||
|
||
## 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.
|