mirror of https://github.com/garrytan/gstack.git
fix(setup): relocate direct Claude install out of the skill-discovery root
A full repo checkout at ~/.claude/skills/gstack sits inside Claude Code's (and
Cursor's) recursive skill-discovery root, so every generated host-mirror and
source SKILL.md inside it gets indexed — hundreds of phantom skills. Claude Code
drops descriptions ("N skill descriptions dropped"); Cursor's cursor-agent CLI
hard-freezes (NGHTTP2_ENHANCE_YOUR_CALM).
Mirror migrate_direct_codex_install for Claude: move the checkout to
~/.gstack/repos/gstack and leave a runtime-only sidecar at ~/.claude/skills/gstack
(bin/, browse/dist, review assets, ETHOS.md, …) with ZERO nested SKILL.md, while
top-level skill aliases stay in ~/.claude/skills. gstack-relink already honors the
GSTACK_SKILLS_DIR/GSTACK_INSTALL_DIR overrides, and /gstack-upgrade already detects
~/.gstack/repos/gstack, so upgrades keep working with no extra changes.
- migrate_direct_claude_install: relocate global direct installs; idempotent
re-run from the relocated path; skips --local and worktree/vendored installs
- create_claude_runtime_root: curated sidecar with zero nested SKILL.md
- README: canonical path + install/team/troubleshooting commands
- test: sources both functions against an isolated fake HOME (fresh / re-run / --local)
Fixes #1202. Addresses #1694.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3bef43bc5a
commit
c26266a97e
23
README.md
23
README.md
|
|
@ -48,14 +48,21 @@ Fork it. Improve it. Make it yours. And if you want to hate on free open source
|
|||
|
||||
Open Claude Code and paste this. Claude does the rest.
|
||||
|
||||
> Install gstack: run **`git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup`** then add a "gstack" section to CLAUDE.md that says to use the /browse skill from gstack for all web browsing, never use mcp\_\_claude-in-chrome\_\_\* tools, and lists the available skills: /office-hours, /plan-ceo-review, /plan-eng-review, /plan-design-review, /design-consultation, /design-shotgun, /design-html, /review, /ship, /land-and-deploy, /canary, /benchmark, /browse, /connect-chrome, /qa, /qa-only, /design-review, /setup-browser-cookies, /setup-deploy, /setup-gbrain, /retro, /investigate, /document-release, /document-generate, /codex, /cso, /autoplan, /plan-devex-review, /devex-review, /careful, /freeze, /guard, /unfreeze, /gstack-upgrade, /learn. Then ask the user if they also want to add gstack to the current project so teammates get it.
|
||||
> Install gstack: run **`git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.gstack/repos/gstack && cd ~/.gstack/repos/gstack && ./setup`** then add a "gstack" section to CLAUDE.md that says to use the /browse skill from gstack for all web browsing, never use mcp\_\_claude-in-chrome\_\_\* tools, and lists the available skills: /office-hours, /plan-ceo-review, /plan-eng-review, /plan-design-review, /design-consultation, /design-shotgun, /design-html, /review, /ship, /land-and-deploy, /canary, /benchmark, /browse, /connect-chrome, /qa, /qa-only, /design-review, /setup-browser-cookies, /setup-deploy, /setup-gbrain, /retro, /investigate, /document-release, /document-generate, /codex, /cso, /autoplan, /plan-devex-review, /devex-review, /careful, /freeze, /guard, /unfreeze, /gstack-upgrade, /learn. Then ask the user if they also want to add gstack to the current project so teammates get it.
|
||||
|
||||
The checkout lives at `~/.gstack/repos/gstack` (outside Claude's skill-discovery
|
||||
root), and `~/.claude/skills/` holds the skill aliases plus a small runtime-only
|
||||
`gstack/` sidecar. Existing installs at `~/.claude/skills/gstack` are migrated
|
||||
automatically on the next `./setup` or `/gstack-upgrade`. This keeps recursive
|
||||
skill scanners (e.g. Cursor) from indexing the repo's generated host-mirror
|
||||
skills. See [#1202](https://github.com/garrytan/gstack/issues/1202).
|
||||
|
||||
### Step 2: Team mode — auto-update for shared repos (recommended)
|
||||
|
||||
From inside your repo, paste this. Switches you to team mode, bootstraps the repo so teammates get gstack automatically, and commits the change:
|
||||
|
||||
```bash
|
||||
(cd ~/.claude/skills/gstack && ./setup --team) && ~/.claude/skills/gstack/bin/gstack-team-init required && git add .claude/ CLAUDE.md && git commit -m "require gstack for AI-assisted work"
|
||||
(cd ~/.gstack/repos/gstack && ./setup --team) && ~/.gstack/repos/gstack/bin/gstack-team-init required && git add .claude/ CLAUDE.md && git commit -m "require gstack for AI-assisted work"
|
||||
```
|
||||
|
||||
No vendored files in your repo, no version drift, no manual upgrades. Every Claude Code session starts with a fast auto-update check (throttled to once/hour, network-failure-safe, completely silent).
|
||||
|
|
@ -67,7 +74,7 @@ Swap `required` for `optional` if you'd rather nudge teammates than block them.
|
|||
OpenClaw spawns Claude Code sessions via ACP, so every gstack skill just works
|
||||
when Claude Code has gstack installed. Paste this to your OpenClaw agent:
|
||||
|
||||
> Install gstack: run `git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup` to install gstack for Claude Code. Then add a "Coding Tasks" section to AGENTS.md that says: when spawning Claude Code sessions for coding work, tell the session to use gstack skills. Include these examples — security audit: "Load gstack. Run /cso", code review: "Load gstack. Run /review", QA test a URL: "Load gstack. Run /qa https://...", build a feature end-to-end: "Load gstack. Run /autoplan, implement the plan, then run /ship", plan before building: "Load gstack. Run /office-hours then /autoplan. Save the plan, don't implement."
|
||||
> Install gstack: run `git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.gstack/repos/gstack && cd ~/.gstack/repos/gstack && ./setup` to install gstack for Claude Code. Then add a "Coding Tasks" section to AGENTS.md that says: when spawning Claude Code sessions for coding work, tell the session to use gstack skills. Include these examples — security audit: "Load gstack. Run /cso", code review: "Load gstack. Run /review", QA test a URL: "Load gstack. Run /qa https://...", build a feature end-to-end: "Load gstack. Run /autoplan, implement the plan, then run /ship", plan before building: "Load gstack. Run /office-hours then /autoplan. Save the plan, don't implement."
|
||||
|
||||
**After setup, just talk to your OpenClaw agent naturally:**
|
||||
|
||||
|
|
@ -453,21 +460,21 @@ Data is stored in [Supabase](https://supabase.com) (open source Firebase alterna
|
|||
|
||||
## Troubleshooting
|
||||
|
||||
**Skill not showing up?** `cd ~/.claude/skills/gstack && ./setup`
|
||||
**Skill not showing up?** `cd ~/.gstack/repos/gstack && ./setup`
|
||||
|
||||
**`/browse` fails?** `cd ~/.claude/skills/gstack && bun install && bun run build`
|
||||
**`/browse` fails?** `cd ~/.gstack/repos/gstack && bun install && bun run build`
|
||||
|
||||
**Stale install?** Run `/gstack-upgrade` — or set `auto_upgrade: true` in `~/.gstack/config.yaml`
|
||||
|
||||
**Want shorter commands?** `cd ~/.claude/skills/gstack && ./setup --no-prefix` — switches from `/gstack-qa` to `/qa`. Your choice is remembered for future upgrades.
|
||||
**Want shorter commands?** `cd ~/.gstack/repos/gstack && ./setup --no-prefix` — switches from `/gstack-qa` to `/qa`. Your choice is remembered for future upgrades.
|
||||
|
||||
**Want namespaced commands?** `cd ~/.claude/skills/gstack && ./setup --prefix` — switches from `/qa` to `/gstack-qa`. Useful if you run other skill packs alongside gstack.
|
||||
**Want namespaced commands?** `cd ~/.gstack/repos/gstack && ./setup --prefix` — switches from `/qa` to `/gstack-qa`. Useful if you run other skill packs alongside gstack.
|
||||
|
||||
**Codex says "Skipped loading skill(s) due to invalid SKILL.md"?** Your Codex skill descriptions are stale. Fix: `cd ~/.codex/skills/gstack && git pull && ./setup --host codex` — or for repo-local installs: `cd "$(readlink -f .agents/skills/gstack)" && git pull && ./setup --host codex`
|
||||
|
||||
**Windows users:** gstack works on Windows 11 via Git Bash or WSL. Node.js is required in addition to Bun — Bun has a known bug with Playwright's pipe transport on Windows ([bun#4253](https://github.com/oven-sh/bun/issues/4253)). The browse server automatically falls back to Node.js. Make sure both `bun` and `node` are on your PATH.
|
||||
|
||||
On Windows without Developer Mode (MSYS2 / Git Bash), `setup` falls back to file copies instead of symlinks because `ln -snf` produces frozen copies that don't refresh on `git pull`. **Re-run `cd ~/.claude/skills/gstack && ./setup` after every `git pull`** so your skill files match the repo. `setup` prints a one-line note reminding you. Unix and WSL keep symlinks and don't need the re-run.
|
||||
On Windows without Developer Mode (MSYS2 / Git Bash), `setup` falls back to file copies instead of symlinks because `ln -snf` produces frozen copies that don't refresh on `git pull`. **Re-run `cd ~/.gstack/repos/gstack && ./setup` after every `git pull`** so your skill files match the repo. `setup` prints a one-line note reminding you. Unix and WSL keep symlinks and don't need the re-run.
|
||||
|
||||
**Claude says it can't see the skills?** Make sure your project's `CLAUDE.md` has a gstack section. Add this:
|
||||
|
||||
|
|
|
|||
101
setup
101
setup
|
|
@ -249,6 +249,59 @@ if [ "$INSTALL_CODEX" -eq 1 ]; then
|
|||
migrate_direct_codex_install "$SOURCE_GSTACK_DIR" "$CODEX_GSTACK"
|
||||
fi
|
||||
|
||||
# ─── Claude: relocate a direct ~/.claude/skills/gstack checkout ──────────────
|
||||
# A full repo checkout sitting at ~/.claude/skills/gstack is itself under
|
||||
# Claude Code's (and Cursor's) recursive skill-discovery root, so every
|
||||
# generated host-mirror SKILL.md (.cursor/, .hermes/, .agents/, …) and source
|
||||
# skill SKILL.md inside it gets indexed — hundreds of phantom skills. Claude
|
||||
# Code drops descriptions ("N skill descriptions dropped"); Cursor's cursor-agent
|
||||
# CLI hard-freezes. This mirrors migrate_direct_codex_install: move the checkout
|
||||
# out of the discovery root to ~/.gstack/repos/gstack and keep ~/.claude/skills
|
||||
# as the alias root (a runtime-only sidecar is created later in the Claude
|
||||
# install step). See issues #1202 and #1694.
|
||||
_CLAUDE_RELOCATED=0
|
||||
migrate_direct_claude_install() {
|
||||
local claude_gstack="$HOME/.claude/skills/gstack"
|
||||
local migrated_dir="$HOME/.gstack/repos/gstack"
|
||||
|
||||
# Only the standard *global* layout migrates. Leave --local, symlinked
|
||||
# worktree/vendored installs, and anything not living directly at
|
||||
# ~/.claude/skills/gstack untouched.
|
||||
[ "$LOCAL_INSTALL" -eq 1 ] && return 0
|
||||
|
||||
if [ "$SOURCE_GSTACK_DIR" = "$claude_gstack" ] && [ ! -L "$claude_gstack" ]; then
|
||||
mkdir -p "$(dirname "$migrated_dir")"
|
||||
if [ -e "$migrated_dir" ]; then
|
||||
echo "gstack setup failed: direct Claude install detected at $claude_gstack" >&2
|
||||
echo "A migrated repo already exists at $migrated_dir; move one aside and rerun setup." >&2
|
||||
exit 1
|
||||
fi
|
||||
log "Migrating direct Claude install to $migrated_dir to avoid duplicate skill discovery..."
|
||||
mv "$SOURCE_GSTACK_DIR" "$migrated_dir"
|
||||
SOURCE_GSTACK_DIR="$migrated_dir"
|
||||
BROWSE_BIN="$SOURCE_GSTACK_DIR/browse/dist/browse"
|
||||
_CLAUDE_RELOCATED=1
|
||||
elif [ "$SOURCE_GSTACK_DIR" = "$migrated_dir" ]; then
|
||||
# Already-relocated install re-running setup (e.g. via /gstack-upgrade,
|
||||
# which cd's into the install dir and runs ./setup). Keep aliases in
|
||||
# ~/.claude/skills and refresh the sidecar instead of falling through to
|
||||
# the worktree-symlink branch (which would replant a full-repo symlink
|
||||
# back inside the discovery root).
|
||||
_CLAUDE_RELOCATED=1
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Decouple the alias root from the checkout location: top-level skill aliases
|
||||
# stay in ~/.claude/skills; the checkout lives outside the discovery root.
|
||||
INSTALL_SKILLS_DIR="$HOME/.claude/skills"
|
||||
INSTALL_GSTACK_DIR="$INSTALL_SKILLS_DIR/gstack"
|
||||
}
|
||||
|
||||
if [ "$INSTALL_CLAUDE" -eq 1 ]; then
|
||||
migrate_direct_claude_install
|
||||
fi
|
||||
|
||||
ensure_playwright_browser() {
|
||||
if [ "$IS_WINDOWS" -eq 1 ]; then
|
||||
# On Windows, Bun can't launch Chromium due to broken pipe handling
|
||||
|
|
@ -817,6 +870,48 @@ create_codex_runtime_root() {
|
|||
fi
|
||||
}
|
||||
|
||||
# ─── Helper: create a minimal ~/.claude/skills/gstack runtime sidecar ────────
|
||||
# Counterpart to create_codex_runtime_root, for relocated Claude installs.
|
||||
# After the checkout moves to ~/.gstack/repos/gstack, skills and the ~9 bin/
|
||||
# scripts that hard-code absolute paths like ~/.claude/skills/gstack/bin/
|
||||
# gstack-config (the most-referenced path in the whole repo) still need them to
|
||||
# resolve. Expose ONLY runtime assets here, and — unlike the Codex sidecar —
|
||||
# NO nested SKILL.md at all: Claude's root skill is the separate _gstack-command
|
||||
# alias and gstack-upgrade is a top-level skill, so the sidecar needs none. That
|
||||
# keeps recursive scanners (Cursor walks ~/.claude/skills recursively) at zero
|
||||
# phantom skills from this directory.
|
||||
create_claude_runtime_root() {
|
||||
local gstack_dir="$1"
|
||||
local claude_gstack="$2"
|
||||
|
||||
if [ -L "$claude_gstack" ]; then
|
||||
rm -f "$claude_gstack"
|
||||
elif [ -d "$claude_gstack" ] && [ "$claude_gstack" != "$gstack_dir" ]; then
|
||||
rm -rf "$claude_gstack"
|
||||
fi
|
||||
|
||||
mkdir -p "$claude_gstack" "$claude_gstack/browse" "$claude_gstack/design" \
|
||||
"$claude_gstack/review" "$claude_gstack/plan-devex-review"
|
||||
|
||||
# Whole-dir runtime assets that contain no SKILL.md
|
||||
for asset in bin scripts extension browse/dist browse/bin design/dist review/specialists; do
|
||||
if [ -e "$gstack_dir/$asset" ]; then
|
||||
_link_or_copy "$gstack_dir/$asset" "$claude_gstack/$asset"
|
||||
fi
|
||||
done
|
||||
|
||||
# Individual runtime files (NOT the whole skill dirs that hold their SKILL.md)
|
||||
for file in ETHOS.md \
|
||||
review/checklist.md review/design-checklist.md \
|
||||
review/greptile-triage.md review/TODOS-format.md \
|
||||
plan-devex-review/dx-hall-of-fame.md \
|
||||
.feature-prompted-model-overlay .feature-prompted-continuous-checkpoint; do
|
||||
if [ -f "$gstack_dir/$file" ]; then
|
||||
_link_or_copy "$gstack_dir/$file" "$claude_gstack/$file"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
create_factory_runtime_root() {
|
||||
local gstack_dir="$1"
|
||||
local factory_gstack="$2"
|
||||
|
|
@ -1000,6 +1095,12 @@ if [ "$INSTALL_CLAUDE" -eq 1 ]; then
|
|||
if [ -x "$GSTACK_RELINK" ]; then
|
||||
GSTACK_SKILLS_DIR="$INSTALL_SKILLS_DIR" GSTACK_INSTALL_DIR="$SOURCE_GSTACK_DIR" "$GSTACK_RELINK" >/dev/null 2>&1 || true
|
||||
fi
|
||||
# Relocated install: build the runtime-only sidecar at ~/.claude/skills/gstack
|
||||
# so absolute asset paths (bin/gstack-config, ETHOS.md, review/*, …) resolve
|
||||
# while the full checkout stays out of the recursive discovery root. #1202/#1694
|
||||
if [ "$_CLAUDE_RELOCATED" -eq 1 ]; then
|
||||
create_claude_runtime_root "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR/gstack"
|
||||
fi
|
||||
# Backwards-compat alias: /connect-chrome → /open-gstack-browser
|
||||
_OGB_LINK="$INSTALL_SKILLS_DIR/connect-chrome"
|
||||
if [ "$SKILL_PREFIX" -eq 1 ]; then
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* Regression coverage for #1202 / #1694 — a direct ~/.claude/skills/gstack
|
||||
* checkout sits inside Claude/Cursor's recursive skill-discovery root, so every
|
||||
* generated host-mirror and source SKILL.md gets indexed (hundreds of phantom
|
||||
* skills; Claude drops descriptions, cursor-agent hard-freezes).
|
||||
*
|
||||
* setup's migrate_direct_claude_install + create_claude_runtime_root move the
|
||||
* checkout to ~/.gstack/repos/gstack and leave a runtime-only sidecar with
|
||||
* ZERO nested SKILL.md. These tests source those two functions straight out of
|
||||
* ./setup and exercise them against an isolated fake HOME — no real install is
|
||||
* touched and no full ./setup run (binary build / playwright) is needed.
|
||||
*/
|
||||
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import { spawnSync } from 'bun';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const SETUP = join(import.meta.dir, '..', 'setup');
|
||||
|
||||
// Run a bash snippet with the two setup functions sourced in, under a fresh
|
||||
// fake HOME. Returns stdout (assertions are echoed as PASS/FAIL lines).
|
||||
function runHarness(body: string): { out: string; code: number } {
|
||||
const home = mkdtempSync(join(tmpdir(), 'gstack-reloc-'));
|
||||
const script = `
|
||||
set -u
|
||||
export HOME='${home}'
|
||||
log() { :; }
|
||||
_link_or_copy() { ln -snf "$1" "$2"; }
|
||||
eval "$(sed -n '/^migrate_direct_claude_install() {/,/^}/p' '${SETUP}')"
|
||||
eval "$(sed -n '/^create_claude_runtime_root() {/,/^}/p' '${SETUP}')"
|
||||
|
||||
make_checkout() {
|
||||
local r="$1"; rm -rf "$r"
|
||||
mkdir -p "$r/.git" "$r/bin" "$r/scripts" "$r/browse/dist" "$r/review/specialists" "$r/plan-devex-review"
|
||||
printf '#!/bin/sh\\necho stub\\n' > "$r/bin/gstack-config"; chmod +x "$r/bin/gstack-config"
|
||||
: > "$r/ETHOS.md"; : > "$r/scripts/jargon-list.json"; : > "$r/review/checklist.md"
|
||||
: > "$r/plan-devex-review/dx-hall-of-fame.md"
|
||||
for s in office-hours ship review browse gstack-upgrade; do mkdir -p "$r/$s"; : > "$r/$s/SKILL.md"; done
|
||||
mkdir -p "$r/.cursor/skills/x"; : > "$r/.cursor/skills/x/SKILL.md"
|
||||
}
|
||||
${body}
|
||||
`;
|
||||
const proc = spawnSync(['bash', '-c', script]);
|
||||
rmSync(home, { recursive: true, force: true });
|
||||
return { out: proc.stdout.toString(), code: proc.exitCode ?? 0 };
|
||||
}
|
||||
|
||||
describe('migrate_direct_claude_install + create_claude_runtime_root (#1202)', () => {
|
||||
test('fresh global install relocates and builds a zero-SKILL.md sidecar', () => {
|
||||
const { out } = runHarness(`
|
||||
mkdir -p "$HOME/.claude/skills"
|
||||
make_checkout "$HOME/.claude/skills/gstack"
|
||||
SOURCE_GSTACK_DIR="$HOME/.claude/skills/gstack"; INSTALL_SKILLS_DIR="$HOME/.claude/skills"
|
||||
LOCAL_INSTALL=0; BROWSE_BIN=""; _CLAUDE_RELOCATED=0
|
||||
migrate_direct_claude_install
|
||||
create_claude_runtime_root "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR/gstack"
|
||||
echo "relocated=$_CLAUDE_RELOCATED"
|
||||
echo "moved=$([ -d "$HOME/.gstack/repos/gstack/.git" ] && echo 1 || echo 0)"
|
||||
echo "aliasRoot=$INSTALL_SKILLS_DIR"
|
||||
echo "nestedSkillMd=$(find "$HOME/.claude/skills/gstack" -name SKILL.md | wc -l | tr -d ' ')"
|
||||
echo "binResolves=$([ -x "$HOME/.claude/skills/gstack/bin/gstack-config" ] && echo 1 || echo 0)"
|
||||
echo "ethosResolves=$([ -e "$HOME/.claude/skills/gstack/ETHOS.md" ] && echo 1 || echo 0)"
|
||||
`);
|
||||
expect(out).toContain('relocated=1');
|
||||
expect(out).toContain('moved=1');
|
||||
expect(out).toContain(`aliasRoot=`);
|
||||
expect(out).toMatch(/aliasRoot=.*\/\.claude\/skills$/m);
|
||||
expect(out).toContain('nestedSkillMd=0');
|
||||
expect(out).toContain('binResolves=1');
|
||||
expect(out).toContain('ethosResolves=1');
|
||||
});
|
||||
|
||||
test('re-run from the relocated path is idempotent and keeps the alias root', () => {
|
||||
const { out } = runHarness(`
|
||||
make_checkout "$HOME/.gstack/repos/gstack"
|
||||
SOURCE_GSTACK_DIR="$HOME/.gstack/repos/gstack"; INSTALL_SKILLS_DIR="$HOME/.gstack/repos"
|
||||
LOCAL_INSTALL=0; _CLAUDE_RELOCATED=0
|
||||
migrate_direct_claude_install
|
||||
echo "relocated=$_CLAUDE_RELOCATED"
|
||||
echo "aliasRoot=$INSTALL_SKILLS_DIR"
|
||||
echo "repoIntact=$([ -d "$HOME/.gstack/repos/gstack/.git" ] && echo 1 || echo 0)"
|
||||
`);
|
||||
expect(out).toContain('relocated=1');
|
||||
expect(out).toMatch(/aliasRoot=.*\/\.claude\/skills$/m);
|
||||
expect(out).toContain('repoIntact=1');
|
||||
});
|
||||
|
||||
test('--local installs are never relocated', () => {
|
||||
const { out } = runHarness(`
|
||||
mkdir -p "$HOME/.claude/skills"
|
||||
make_checkout "$HOME/.claude/skills/gstack"
|
||||
SOURCE_GSTACK_DIR="$HOME/.claude/skills/gstack"; LOCAL_INSTALL=1; _CLAUDE_RELOCATED=0
|
||||
migrate_direct_claude_install
|
||||
echo "relocated=$_CLAUDE_RELOCATED"
|
||||
echo "stillThere=$([ -d "$HOME/.claude/skills/gstack/.git" ] && echo 1 || echo 0)"
|
||||
`);
|
||||
expect(out).toContain('relocated=0');
|
||||
expect(out).toContain('stillThere=1');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue