Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Open Claude Code and paste this. Claude does the rest.

> Install gstack: run **`git clone 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, /review, /ship, /land-and-deploy, /canary, /benchmark, /browse, /qa, /qa-only, /design-review, /setup-browser-cookies, /setup-deploy, /retro, /investigate, /document-release, /codex, /cso, /autoplan, /careful, /freeze, /guard, /unfreeze, /gstack-upgrade. Then ask the user if they also want to add gstack to the current project so teammates get it.

If you want prefixed Claude skill names in `~/.claude/skills/`, run `./setup --prefix gstack-`. That creates entries like `gstack-review` and `gstack-ship`. Codex installs already use the `gstack-` prefix by default.

### Step 2: Add to your repo so teammates get it (optional)

> Add gstack to this project: run **`cp -Rf ~/.claude/skills/gstack .claude/skills/gstack && rm -rf .claude/skills/gstack/.git && cd .claude/skills/gstack && ./setup`** then add a "gstack" section to this project's CLAUDE.md that says to use the /browse skill from gstack for all web browsing, never use mcp\_\_claude-in-chrome\_\_\* tools, lists the available skills: /office-hours, /plan-ceo-review, /plan-eng-review, /plan-design-review, /design-consultation, /review, /ship, /land-and-deploy, /canary, /benchmark, /browse, /qa, /qa-only, /design-review, /setup-browser-cookies, /setup-deploy, /retro, /investigate, /document-release, /codex, /cso, /careful, /freeze, /guard, /unfreeze, /gstack-upgrade, and tells Claude that if gstack skills aren't working, run `cd .claude/skills/gstack && ./setup` to build the binary and register skills.
Expand Down
21 changes: 18 additions & 3 deletions setup
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,20 @@ esac

# ─── Parse --host flag ─────────────────────────────────────────
HOST="claude"
CLAUDE_PREFIX=""
while [ $# -gt 0 ]; do
case "$1" in
--host) HOST="$2"; shift 2 ;;
--host=*) HOST="${1#--host=}"; shift ;;
--prefix)
[ $# -lt 2 ] && echo "Missing value for --prefix" >&2 && exit 1
CLAUDE_PREFIX="$2"
shift 2
;;
--prefix=*)
CLAUDE_PREFIX="${1#--prefix=}"
shift
;;
*) shift ;;
esac
done
Expand Down Expand Up @@ -137,17 +147,22 @@ mkdir -p "$HOME/.gstack/projects"
link_claude_skill_dirs() {
local gstack_dir="$1"
local skills_dir="$2"
local skill_prefix="${3:-}"
local linked=()
for skill_dir in "$gstack_dir"/*/; do
if [ -f "$skill_dir/SKILL.md" ]; then
skill_name="$(basename "$skill_dir")"
target_name="$skill_name"
# Skip node_modules
[ "$skill_name" = "node_modules" ] && continue
target="$skills_dir/$skill_name"
if [ -n "$skill_prefix" ] && [ "${skill_name#"$skill_prefix"}" = "$skill_name" ]; then
target_name="${skill_prefix}$skill_name"
fi
target="$skills_dir/$target_name"
# Create or update symlink; skip if a real file/directory exists
if [ -L "$target" ] || [ ! -e "$target" ]; then
ln -snf "gstack/$skill_name" "$target"
linked+=("$skill_name")
linked+=("$target_name")
fi
fi
done
Expand Down Expand Up @@ -222,7 +237,7 @@ create_agents_sidecar() {
SKILLS_BASENAME="$(basename "$SKILLS_DIR")"
if [ "$INSTALL_CLAUDE" -eq 1 ]; then
if [ "$SKILLS_BASENAME" = "skills" ]; then
link_claude_skill_dirs "$GSTACK_DIR" "$SKILLS_DIR"
link_claude_skill_dirs "$GSTACK_DIR" "$SKILLS_DIR" "$CLAUDE_PREFIX"
echo "gstack ready (claude)."
echo " browse: $BROWSE_BIN"
else
Expand Down
30 changes: 30 additions & 0 deletions test/gen-skill-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -982,11 +982,41 @@ describe('setup script validation', () => {
expect(setupContent).toContain('claude|codex|auto');
});

test('setup supports --prefix for Claude skill symlinks', () => {
expect(setupContent).toContain('--prefix');
expect(setupContent).toContain('CLAUDE_PREFIX');
});

test('auto mode detects claude and codex binaries', () => {
expect(setupContent).toContain('command -v claude');
expect(setupContent).toContain('command -v codex');
});

test('link_claude_skill_dirs applies optional prefix to target names', () => {
const fnStart = setupContent.indexOf('link_claude_skill_dirs()');
const fnEnd = setupContent.indexOf('}', setupContent.indexOf('linked[@]}', fnStart));
const fnBody = setupContent.slice(fnStart, fnEnd);
expect(fnBody).toContain('local skill_prefix="${3:-}"');
expect(fnBody).toContain('target_name="$skill_name"');
expect(fnBody).toContain('target="$skills_dir/$target_name"');
});

test('Claude install passes CLAUDE_PREFIX into link_claude_skill_dirs', () => {
const claudeSection = setupContent.slice(
setupContent.indexOf('# 4. Install for Claude'),
setupContent.indexOf('# 5. Install for Codex')
);
expect(claudeSection).toContain('link_claude_skill_dirs "$GSTACK_DIR" "$SKILLS_DIR" "$CLAUDE_PREFIX"');
});

test('link_claude_skill_dirs avoids double-prefixing already-prefixed skills', () => {
const fnStart = setupContent.indexOf('link_claude_skill_dirs()');
const fnEnd = setupContent.indexOf('}', setupContent.indexOf('linked[@]}', fnStart));
const fnBody = setupContent.slice(fnStart, fnEnd);
expect(fnBody).toContain('if [ -n "$skill_prefix" ] && [ "${skill_name#"$skill_prefix"}" = "$skill_name" ]; then');
expect(fnBody).toContain('target_name="${skill_prefix}$skill_name"');
});

test('create_agents_sidecar links runtime assets', () => {
// Sidecar must link bin, browse, review, qa
const fnStart = setupContent.indexOf('create_agents_sidecar()');
Expand Down