-
Notifications
You must be signed in to change notification settings - Fork 90
feat(agent): add Gemini CLI as an install target #236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,8 @@ export type AgentTarget = | |
| | 'codex' | ||
| | 'kiro' | ||
| | 'windsurf' | ||
| | 'copilot'; | ||
| | 'copilot' | ||
| | 'gemini'; | ||
|
|
||
| export interface TargetSpec { | ||
| status: 'ga' | 'experimental'; | ||
|
|
@@ -189,6 +190,8 @@ export function pathFor(target: AgentTarget, skill: string): string { | |
| return `.windsurf/rules/${skill}.md`; | ||
| case 'copilot': | ||
| return `.github/instructions/${skill}.instructions.md`; | ||
| case 'gemini': | ||
| return 'GEMINI.md'; | ||
| case 'codex': | ||
| return 'AGENTS.md'; | ||
| } | ||
|
|
@@ -243,11 +246,34 @@ export const TARGETS: Record<AgentTarget, TargetSpec> = { | |
| // GitHub Copilot path-specific instructions: frontmatter carries `applyTo`. | ||
| // `applyTo: '**'` means the file is ALWAYS injected into Copilot requests | ||
| // (there is no on-demand "model decides" mode like Cursor/Windsurf), so | ||
| // render the compact body to keep the always-on context cost small — the | ||
| // render the compact body to keep the always-on context cost small -- the | ||
| // same reasoning that drives windsurf's compact render. | ||
| compactBody: true, | ||
| wrap: wrapCopilot, | ||
| }, | ||
| /** | ||
| * gemini target -- managed-section mode (GEMINI.md). | ||
| * | ||
| * Gemini CLI reads project-level instructions from a `GEMINI.md` file in | ||
| * the repo root. The file is plain Markdown, loaded at the start of every | ||
| * session (always-on). Because all installed skills share this single file | ||
| * we use managed-section mode -- the same approach as codex/AGENTS.md -- | ||
| * writing only a sentinel-delimited section so any existing user content | ||
| * in GEMINI.md is never clobbered. | ||
| * | ||
| * The compact body is used (same reasoning as windsurf/copilot): GEMINI.md | ||
| * is always-injected, so keeping it small reduces context cost. | ||
| * | ||
| * --force replaces the managed section unconditionally but never touches | ||
| * content outside the sentinels. | ||
| */ | ||
| gemini: { | ||
| status: 'experimental', | ||
| path: pathFor('gemini', SKILL_NAME), | ||
| mode: 'managed-section', | ||
| // GEMINI.md is plain Markdown; wrap is a no-op (no frontmatter). | ||
| wrap: (_name, _description, body) => body, | ||
| }, | ||
|
Comment on lines
+254
to
+276
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Use a target-neutral managed-section sentinel. Gemini uses the shared managed-section writer, but 🤖 Prompt for AI Agents |
||
| /** | ||
| * codex target — managed-section mode. | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -179,8 +179,12 @@ describe('content integrity', () => { | |
| expect(content.startsWith('---'), `copilot: should start with ---`).toBe(true); | ||
| expect(content).toContain("applyTo: '**'"); | ||
| expect(content).toContain('description:'); | ||
| } else if (target === 'gemini') { | ||
| // GEMINI.md is plain Markdown -- no frontmatter expected. | ||
| expect(content.startsWith('---'), `gemini: must NOT start with ---`).toBe(false); | ||
| expect(content).not.toContain('applyTo:'); | ||
| expect(content).not.toContain('trigger:'); | ||
| } | ||
|
|
||
| // (b) branding — the renamed H1 must be present in every body variant | ||
| expect(content).toContain('TestSprite Verification Loop'); | ||
| // The full-body intro line lives only in the FULL body; compact-body targets | ||
|
|
@@ -219,6 +223,9 @@ describe('content integrity', () => { | |
| } else if (target === 'copilot') { | ||
| expect(content.startsWith('---'), `copilot/onboard: should start with ---`).toBe(true); | ||
| expect(content).toContain("applyTo: '**'"); | ||
| } else if (target === 'gemini') { | ||
| // GEMINI.md is plain Markdown -- no frontmatter. | ||
| expect(content.startsWith('---'), `gemini/onboard: must NOT start with ---`).toBe(false); | ||
| } | ||
|
|
||
| // Load-bearing onboard string: the skill body must reference setup | ||
|
|
@@ -264,8 +271,38 @@ describe('content integrity', () => { | |
| // (d) No frontmatter fence — AGENTS.md is plain prose | ||
| expect(content.startsWith('---'), 'codex: must NOT start with ---').toBe(false); | ||
| }); | ||
| }); | ||
|
|
||
| it('gemini GEMINI.md contains ONE managed section with verify and onboard content', () => { | ||
| const tmpDir = freshTmpDir(); | ||
| runCli(['agent', 'install', '--target=gemini', '--dir', tmpDir, '--output', 'json']); | ||
|
|
||
| const filePath = join(tmpDir, TARGETS.gemini.path); | ||
| const content = readFileSync(filePath, 'utf8'); | ||
|
Comment on lines
+275
to
+280
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Assert the CLI exit code and JSON output. The As per path instructions, tests must cover exit-code paths and preserve machine-readable JSON output discipline. 🤖 Prompt for AI AgentsSource: Path instructions |
||
|
|
||
| // (a) Exactly ONE pair of sentinels | ||
| const escRe = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | ||
| const beginCount = (content.match(new RegExp(escRe(MANAGED_SECTION_BEGIN), 'g')) ?? []).length; | ||
| const endCount = (content.match(new RegExp(escRe(MANAGED_SECTION_END), 'g')) ?? []).length; | ||
| expect(beginCount, 'exactly one BEGIN sentinel').toBe(1); | ||
| expect(endCount, 'exactly one END sentinel').toBe(1); | ||
|
|
||
| // BEGIN must come before END | ||
| expect(content.indexOf(MANAGED_SECTION_BEGIN)).toBeLessThan( | ||
| content.indexOf(MANAGED_SECTION_END), | ||
| ); | ||
|
|
||
| // (b) Load-bearing command strings from the compact verify body | ||
| expect(content).toContain('testsprite test run'); | ||
| expect(content).toContain('--wait'); | ||
| expect(content).toContain('test artifact get'); | ||
|
|
||
| // (c) Onboard one-liner must be present | ||
| expect(content).toContain('First-time setup'); | ||
|
|
||
| // (d) No frontmatter fence -- GEMINI.md is plain prose | ||
| expect(content.startsWith('---'), 'gemini: must NOT start with ---').toBe(false); | ||
| }); | ||
|
Comment on lines
+275
to
+304
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Cover managed-section preservation and reruns. This test starts from an empty As per path instructions, tests must cover the new behavior deterministically and offline. 🧰 Tools🪛 ast-grep (0.44.1)[warning] 283-283: Do not use variable for regular expressions (regexp-non-literal-typescript) [warning] 284-284: Do not use variable for regular expressions (regexp-non-literal-typescript) 🤖 Prompt for AI AgentsSource: Path instructions |
||
| }); | ||
| // --------------------------------------------------------------------------- | ||
| // 3. Idempotent re-run | ||
| // --------------------------------------------------------------------------- | ||
|
|
@@ -811,7 +848,7 @@ describe('agent list', () => { | |
| }>; | ||
| expect(Array.isArray(parsed)).toBe(true); | ||
|
|
||
| // Expected: 8 targets × 2 skills = 16 rows | ||
| // Expected: targets x 2 skills per target = total rows (computed dynamically) | ||
| const expectedCount = Object.keys(TARGETS).length * DEFAULT_SKILLS.length; | ||
| expect(parsed.length).toBe(expectedCount); | ||
|
|
||
|
|
@@ -848,6 +885,7 @@ describe('matrix coverage guard', () => { | |
| 'kiro', | ||
| 'windsurf', | ||
| 'copilot', | ||
| 'gemini', | ||
| 'codex', | ||
| ]); | ||
| }); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Document Gemini in the managed-section
--forcebehavior.The target help includes
gemini, but the existing--forcedescription still says onlycodexis managed-section. Update it to mention both targets or all managed-section targets, while retaining the no-clobber guarantee.As per path instructions, managed-section targets’ user content outside the sentinels must remain explicitly protected.
🤖 Prompt for AI Agents
Source: Path instructions