Main shipped /plan-ceo-review + /office-hours mode-posture fixes as
v1.1.2.0 — same version slot this branch used. Bumped ours to
v1.1.3.0.
Resolved conflicts:
- VERSION / package.json: 1.1.2.0 → 1.1.3.0
- CHANGELOG.md: our entry moved up to [1.1.3.0], main's mode-posture
entry kept at [1.1.2.0]. Sequence: 1.1.3.0 → 1.1.2.0 → 1.1.1.0 →
1.1.0.0 → 1.0.0.0
- Migration renamed v1.1.2.0.sh → v1.1.3.0.sh (version string inside
+ test path reference + hardening test describe block all updated)
Also expanded our CHANGELOG entry to reflect the testing work that
landed over the debugging loop: 8 live-fire E2E tests, 21 free-tier
hardening tests, collision sentinel, env: param on runSkillTest, and
GSTACK_HOME storage-path fix.
No code overlap with main's changes (main edited preamble writing-style
rules + plan-ceo-review and office-hours templates; ours edited
context-save / context-restore / migration / tests). SKILL.md files
regenerated via bun run gen:skill-docs --host all. Golden fixtures
updated.
bun test: 0 failures after renaming v1.1.2.0 → v1.1.3.0 in the hardening
test describe block and MIGRATION constant.
The skill templates hardcoded CHECKPOINT_DIR="\$HOME/.gstack/projects/\$SLUG/checkpoints"
which ignored any GSTACK_HOME override. Tests setting GSTACK_HOME
via env were writing to the test's expected path but the skill was
writing to the real user's ~/.gstack. The files existed — just not
where the assertion looked. 0/8 pass despite Skill tool routing
working correctly in the 3rd paid run.
Fix: \${GSTACK_HOME:-\$HOME/.gstack} in all three call sites
(context-save save flow, context-save list flow, context-restore
restore flow). Default behavior unchanged for real users (no
GSTACK_HOME set). Tests can now redirect storage to a tmp dir by
setting GSTACK_HOME via env: (added to runSkillTest in 5f316e0e).
Also follows the existing convention from the preamble, which already
uses \${GSTACK_HOME:-\$HOME/.gstack} for the learnings file lookup.
Inconsistency between preamble and skill body was the real bug —
two different storage-root resolutions in the same skill.
All SKILL.md files regenerated. Golden fixtures updated.
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.
Main shipped the v1 prompts rewrite (simpler writing style + real LOC
receipts + /plan-tune observational substrate). Resolved conflicts:
- VERSION / package.json: bumped 0.18.5.0 → 1.0.1.0 (main is 1.0.0.0,
this branch lands next).
- CHANGELOG: moved the /context-save + /context-restore entry to the
top as v1.0.1.0, above main's v1.0.0.0. Also removed the em-dash
variants in the new entry (ship voice rule).
- TODOS: kept both sections — Context skills (lane feature TODO) first,
main's PACING_UPDATES_V0 + Plan Tune v2 deferrals below.
- Migration: renamed gstack-upgrade/migrations/v0.18.5.0.sh →
v1.0.1.0.sh (matches new version). Test path updated.
preamble.ts auto-merged cleanly: main's question-tuning, explain_level,
and writing-style sections composed with my context-save/context-restore
routing rule.
All SKILL.md files regenerated via `bun run gen:skill-docs --host all`
per CLAUDE.md's "never resolve generated files by accepting either
side" rule. Golden fixtures (claude/codex/factory ship) also regenerated.
bun test: 0 failures.
scripts/resolvers/preamble.ts:238 is the source of truth for the routing
rules that gstack writes into users' CLAUDE.md on first skill run, AND
gets baked into every generated SKILL.md. A single 'invoke checkpoint'
line points at a skill that no longer exists.
Replace with two lines:
- Save progress, save state, save my work → invoke context-save
- Resume, where was I, pick up where I left off → invoke context-restore
Tier comment at :750 also updated.
All SKILL.md files regenerated via bun run gen:skill-docs.
Claude Code ships /checkpoint as a native alias for /rewind (Esc+Esc),
which was shadowing the gstack skill. Training-data bleed meant agents
saw /checkpoint and sometimes described it as a built-in instead of
invoking the Skill tool, so nothing got saved.
Fix: rename the skill and split save from restore so each skill has one
job. Restore now loads the most recent saved context across ALL branches
by default (the previous flow was ambiguous between mode="restore" and
mode="list" and agents applied list-flow filtering to restore).
New commands:
- /context-save → save current state
- /context-save list → list saved contexts (current branch default)
- /context-restore → load newest saved context across all branches
- /context-restore X → load specific saved context by title fragment
Storage directory unchanged at ~/.gstack/projects/$SLUG/checkpoints/ so
existing saved files remain loadable.
Canonical ordering is now the filename YYYYMMDD-HHMMSS prefix, not
filesystem mtime — filenames are stable across copies/rsync, mtime is
not.
Empty-set handling in both restore and list flows uses find+sort instead
of ls -1t, which on macOS falls back to listing cwd when the input is
empty.
Sources for the collision:
- https://code.claude.com/docs/en/checkpointing
- https://claudelog.com/mechanics/rewind/