From e29d7a9ac9a3fbe4dd524a1d7b53a39fe27ced50 Mon Sep 17 00:00:00 2001 From: liaoshichang02 Date: Fri, 20 Mar 2026 13:59:04 +0800 Subject: [PATCH 1/2] fix: avoid duplicate Codex skill discovery Migrate direct ~/.codex/skills/gstack installs into ~/.gstack/repos/gstack during setup.\n\nCreate a minimal Codex runtime root that only exposes runtime assets and generated SKILL.md files so Codex no longer discovers duplicate source skills recursively. --- README.md | 8 +++-- setup | 67 +++++++++++++++++++++++++++++++++---- test/gen-skill-docs.test.ts | 19 +++++++++++ 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b7ddb7d1..b81613bf 100644 --- a/README.md +++ b/README.md @@ -61,10 +61,14 @@ Real files get committed to your repo (not a submodule), so `git clone` just wor gstack works on any agent that supports the [SKILL.md standard](https://github.com/anthropics/claude-code). Skills live in `.agents/skills/` and are discovered automatically. ```bash -git clone https://github.com/garrytan/gstack.git ~/.codex/skills/gstack -cd ~/.codex/skills/gstack && ./setup --host codex +git clone https://github.com/garrytan/gstack.git ~/gstack +cd ~/gstack && ./setup --host codex ``` +`setup --host codex` creates the runtime root at `~/.codex/skills/gstack` and +links the generated Codex skills at the top level. This avoids duplicate skill +discovery from the source repo checkout. + Or let setup auto-detect which agents you have installed: ```bash diff --git a/setup b/setup index cf3e5050..92307b13 100755 --- a/setup +++ b/setup @@ -11,6 +11,8 @@ fi GSTACK_DIR="$(cd "$(dirname "$0")" && pwd)" SKILLS_DIR="$(dirname "$GSTACK_DIR")" BROWSE_BIN="$GSTACK_DIR/browse/dist/browse" +CODEX_SKILLS="$HOME/.codex/skills" +CODEX_GSTACK="$CODEX_SKILLS/gstack" # ─── Parse --host flag ───────────────────────────────────────── HOST="claude" @@ -43,6 +45,32 @@ elif [ "$HOST" = "codex" ]; then INSTALL_CODEX=1 fi +migrate_direct_codex_install() { + local gstack_dir="$1" + local codex_gstack="$2" + local migrated_dir="$HOME/.gstack/repos/gstack" + + [ "$gstack_dir" = "$codex_gstack" ] || return 0 + [ -L "$gstack_dir" ] && return 0 + + mkdir -p "$(dirname "$migrated_dir")" + if [ -e "$migrated_dir" ] && [ "$migrated_dir" != "$gstack_dir" ]; then + echo "gstack setup failed: direct Codex install detected at $gstack_dir" >&2 + echo "A migrated repo already exists at $migrated_dir; move one of them aside and rerun setup." >&2 + exit 1 + fi + + echo "Migrating direct Codex install to $migrated_dir to avoid duplicate skill discovery..." + mv "$gstack_dir" "$migrated_dir" + GSTACK_DIR="$migrated_dir" + SKILLS_DIR="$(dirname "$GSTACK_DIR")" + BROWSE_BIN="$GSTACK_DIR/browse/dist/browse" +} + +if [ "$INSTALL_CODEX" -eq 1 ]; then + migrate_direct_codex_install "$GSTACK_DIR" "$CODEX_GSTACK" +fi + ensure_playwright_browser() { ( cd "$GSTACK_DIR" @@ -171,6 +199,38 @@ create_agents_sidecar() { done } +# ─── Helper: create a minimal ~/.codex/skills/gstack runtime root ─────────── +# Codex scans ~/.codex/skills recursively. Exposing the whole repo here causes +# duplicate skills because source SKILL.md files and generated Codex skills are +# both discoverable. Keep this directory limited to runtime assets + root skill. +create_codex_runtime_root() { + local gstack_dir="$1" + local codex_gstack="$2" + local agents_dir="$gstack_dir/.agents/skills" + + if [ -L "$codex_gstack" ]; then + rm -f "$codex_gstack" + fi + + mkdir -p "$codex_gstack" "$codex_gstack/browse" "$codex_gstack/gstack-upgrade" + + if [ -f "$agents_dir/gstack/SKILL.md" ]; then + ln -snf "$agents_dir/gstack/SKILL.md" "$codex_gstack/SKILL.md" + fi + if [ -d "$gstack_dir/bin" ]; then + ln -snf "$gstack_dir/bin" "$codex_gstack/bin" + fi + if [ -d "$gstack_dir/browse/dist" ]; then + ln -snf "$gstack_dir/browse/dist" "$codex_gstack/browse/dist" + fi + if [ -d "$gstack_dir/browse/bin" ]; then + ln -snf "$gstack_dir/browse/bin" "$codex_gstack/browse/bin" + fi + if [ -f "$agents_dir/gstack-upgrade/SKILL.md" ]; then + ln -snf "$agents_dir/gstack-upgrade/SKILL.md" "$codex_gstack/gstack-upgrade/SKILL.md" + fi +} + # 4. Install for Claude (default) SKILLS_BASENAME="$(basename "$SKILLS_DIR")" if [ "$INSTALL_CLAUDE" -eq 1 ]; then @@ -187,14 +247,9 @@ fi # 5. Install for Codex if [ "$INSTALL_CODEX" -eq 1 ]; then - CODEX_SKILLS="$HOME/.codex/skills" - CODEX_GSTACK="$CODEX_SKILLS/gstack" mkdir -p "$CODEX_SKILLS" - # Symlink gstack source for runtime assets (bin/, browse/dist/) - if [ -L "$CODEX_GSTACK" ] || [ ! -e "$CODEX_GSTACK" ]; then - ln -snf "$GSTACK_DIR" "$CODEX_GSTACK" - fi + create_codex_runtime_root "$GSTACK_DIR" "$CODEX_GSTACK" # Install generated Codex-format skills (not Claude source dirs) link_codex_skill_dirs "$GSTACK_DIR" "$CODEX_SKILLS" diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index 6627c5c7..2cc09379 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -686,8 +686,10 @@ describe('setup script validation', () => { setupContent.indexOf('# 5. Install for Codex'), setupContent.indexOf('# 6. Create') ); + expect(codexSection).toContain('create_codex_runtime_root'); expect(codexSection).toContain('link_codex_skill_dirs'); expect(codexSection).not.toContain('link_claude_skill_dirs'); + expect(codexSection).not.toContain('ln -snf "$GSTACK_DIR" "$CODEX_GSTACK"'); }); test('link_codex_skill_dirs reads from .agents/skills/', () => { @@ -727,6 +729,23 @@ describe('setup script validation', () => { expect(fnBody).toContain('review'); expect(fnBody).toContain('qa'); }); + + test('create_codex_runtime_root exposes only runtime assets', () => { + const fnStart = setupContent.indexOf('create_codex_runtime_root()'); + const fnEnd = setupContent.indexOf('}', setupContent.indexOf('gstack-upgrade/SKILL.md', fnStart)); + const fnBody = setupContent.slice(fnStart, fnEnd); + expect(fnBody).toContain('gstack/SKILL.md'); + expect(fnBody).toContain('browse/dist'); + expect(fnBody).toContain('browse/bin'); + expect(fnBody).toContain('gstack-upgrade/SKILL.md'); + expect(fnBody).not.toContain('ln -snf "$gstack_dir" "$codex_gstack"'); + }); + + test('direct Codex installs are migrated out of ~/.codex/skills/gstack', () => { + expect(setupContent).toContain('migrate_direct_codex_install'); + expect(setupContent).toContain('$HOME/.gstack/repos/gstack'); + expect(setupContent).toContain('avoid duplicate skill discovery'); + }); }); describe('telemetry', () => { From 36fc61ff5f9e696d3391fb6b8419e220ac49a645 Mon Sep 17 00:00:00 2001 From: shichangs Date: Fri, 20 Mar 2026 14:46:27 +0800 Subject: [PATCH 2/2] fix: include review runtime assets in Codex runtime root Generated Codex skills (review, ship, plan-ceo-review, plan-eng-review) reference checklist.md, design-checklist.md, greptile-triage.md, and TODOS-format.md under .agents/skills/gstack/review/. Symlink these individual files instead of the whole review/ dir (which contains SKILL.md and would re-introduce duplicate discovery). Co-Authored-By: Claude Opus 4.6 --- setup | 8 +++++++- test/gen-skill-docs.test.ts | 7 ++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/setup b/setup index 92307b13..bbb3a2e7 100755 --- a/setup +++ b/setup @@ -212,7 +212,7 @@ create_codex_runtime_root() { rm -f "$codex_gstack" fi - mkdir -p "$codex_gstack" "$codex_gstack/browse" "$codex_gstack/gstack-upgrade" + mkdir -p "$codex_gstack" "$codex_gstack/browse" "$codex_gstack/gstack-upgrade" "$codex_gstack/review" if [ -f "$agents_dir/gstack/SKILL.md" ]; then ln -snf "$agents_dir/gstack/SKILL.md" "$codex_gstack/SKILL.md" @@ -229,6 +229,12 @@ create_codex_runtime_root() { if [ -f "$agents_dir/gstack-upgrade/SKILL.md" ]; then ln -snf "$agents_dir/gstack-upgrade/SKILL.md" "$codex_gstack/gstack-upgrade/SKILL.md" fi + # Review runtime assets (individual files, NOT the whole review/ dir which has SKILL.md) + for f in checklist.md design-checklist.md greptile-triage.md TODOS-format.md; do + if [ -f "$gstack_dir/review/$f" ]; then + ln -snf "$gstack_dir/review/$f" "$codex_gstack/review/$f" + fi + done } # 4. Install for Claude (default) diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index 2cc09379..5cf50799 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -732,12 +732,17 @@ describe('setup script validation', () => { test('create_codex_runtime_root exposes only runtime assets', () => { const fnStart = setupContent.indexOf('create_codex_runtime_root()'); - const fnEnd = setupContent.indexOf('}', setupContent.indexOf('gstack-upgrade/SKILL.md', fnStart)); + const fnEnd = setupContent.indexOf('}', setupContent.indexOf('done', setupContent.indexOf('review/', fnStart))); const fnBody = setupContent.slice(fnStart, fnEnd); expect(fnBody).toContain('gstack/SKILL.md'); expect(fnBody).toContain('browse/dist'); expect(fnBody).toContain('browse/bin'); expect(fnBody).toContain('gstack-upgrade/SKILL.md'); + // Review runtime assets (individual files, not the whole dir) + expect(fnBody).toContain('checklist.md'); + expect(fnBody).toContain('design-checklist.md'); + expect(fnBody).toContain('greptile-triage.md'); + expect(fnBody).toContain('TODOS-format.md'); expect(fnBody).not.toContain('ln -snf "$gstack_dir" "$codex_gstack"'); });