v1.92.22.0 feat: Gemini CLI host, and older installs drop the dev toolchain (#3778)

* setup: explicit contributor marker; move pre-v1.91.231.0 installs to production deps (#3447)

./setup used node_modules/typescript as its contributor signal, so every
install made before v1.91.231.0 (a full bun install) kept ~520 MB of test and
eval packages forever. The contributor is now the clone bin/dev-setup marks
with git config gstack.contributor true, or GSTACK_CONTRIBUTOR=1. An unmarked
clone with the dev toolchain moves node_modules aside under a checkout lock,
installs production-only, verifies every dependency, and drops the backup; any
failure puts the full install back with SETUP_DEPS_MIGRATE_FAILED. CI and a
clone whose git config cannot be read never migrate. Bun cache entries for the
pinned Agent SDK are removed only when no other project links to them.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* hosts: add Gemini CLI as an experimental host (#1247)

./setup --host gemini (alias gemini-cli; --host auto when gemini is on PATH)
installs gstack-<skill> directories into ~/.gemini/skills, which Gemini CLI
0.66.0 lists with gemini skills list, plus the ~/.gemini/skills/gstack runtime
root. Renders go to .gemini-cli/ (gitignored) with name+description
frontmatter whose names match their directories, CLAUDE.md rewritten to
GEMINI.md, and a tool glossary for Gemini CLI's names (and Antigravity's, which
can read the same folder). gstack-uninstall removes only gstack-managed entries
there, so the user's own skills stay. Docs: ADDING_A_HOST.md verified facts,
hosts.md, uninstall.md, README.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (leave Bun's shared cache alone)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: Gemini CLI host and contributor marker (#1247, #3447)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: host lists name Copilot, Antigravity and Gemini CLI

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: the plan-mode re-render guard count includes the Gemini CLI host

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: troubleshooting's runtime-root guard host list names Gemini CLI

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-01 09:35:42 +00:00
1 parent c03f185d15
commit e4e4e42967
34 files changed
+910 -74

No files matched your search

+40
View File
@@ -204,6 +204,46 @@ describe('gstack-uninstall', () => {
expect(fs.existsSync(path.join(mockGitRoot, '.cursor', 'rules', 'keep.md'))).toBe(true);
});
// Value: protects=uninstall clears gstack's Gemini CLI skills from ~/.gemini/skills, a folder the user's own skills share;
// fails_when=the Gemini block is dropped, or loses the provenance gate and sweeps a user's gstack-prefixed skill;
// why_new=#1247 adds the Gemini CLI host; seam=none; no_claim=Gemini CLI itself (temp HOME fixture)
test('--force removes Gemini CLI gstack skills and leaves the user\'s own', () => {
const banner = '<!-- AUTO-GENERATED from SKILL.md.tmpl - DO NOT EDIT DIRECTLY -->\n# x\n';
const skills = path.join(mockHome, '.gemini', 'skills');
const render = path.join(tmpDir, 'render', 'gstack-review');
fs.mkdirSync(render, { recursive: true });
fs.writeFileSync(path.join(render, 'SKILL.md'), banner);
fs.mkdirSync(path.join(skills, 'gstack'), { recursive: true });
fs.writeFileSync(path.join(skills, 'gstack', 'SKILL.md'), banner);
fs.symlinkSync(render, path.join(skills, 'gstack-review'));
for (const [name, body] of [['gstack-my-notes', '# mine\n'], ['frontend-design', 'keep']]) {
fs.mkdirSync(path.join(skills, name), { recursive: true });
fs.writeFileSync(path.join(skills, name, 'SKILL.md'), body);
}
fs.writeFileSync(path.join(mockHome, '.gemini', 'settings.json'), '{}');
const result = spawnSync('bash', [UNINSTALL, '--force'], {
stdio: 'pipe',
timeout: 30_000,
env: {
...process.env,
HOME: mockHome,
GSTACK_DIR: path.join(mockHome, '.claude', 'skills', 'gstack'),
GSTACK_STATE_DIR: path.join(mockHome, '.gstack'),
},
cwd: mockGitRoot,
});
expect(result.status).toBe(0);
expect(fs.existsSync(path.join(skills, 'gstack'))).toBe(false);
expect(fs.existsSync(path.join(skills, 'gstack-review'))).toBe(false);
expect(fs.existsSync(path.join(render, 'SKILL.md'))).toBe(true);
expect(fs.existsSync(path.join(skills, 'gstack-my-notes', 'SKILL.md'))).toBe(true);
expect(fs.existsSync(path.join(skills, 'frontend-design', 'SKILL.md'))).toBe(true);
expect(fs.existsSync(path.join(mockHome, '.gemini', 'settings.json'))).toBe(true);
expect(result.stderr.toString()).toContain('gstack-my-notes');
});
test("a user's own gstack-prefixed Cursor dir (no banner) survives and is listed", () => {
// S5: the bare gstack* glob must not sweep a dir that merely starts
// with "gstack" — provenance comes from the generated banner, and a