Third review cycle on the ownership model, every item reproduced against a
fixture before the fix:
- Runtime assets (sections/, templates/, checklist.md, ...) were refreshed
with rm -rf regardless of who owned the directory, so an unclaimed or
weakly-owned directory lost the user's same-named real files. Real assets
are now replaced only in a directory gstack created or strongly owns
(marker, or SKILL.md symlink into gstack), plus the legacy Windows
real-copy shape; elsewhere they are kept and reported. Symlinks are never
content and are always refreshed.
- The prefix-flip cleanup deleted a customized banner-bearing SKILL.md that
the link pass would have backed up. Both cleanups now compare the file
against the source (raw, or with its name: line rewritten to the entry
name, which is how alias and prefixed copies legitimately differ) and
move a differing file to the backup root.
- A failed backup (unwritable root) returned success and the caller linked
over the file anyway. It now fails, and the entry is left untouched and
reported.
- A foreign DIRECTORY symlink whose target had no SKILL.md fell through to
the "unclaimed directory" rule and was replaced by a real directory. A
symlink that does not resolve into gstack is foreign, full stop.
- The alias installers stamped .gstack-owned into pre-existing directories;
they now follow the same created-or-already-marked rule.
- A directory counts as "only links" only when every link resolves into
gstack: a user's own symlink makes it mixed, so their link survives.
- The gstack-tree heuristic requires bin/gstack-relink, not just a VERSION
file, a setup script and a bin/ directory.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The first #2119 gate treated a byte-identical or banner-bearing real-file
SKILL.md as full ownership, so a prefix flip could rm -rf a user's directory
(their own qa skill started from a gstack SKILL.md, plus my-templates/) and
the link pass could replace their customized file with a symlink. Two
strengths now:
- STRONG: the .gstack-owned marker (we created the directory), or a
directory holding nothing but symlinks and the marker (deleting it loses
no data). Only strong proof removes a directory whole.
- WEAK: byte-identity with our source or the two-line gen-skill-docs banner
on a real file. Weak proof covers that SKILL.md and our runtime-asset
links only; a differing file is moved to
${GSTACK_HOME:-~/.gstack}/backups/skills/<ts>/<skill>/ before we link
over it, and setup/relink print one summary line naming what moved.
The marker is written on every platform now (path-independent proof for
Windows copies and for checkouts whose path carries no gstack segment), but
only for a directory gstack creates: a directory we merely link into
(unclaimed, or a legacy install) never becomes deletable whole. A directory
with no SKILL.md at all is unclaimed: the link pass may add our file, the
cleanup pass has nothing to remove.
Also from the review passes: the banner check reads 8192 bytes, not 40
lines (investigate, office-hours, plan-ceo-review and design-consultation
carry the banner past line 40 and were left "foreign" on pre-marker
Windows installs); a link into a checkout named without a gstack segment
(git worktree add ../gstack-<branch>) is ours when that tree carries
setup + VERSION + bin/; relink's fast path is gone so both files
canonicalize before judging; relink's root alias (_gstack-command) is
gated and stamped like every other entry; relink reports the bare entry
name with setup's wording and setup dedupes when forwarding
(_run_relink_quiet); the summary names the browser skills as examples.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The relink gate alone left three destructive sites open:
- link_claude_skill_dirs runs BEFORE relink on every ./setup and used
`ln -snf` (Linux replaces a user's real SKILL.md with a symlink into
gstack) or, on Windows, rm -rf + cp followed by a marker that made the
user's directory "ours" on the next flip. It and _install_alias_skill_md
now consult _claude_entry_is_ours first and skip loudly.
- cleanup_prefixed_claude_symlinks kept a bare name-match deletion and a
`*gstack*` substring match. Symlink arms use anchored `gstack/` segment
patterns; the Windows real-file arm proves provenance (marker,
byte-identity with our source, or the full two-line gen-skill-docs banner
within the first 40 lines, never a one-line substring another generator
could emit). cleanup_old_claude_symlinks uses the same banner rule.
- gstack-relink's fast path judged absolute targets before canonicalizing,
so `/x/gstack/../foreign/SKILL.md` counted as ours; dot-segment targets
now canonicalize first. Its banner rule matches setup's.
The `.gstack-owned` marker records the owning payload's realpath. Entries
skipped by setup or relink are listed in the final setup summary.
Chromium bootstrap refinements from the pre-landing review: an INT/TERM
trap kills the installer's process tree; the Windows npm chain no longer
masks an install failure; GSTACK_SKIP_PLAYWRIGHT=1 is reported as a choice
rather than a failure and sends no telemetry; the timeout knob is
normalized (0, 000, non-numeric, or more than nine digits fall back to the
600s default instead of killing on the first poll or never killing).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>