mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-05 13:15:24 +02:00
fix: avoid duplicate Codex skill discovery (#236)
Adds migrate_direct_codex_install() to move old direct installs from ~/.codex/skills/gstack to ~/.gstack/repos/gstack. Adds create_codex_runtime_root() to expose only runtime assets (bin/, browse/, review files) via symlinks instead of symlinking the entire repo. Fixes #235 Co-authored-by: shichangs <shichangs@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
IS_WINDOWS=0
|
||||
case "$(uname -s)" in
|
||||
@@ -48,6 +50,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() {
|
||||
if [ "$IS_WINDOWS" -eq 1 ]; then
|
||||
# On Windows, Bun can't launch Chromium due to broken pipe handling
|
||||
@@ -248,6 +276,44 @@ 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" "$codex_gstack/review"
|
||||
|
||||
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
|
||||
# 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)
|
||||
SKILLS_BASENAME="$(basename "$SKILLS_DIR")"
|
||||
if [ "$INSTALL_CLAUDE" -eq 1 ]; then
|
||||
@@ -264,14 +330,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"
|
||||
|
||||
|
||||
@@ -853,8 +853,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/', () => {
|
||||
@@ -894,6 +896,28 @@ 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('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"');
|
||||
});
|
||||
|
||||
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', () => {
|
||||
|
||||
Reference in New Issue
Block a user