mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-22 04:38:24 +08: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.
|
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
|
```bash
|
||||||
git clone https://github.com/garrytan/gstack.git ~/.codex/skills/gstack
|
git clone https://github.com/garrytan/gstack.git ~/gstack
|
||||||
cd ~/.codex/skills/gstack && ./setup --host codex
|
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:
|
Or let setup auto-detect which agents you have installed:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
73
setup
73
setup
@@ -11,6 +11,8 @@ fi
|
|||||||
GSTACK_DIR="$(cd "$(dirname "$0")" && pwd)"
|
GSTACK_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
SKILLS_DIR="$(dirname "$GSTACK_DIR")"
|
SKILLS_DIR="$(dirname "$GSTACK_DIR")"
|
||||||
BROWSE_BIN="$GSTACK_DIR/browse/dist/browse"
|
BROWSE_BIN="$GSTACK_DIR/browse/dist/browse"
|
||||||
|
CODEX_SKILLS="$HOME/.codex/skills"
|
||||||
|
CODEX_GSTACK="$CODEX_SKILLS/gstack"
|
||||||
|
|
||||||
IS_WINDOWS=0
|
IS_WINDOWS=0
|
||||||
case "$(uname -s)" in
|
case "$(uname -s)" in
|
||||||
@@ -48,6 +50,32 @@ elif [ "$HOST" = "codex" ]; then
|
|||||||
INSTALL_CODEX=1
|
INSTALL_CODEX=1
|
||||||
fi
|
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() {
|
ensure_playwright_browser() {
|
||||||
if [ "$IS_WINDOWS" -eq 1 ]; then
|
if [ "$IS_WINDOWS" -eq 1 ]; then
|
||||||
# On Windows, Bun can't launch Chromium due to broken pipe handling
|
# On Windows, Bun can't launch Chromium due to broken pipe handling
|
||||||
@@ -248,6 +276,44 @@ create_agents_sidecar() {
|
|||||||
done
|
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)
|
# 4. Install for Claude (default)
|
||||||
SKILLS_BASENAME="$(basename "$SKILLS_DIR")"
|
SKILLS_BASENAME="$(basename "$SKILLS_DIR")"
|
||||||
if [ "$INSTALL_CLAUDE" -eq 1 ]; then
|
if [ "$INSTALL_CLAUDE" -eq 1 ]; then
|
||||||
@@ -264,14 +330,9 @@ fi
|
|||||||
|
|
||||||
# 5. Install for Codex
|
# 5. Install for Codex
|
||||||
if [ "$INSTALL_CODEX" -eq 1 ]; then
|
if [ "$INSTALL_CODEX" -eq 1 ]; then
|
||||||
CODEX_SKILLS="$HOME/.codex/skills"
|
|
||||||
CODEX_GSTACK="$CODEX_SKILLS/gstack"
|
|
||||||
mkdir -p "$CODEX_SKILLS"
|
mkdir -p "$CODEX_SKILLS"
|
||||||
|
|
||||||
# Symlink gstack source for runtime assets (bin/, browse/dist/)
|
create_codex_runtime_root "$GSTACK_DIR" "$CODEX_GSTACK"
|
||||||
if [ -L "$CODEX_GSTACK" ] || [ ! -e "$CODEX_GSTACK" ]; then
|
|
||||||
ln -snf "$GSTACK_DIR" "$CODEX_GSTACK"
|
|
||||||
fi
|
|
||||||
# Install generated Codex-format skills (not Claude source dirs)
|
# Install generated Codex-format skills (not Claude source dirs)
|
||||||
link_codex_skill_dirs "$GSTACK_DIR" "$CODEX_SKILLS"
|
link_codex_skill_dirs "$GSTACK_DIR" "$CODEX_SKILLS"
|
||||||
|
|
||||||
|
|||||||
@@ -853,8 +853,10 @@ describe('setup script validation', () => {
|
|||||||
setupContent.indexOf('# 5. Install for Codex'),
|
setupContent.indexOf('# 5. Install for Codex'),
|
||||||
setupContent.indexOf('# 6. Create')
|
setupContent.indexOf('# 6. Create')
|
||||||
);
|
);
|
||||||
|
expect(codexSection).toContain('create_codex_runtime_root');
|
||||||
expect(codexSection).toContain('link_codex_skill_dirs');
|
expect(codexSection).toContain('link_codex_skill_dirs');
|
||||||
expect(codexSection).not.toContain('link_claude_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/', () => {
|
test('link_codex_skill_dirs reads from .agents/skills/', () => {
|
||||||
@@ -894,6 +896,28 @@ describe('setup script validation', () => {
|
|||||||
expect(fnBody).toContain('review');
|
expect(fnBody).toContain('review');
|
||||||
expect(fnBody).toContain('qa');
|
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', () => {
|
describe('telemetry', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user