mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 08:23:59 +01:00
* fix(plan-eng-review): register the structure question id so never-ask can fire The complexity gate's Original/Smaller arrangement question had no registered question_id, so each run invented a per-plan slug (one install logged ten different ids for it). --check matches ids exactly, so a stored never-ask could never fire and the question was asked every time. - Register plan-eng-review-structure as a two-way routing question. - The template names that exact id and says AUTO_DECIDE picks the recommended arrangement; the preamble's check-first rule does the rest. Wording is byte-neutral against the parity budget and the per-host pins. - --check falls back to $GSTACK_HOME/global-question-preferences.json, the file the question-preference hook already reads, when the project file has no entry for the id, so one setting covers every repo and both layers agree. A project entry still wins, the one-way and split-chain guards run first, a malformed or non-object global file is ignored with a NOTE, and --write stays per-project. - Tests for the fallback and the template contract; two wording pins updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commitfb0334eb5c) * fix(context): pin English provenance markers and ask when branch cannot discriminate Addresses three gaps from #3217: 1. context-save: markers must stay the five English literals; translated tokens break restore sorting. Decisions Made claims about file contents need evidence or an assumed Remaining Work item. 2. context-restore: unknown/translated parenthesised markers sort under Verify first as (path assumed). 3. When CURRENT_BRANCH cannot discriminate and recent candidates have different titles, ask instead of silently loading newest. Pins the new contract in the provenance prompt-byte test. Fixes #3217 (cherry picked from commit2a02647fd4) * docs: add a neutral how-to for bringing up a first deploy (#3150) /setup-deploy stops at "Nothing is deployed yet" and writes nothing, because it has no production URL or deploy trigger to record. The docs said nothing about the step in between. The maintainers chose to keep that stop vendor-neutral and asked for a neutral docs page instead (#3150, option 2). docs/howto-bring-up-a-first-deploy.md walks through the first deploy: where the app runs and how deploys start, the services it needs, env and secrets, DNS and TLS, checking that it works, and running /setup-deploy again. It names no hosting provider or deploy tool. The README Docs table and the /setup-deploy section of docs/skills.md link to it. The stop's own text is unchanged. Fixes #3150 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit5c5d8ec96f) * docs(changelog): contributor fixes from #3249, #3244, #3264 under [Unreleased] Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: Trevor Bezdek <bezdekt@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Chao Chen <209239467+surlymochan@users.noreply.github.com> Co-authored-by: y$un_ <forrest.sun527@gmail.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
300 lines
15 KiB
Cheetah
300 lines
15 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 (preferring the current branch, falling back across branches) 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. (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: prefer the most recent checkpoint saved on the CURRENT branch; if
|
|
this branch has none, fall back to the most recent across ALL branches.** The
|
|
fallback is for Conductor workspace handoff — a context saved on one branch can
|
|
be resumed from another. The current-branch preference exists because every
|
|
worktree of a repo shares one checkpoints directory (same origin-derived slug),
|
|
so without it `/context-restore` in one worktree could silently load a sibling
|
|
worktree's newer checkpoint.
|
|
|
|
**Do NOT hard-filter the candidate set to the current branch** — other-branch
|
|
checkpoints stay in the set as a fallback. They are just ordered *after* the
|
|
current branch's own, so a current-branch save is never shadowed by a newer
|
|
sibling-worktree save. (`/context-save list` is the flow that hard-scopes to one
|
|
branch.)
|
|
|
|
---
|
|
|
|
## Detect command
|
|
|
|
Parse the user's input:
|
|
|
|
- `/context-restore` → load the most recent saved context (current branch first, then 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}}
|
|
GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
|
|
CHECKPOINT_DIR="$GSTACK_STATE_ROOT/projects/$SLUG/checkpoints"
|
|
# Project identity: canonical remote (root only when there is no remote).
|
|
PROJECT_REMOTE=$(~/.claude/skills/gstack/bin/gstack-slug --get PROJECT_REMOTE 2>/dev/null) || true
|
|
PROJECT_ROOT=$(~/.claude/skills/gstack/bin/gstack-slug --get PROJECT_ROOT 2>/dev/null) || true
|
|
LEGACY_SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get LEGACY_SLUG 2>/dev/null) || true
|
|
echo "PROJECT_IDENTITY: ${PROJECT_REMOTE:-${PROJECT_ROOT:-unknown}}"
|
|
echo "PROJECT_ROOT: ${PROJECT_ROOT:-unknown}"
|
|
if [ -n "${LEGACY_SLUG:-}" ] && [ -d "$GSTACK_STATE_ROOT/projects/$LEGACY_SLUG/checkpoints" ]; then
|
|
LEGACY_N=$(find "$GSTACK_STATE_ROOT/projects/$LEGACY_SLUG/checkpoints" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
if [ "$LEGACY_N" -gt 0 ]; then
|
|
echo "LEGACY_BUCKET: projects/$LEGACY_SLUG holds $LEGACY_N earlier checkpoint(s) from the bucket this project shared with other nested-group repos; review and copy with: ~/.claude/skills/gstack/bin/gstack-slug --adopt-legacy"
|
|
fi
|
|
fi
|
|
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.
|
|
# Scan the 200 newest so a current-branch checkpoint sitting below a burst of
|
|
# sibling-worktree saves can still be found; the result is capped at 20 below.
|
|
ALL=$(find "$CHECKPOINT_DIR" -maxdepth 1 -name "*.md" -type f 2>/dev/null | sort -r | head -200)
|
|
if [ -z "$ALL" ]; then
|
|
echo "NO_CHECKPOINTS"
|
|
else
|
|
# Order current-branch checkpoints first, other branches after. A git branch
|
|
# is checked out in at most one worktree, and all worktrees of a repo share
|
|
# one checkpoints dir (same origin-derived slug), so without this preference
|
|
# `/context-restore` in worktree A could load worktree B's newer checkpoint.
|
|
# Cross-branch resume (Conductor handoff) is preserved as the fallback: when
|
|
# the current branch has no checkpoint, the full newest-first set is used.
|
|
# CURRENT_BRANCH may be pre-set (tests); otherwise resolve it from git.
|
|
: "${CURRENT_BRANCH:=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)}"
|
|
SAME=""; OTHER=""
|
|
while IFS= read -r f; do
|
|
[ -n "$f" ] || continue
|
|
b=$(grep -m1 '^branch:' "$f" 2>/dev/null | sed 's/^branch:[[:space:]]*//')
|
|
if [ -n "$CURRENT_BRANCH" ] && [ "$b" = "$CURRENT_BRANCH" ]; then
|
|
SAME="${SAME}${f}
|
|
"
|
|
else
|
|
OTHER="${OTHER}${f}
|
|
"
|
|
fi
|
|
done <<EOF
|
|
$ALL
|
|
EOF
|
|
# Identity check: only checkpoints stamped with THIS project's
|
|
# identity are candidates for "latest". Unstamped (older) checkpoints are
|
|
# trusted unless the directory demonstrably holds another project's files.
|
|
CLASSIFIED=$(printf '%s%s' "$SAME" "$OTHER" | grep -v '^[[:space:]]*$' \
|
|
| ~/.claude/skills/gstack/bin/gstack-slug --classify-checkpoints 2>/dev/null)
|
|
if [ -z "$CLASSIFIED" ]; then
|
|
echo "IDENTITY_CHECK_FAILED"
|
|
printf '%s%s' "$SAME" "$OTHER" | grep -v '^[[:space:]]*$' | head -20 | sed 's/^/UNVERIFIED /'
|
|
else
|
|
FOREIGN_N=$(printf '%s\n' "$CLASSIFIED" | grep -c '^foreign' || true)
|
|
VERIFIED=$(printf '%s\n' "$CLASSIFIED" | awk -F '\t' -v f="$FOREIGN_N" \
|
|
'$(1) == "match" || $(1) == "match-root" || ($(1) == "unstamped" && f == 0) { print $(2) }')
|
|
# Cap at 20: a user with 10k saved files shouldn't blow the context window.
|
|
FILES=$(printf '%s\n' "$VERIFIED" | head -20)
|
|
if [ -n "$FILES" ]; then echo "$FILES"; else echo "NO_VERIFIED_CHECKPOINTS"; fi
|
|
# A newer checkpoint of another branch that names the same task.
|
|
printf '%s\n' "$VERIFIED" | ~/.claude/skills/gstack/bin/gstack-slug --newer-task "$CURRENT_BRANCH" 2>/dev/null || true
|
|
printf '%s\n' "$CLASSIFIED" | awk -F '\t' -v f="$FOREIGN_N" \
|
|
'$(1) == "match-root" { print "ROOT_DIFFERS " $(2) }
|
|
$(1) == "foreign" { print "FOREIGN " $(2) }
|
|
$(1) == "unstamped" && f > 0 { print "UNVERIFIED " $(2) }' | head -40
|
|
if [ "$FOREIGN_N" -gt 0 ]; then
|
|
echo "SHARED_BUCKET: $FOREIGN_N checkpoint(s) here were saved by another project"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
# Checkpoints saved in a repository below this directory (BELOW_CWD, POINTER).
|
|
~/.claude/skills/gstack/bin/gstack-slug --checkpoints-below 2>/dev/null || true
|
|
```
|
|
|
|
**Candidates include every `.md` file in the directory**, but they are ordered
|
|
**current-branch-first** (the branch is read from each file's `branch:`
|
|
frontmatter). Other-branch files stay in the set as a fallback, which preserves
|
|
Conductor workspace handoff when the current branch has no checkpoint of its own.
|
|
|
|
**Project identity.** Plain path lines are this project's checkpoints (verified by
|
|
the `remote:`/`project_root:` stamp, or unstamped in a directory no other project
|
|
has written to). Prefixed lines are never candidates for "latest":
|
|
- `FOREIGN <path>`: saved by a different project that shares this directory.
|
|
- `UNVERIFIED <path>`: saved before identity stamps, in a directory another
|
|
project also uses, so it may belong to either.
|
|
- `ROOT_DIFFERS <path>`: same repository, different checkout (Conductor workspace
|
|
handoff or a moved clone). This is information, not a mismatch; the file is
|
|
also listed as a plain path line.
|
|
- `NEWER_TASK <path>`: another branch of this repository saved a newer checkpoint
|
|
for the same task (its worktree is below this main tree, or the title or ticket
|
|
matches). The file is also a plain path line, after the current branch's own.
|
|
- `BELOW_CWD <path>` / `POINTER <path>`: saved by a session in a repository below
|
|
this directory (`POINTER`: a /context-save started here recorded it). These are
|
|
candidates, not mismatches.
|
|
|
|
### Step 2: Load the right file
|
|
|
|
- If the user specified a title fragment or number: find the matching file among
|
|
the candidates, including `FOREIGN` and `UNVERIFIED` lines (a user may restore
|
|
another project's checkpoint deliberately).
|
|
- Otherwise: load the **first plain path line returned by Step 1 above**, which
|
|
is the newest `YYYYMMDD-HHMMSS` checkpoint of this project for the current
|
|
branch, or, if the current branch has none, the newest across all branches.
|
|
- **No useful branch discriminator.** When `CURRENT_BRANCH` is empty, `HEAD`, or
|
|
otherwise cannot discriminate (cwd is not a git repo, or branch is unknown), do
|
|
not silently load newest if the recent candidates (e.g. last 24 hours, or the
|
|
three newest) carry **different titles**. Show those titles with timestamps via
|
|
AskUserQuestion, mark newest as the recommended option, and wait. Only auto-load
|
|
newest when every recent candidate shares one title (same topic).
|
|
- **Do not silently take an older checkpoint.** If Step 1 printed `NEWER_TASK`,
|
|
or a `BELOW_CWD`/`POINTER` file is newer than the first plain path line (or
|
|
there is none), show both (title, branch, saved time, and `project_root` for a
|
|
file below this directory), propose the newer one as the continuation, and ask
|
|
via AskUserQuestion which to load. A `BELOW_CWD`/`POINTER` file is not a
|
|
PROJECT MISMATCH: say "Saved in `{project_root}`, below this directory."
|
|
- If Step 1 printed `NO_VERIFIED_CHECKPOINTS` or `IDENTITY_CHECK_FAILED`, do not
|
|
present any checkpoint as the latest one. Say "No checkpoint in this directory
|
|
is verified as this project's", list the `FOREIGN`/`UNVERIFIED` titles with the
|
|
`remote:`/`project_root:` they name (or "unknown"), and ask which one, if any, to
|
|
open.
|
|
|
|
**Report identity problems before any summary.** If the chosen file is `FOREIGN`
|
|
or `UNVERIFIED`, start with:
|
|
"PROJECT MISMATCH: this checkpoint was saved by `{remote or project_root from its
|
|
frontmatter, or 'an unknown project'}`, not this project (`{PROJECT_IDENTITY}`)."
|
|
If Step 1 printed `SHARED_BUCKET`, say "This checkpoints directory also holds N
|
|
checkpoint(s) from another project; only this project's are listed." If it printed
|
|
`LEGACY_BUCKET`, relay that line. If the chosen file is `ROOT_DIFFERS`, add
|
|
"Saved from another checkout of this repository at `{project_root}`." as info.
|
|
|
|
**Sort Remaining Work by provenance.** Keep every item's original text and saved
|
|
order, and drop nothing. Put an item under **Verify first** when it:
|
|
- ends in `(path assumed)` or `(code read)`;
|
|
- ends in `(path run)` but its text reports a failure;
|
|
- ends in a parenthesised trailing marker that is **not** one of the five English
|
|
literals `(path run)`, `(path read)`, `(path assumed)`, `(target state checked)`,
|
|
`(code read)` — treat a translated or unknown marker as `(path assumed)` (→ Verify
|
|
first), never as unmarked Next steps;
|
|
- has no marker and is a writing step (migration, sync, insert, import, a dialog that
|
|
writes) or names a concrete path (a runnable command, CLI flag or switch, config key
|
|
or value, or file or directory path).
|
|
|
|
Every other item goes under **Next steps**: `(path run)` with a successful outcome,
|
|
`(path read)`, `(target state checked)`, and unmarked items that neither write nor
|
|
name a concrete path. Verifying means read-only inspection: read the file or the
|
|
target, or run a command that changes nothing. Checkpoints saved before provenance
|
|
markers existed have none, so their concrete-path items land under Verify first. When
|
|
the file has no provenance markers at all, print this line above the groups:
|
|
`This checkpoint predates provenance markers; items naming commands, paths or writes are listed under Verify first.`
|
|
|
|
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
|
|
{legacy banner line, if it applies}
|
|
|
|
Next steps
|
|
{Next steps items, in saved order, original text}
|
|
|
|
Verify first (inspect read-only before executing anything)
|
|
{Verify first items, in saved order, original text}
|
|
|
|
### 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, take the first Remaining Work item in saved order. If it is under Next steps,
|
|
suggest starting there. If it is under Verify first, suggest verifying it (read-only)
|
|
before doing it or any later item, so a later runnable step never jumps ahead of an
|
|
unverified earlier one.
|
|
|
|
---
|
|
|
|
## If no saved contexts exist
|
|
|
|
If Step 1 printed `NO_CHECKPOINTS` and no `BELOW_CWD` or `POINTER` line, tell the user:
|
|
|
|
"No saved contexts yet. Run `/context-save` first to save your current working
|
|
state, then `/context-restore` will find it."
|
|
|
|
If it also printed `LEGACY_BUCKET`, relay that line: earlier checkpoints are kept
|
|
in the old bucket until the user copies them with `gstack-slug --adopt-legacy`.
|
|
|
|
---
|
|
|
|
## Important Rules
|
|
|
|
- **Never modify code.** This skill only reads saved files and presents them.
|
|
- **Prefer the current branch's own checkpoint, but keep all branches in the
|
|
fallback set.** Cross-branch resume (Conductor handoff) works when the
|
|
current branch has no checkpoint, and a sibling worktree's newer save never
|
|
shadows this branch's own.
|
|
- **Never present another project's checkpoint as "latest".** Identity is the
|
|
canonical remote (the root only when there is no remote); a mismatch is
|
|
reported before any summary.
|
|
- **"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.
|