mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-14 00:49:00 +02:00
feat(setup): wire --host cursor through the full install path
'./setup --host cursor' was accepted by the flag parser and then did nothing: no INSTALL_CURSOR branch existed, so the script built binaries, printed no 'ready' line, and installed zero skills — Cursor users had no way to install gstack at all. Full install slice, re-derived from PR #2547 by @szsunyuan onto the current installers: generate .cursor/ skill docs (host config already existed), create a minimal ~/.cursor/skills/gstack runtime root (root SKILL.md + bin/lib/browse assets + review checklist pair + ETHOS.md + supabase config — bin and lib travel together because bin scripts import ../lib), link the generated gstack-* skills, and plant the repo-local .cursor/skills/gstack sidecar WITHOUT ever wiping the generated SKILL.md files it shares a directory with (link-before-sidecar ordering keeps the generation fallback alive). Auto mode detects Cursor via the cursor binary or the ~/.cursor footprint. gstack-uninstall removes ~/.cursor/skills/gstack* and per-project .cursor/skills/gstack* — and never rmdir's .cursor itself, where Cursor stores user rules. Re-derivation deltas from the PR: the link guards carry the #2444 IS_WINDOWS bypass (re-runs refresh real-dir copies), lib/ and supabase/config.sh ride along like every other runtime root, and the hosts/cursor.ts sidecar field is omitted (HostConfig no longer carries one — sidecar behavior lives in setup). Fixes #1358 Co-authored-by: Yuan Sun <forrest.sun527@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Yuan Sun
Claude Fable 5
parent
c84246845e
commit
2be9bd0660
@@ -2490,6 +2490,86 @@ describe('setup script validation', () => {
|
||||
expect(setupContent).toContain('OPENCODE_GSTACK="$OPENCODE_SKILLS/gstack"');
|
||||
});
|
||||
|
||||
// --host cursor full install slice (#1358, PR #2547 by @szsunyuan re-derived)
|
||||
test('auto mode detects Cursor via binary or ~/.cursor directory', () => {
|
||||
expect(setupContent).toContain('command -v cursor');
|
||||
expect(setupContent).toContain('[ -d "$HOME/.cursor" ] && INSTALL_CURSOR=1');
|
||||
});
|
||||
|
||||
test('setup supports --host cursor with install section and Cursor skill path vars', () => {
|
||||
expect(setupContent).toContain('INSTALL_CURSOR=');
|
||||
expect(setupContent).toContain('CURSOR_SKILLS="$HOME/.cursor/skills"');
|
||||
expect(setupContent).toContain('CURSOR_GSTACK="$CURSOR_SKILLS/gstack"');
|
||||
expect(setupContent).toContain('create_cursor_runtime_root');
|
||||
expect(setupContent).toContain('create_cursor_sidecar');
|
||||
expect(setupContent).toContain('link_cursor_skill_dirs');
|
||||
expect(setupContent).toContain('gstack ready (cursor).');
|
||||
});
|
||||
|
||||
test('create_cursor_runtime_root exposes only Cursor runtime assets', () => {
|
||||
const fnStart = setupContent.indexOf('create_cursor_runtime_root()');
|
||||
const fnEnd = setupContent.indexOf('create_cursor_sidecar()', 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).toContain('checklist.md');
|
||||
expect(fnBody).toContain('TODOS-format.md');
|
||||
// bin scripts import ../lib — the two must travel together.
|
||||
expect(fnBody).toContain('$cursor_gstack/lib');
|
||||
expect(fnBody).not.toContain('design-checklist.md');
|
||||
expect(fnBody).not.toContain('greptile-triage.md');
|
||||
expect(fnBody).not.toContain('review/specialists');
|
||||
expect(fnBody).not.toContain('qa/templates');
|
||||
expect(fnBody).not.toContain('_link_or_copy "$gstack_dir" "$cursor_gstack"');
|
||||
});
|
||||
|
||||
test('create_cursor_sidecar plants runtime assets without wiping generated SKILL.md', () => {
|
||||
const fnStart = setupContent.indexOf('create_cursor_sidecar()');
|
||||
const fnEnd = setupContent.indexOf('link_cursor_skill_dirs()', fnStart);
|
||||
const fnBody = setupContent.slice(fnStart, fnEnd);
|
||||
expect(fnBody).toContain('.cursor/skills/gstack');
|
||||
expect(fnBody).toContain('bin');
|
||||
expect(fnBody).toContain('browse/dist');
|
||||
expect(fnBody).toContain('browse/bin');
|
||||
expect(fnBody).toContain('ETHOS.md');
|
||||
expect(fnBody).not.toContain('rm -rf');
|
||||
});
|
||||
|
||||
test('link_cursor_skill_dirs skips the gstack runtime root directory', () => {
|
||||
const fnStart = setupContent.indexOf('link_cursor_skill_dirs()');
|
||||
const fnEnd = setupContent.indexOf('}', setupContent.indexOf('linked[@]', fnStart));
|
||||
const fnBody = setupContent.slice(fnStart, fnEnd);
|
||||
expect(fnBody).toContain('[ "$skill_name" = "gstack" ] && continue');
|
||||
// #2444-aware guard: Windows bypass, else only replace symlink-or-missing.
|
||||
expect(fnBody).toContain('[ "$IS_WINDOWS" -eq 1 ] || [ -L "$target" ] || [ ! -e "$target" ]');
|
||||
});
|
||||
|
||||
// #2142 deleted existing ~/.cursor/skills/<name> dirs with `rm -rf "$target"`
|
||||
// before relinking. That can wipe unowned Cursor skills. Only replace a
|
||||
// symlink or a missing path; never the whole skills directory.
|
||||
test('link_cursor_skill_dirs does not delete unowned Cursor skill directories', () => {
|
||||
const fnStart = setupContent.indexOf('link_cursor_skill_dirs()');
|
||||
const fnEnd = setupContent.indexOf('}', setupContent.indexOf('linked[@]', fnStart));
|
||||
const fnBody = setupContent.slice(fnStart, fnEnd);
|
||||
expect(fnBody).not.toContain('rm -rf "$target"');
|
||||
expect(fnBody).not.toContain('rm -rf "$skills_dir"');
|
||||
expect(setupContent).not.toContain('rm -rf "$CURSOR_SKILLS"');
|
||||
});
|
||||
|
||||
test('Cursor install links generated skills before planting the sidecar', () => {
|
||||
const cursorInstall = setupContent.slice(
|
||||
setupContent.indexOf('# 6d. Install for Cursor'),
|
||||
setupContent.indexOf('# 7. Create .agents/ sidecar'),
|
||||
);
|
||||
const linkCall = cursorInstall.indexOf('link_cursor_skill_dirs "$SOURCE_GSTACK_DIR"');
|
||||
const sidecarCall = cursorInstall.indexOf('create_cursor_sidecar "$SOURCE_GSTACK_DIR"');
|
||||
expect(linkCall).toBeGreaterThan(-1);
|
||||
expect(sidecarCall).toBeGreaterThan(-1);
|
||||
expect(linkCall).toBeLessThan(sidecarCall);
|
||||
});
|
||||
|
||||
test('setup installs OpenCode skills into a nested gstack runtime root', () => {
|
||||
expect(setupContent).toContain('create_opencode_runtime_root');
|
||||
expect(setupContent).toContain('.opencode/skills');
|
||||
|
||||
Reference in New Issue
Block a user