Skip to content

feat(gemini): add Gemini CLI as supported target with integration tests#917

Merged
danielmeppiel merged 11 commits intomicrosoft:mainfrom
stbenjam:feat/gemini-cli
Apr 25, 2026
Merged

feat(gemini): add Gemini CLI as supported target with integration tests#917
danielmeppiel merged 11 commits intomicrosoft:mainfrom
stbenjam:feat/gemini-cli

Conversation

@stbenjam
Copy link
Copy Markdown
Contributor

@stbenjam stbenjam commented Apr 24, 2026

Description

Add full Gemini CLI support including target profile, runtime setup scripts, golden scenario E2E test, and offline integration tests verifying command/instruction/skill/MCP deployment to .gemini/.

I have tested this locally with a variety of packages including microsoft/apm-sample-package and various Claude Code-formatted plugins, and it works pretty well. I've also added integration "golden" tests and they pass against both Vertex AI and my personal Gemini accounts.

Fixes # (issue)

N/A

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

@stbenjam
Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree company="Red Hat"

@stbenjam
Copy link
Copy Markdown
Contributor Author

stbenjam commented Apr 24, 2026

@danielmeppiel @sergio-sisternes-epam Hey folks, this is a great project. <3 One barrier for me currently is the lack of Gemini CLI support.

Because it's a significant addition and I see several other active PRs that might conflict, I wanted to check in first if this a feature you’re interested in?

If so, I’m happy to manage the rebasing and keep it up-to-date against the main branch so it’s an easy lift when you get to it. I just didn't want to spin my wheels if you won't have time to get to this.

@danielmeppiel
Copy link
Copy Markdown
Collaborator

Let's get Gemini CLI supported!

@danielmeppiel danielmeppiel added the panel-review Trigger the apm-review-panel gh-aw workflow label Apr 24, 2026
@github-actions
Copy link
Copy Markdown

APM Review Panel Verdict

Disposition: REQUEST_CHANGES (four mechanical pre-merge fixes; architecture is approved)


Per-persona findings

Python Architect: The PR is a clean, additive extension of the data-driven TargetProfile / PrimitiveMapping system. Every new path follows established patterns — GeminiClientAdapter inheriting CopilotClientAdapter is correct (identical mcpServers JSON schema), the KNOWN_TARGETS["gemini"] entry drives all integration routing automatically, and the _write_gemini_command / copy_instruction_gemini static methods follow the Template Method pattern already used for cursor and claude variants. The AgentsCompiler if not results -> success=True fix is correct: data-driven targets (gemini, cursor) legitimately produce no compilation output.

Class diagram (integration subsystem, touched classes in bold):

classDiagram
    direction TB

    class CopilotClientAdapter {
        +get_config_path() str
        +update_config(updates)
        +configure_mcp_server(url, name, ...) bool
        +_format_server_config(info, env, vars) dict
    }

    class GeminiClientAdapter {
        <<ConcreteStrategy>>
        +supports_user_scope bool
        +get_config_path() str
        +update_config(updates)
        +get_current_config() dict
        +configure_mcp_server(url, name, ...) bool
    }
    note for GeminiClientAdapter "Reuses mcpServers schema;\nopt-in: writes only if .gemini/ exists"

    class ClientFactory {
        <<StaticFactory>>
        +create_client(target) adapter
        -_client_map dict
    }

    class BaseIntegrator {
        <<Abstract>>
        +check_collision(target, rel, managed, force)
        +find_files_by_glob(path, pattern)
        +resolve_links(content, src, tgt)
        +sync_remove_files(root, managed, ...)
    }

    class CommandIntegrator {
        <<TemplateMethod>>
        +integrate_commands_for_target(info, root, target, force, managed)
        +_write_gemini_command(src, tgt)$
    }

    class InstructionIntegrator {
        <<TemplateMethod>>
        +integrate_instructions_for_target(...)
        +copy_instruction_gemini(src, tgt) int
        +_convert_to_gemini_rules(content) str$
    }

    class MCPIntegrator {
        +remove_stale_mcp_servers(stale, runtime, logger)
        +_detect_runtimes(scripts) set
    }

    class TargetProfile {
        <<ValueObject>>
        +name str
        +root_dir str
        +primitives dict
        +auto_create bool
        +detect_by_dir bool
        +user_supported bool
    }

    CopilotClientAdapter <|-- GeminiClientAdapter : extends
    ClientFactory ..> GeminiClientAdapter : creates
    BaseIntegrator <|-- CommandIntegrator : extends
    BaseIntegrator <|-- InstructionIntegrator : extends
    CommandIntegrator ..> TargetProfile : reads primitives
    InstructionIntegrator ..> TargetProfile : reads primitives
    MCPIntegrator ..> ClientFactory : delegates
Loading

Execution flow diagram (install path for a gemini target):

flowchart TD
    A[apm install] --> B[detect_target / active_targets]
    B --> C{.gemini/ exists?}
    C -- yes --> D[KNOWN_TARGETS gemini TargetProfile]
    C -- no --> E[other targets / copilot fallback]
    D --> F[install/phases/targets.py run]
    F --> G[resolve_targets -> TargetProfile gemini]
    G --> H[dispatch.get_dispatch_table]
    H --> I{primitive type}
    I -- commands --> J[CommandIntegrator.integrate_commands_for_target]
    J --> K{format_id == gemini_command?}
    K -- yes --> L[_write_gemini_command: .prompt.md -> .toml]
    K -- no --> M[integrate_command standard path]
    I -- instructions --> N[InstructionIntegrator.integrate_instructions_for_target]
    N --> O[copy_instruction_gemini: strip frontmatter, .md]
    I -- hooks --> P[HookIntegrator: merge into settings.json]
    I -- MCP --> Q[MCPIntegrator -> GeminiClientAdapter.configure_mcp_server]
    Q --> R{.gemini/ dir exists?}
    R -- no --> S[return True silently - opt-in]
    R -- yes --> T[write .gemini/settings.json mcpServers]
Loading

Design patterns: Inheritance / Template Method (GeminiClientAdapter extends CopilotClientAdapter; copy_instruction_gemini mirrors copy_instruction_cursor / copy_instruction_claude); Data-Driven Configuration (KNOWN_TARGETS dict as single source of truth for all routing); Static Factory (ClientFactory._client_map dispatch dict).

Single issue (required): GeminiClientAdapter.configure_mcp_server uses 4 bare print() calls for error and success output, bypassing _rich_error() / _rich_success() / _rich_info(). Violates the APM output architecture contract. File: src/apm_cli/adapters/client/gemini.py, lines ~95, ~110, ~115, ~120.


CLI Logging Expert: Two output hygiene issues.

  1. (Required) GeminiClientAdapter.configure_mcp_server — 4 bare print() calls ("Error: server_url cannot be empty", "Error: MCP server '...' not found in registry", "Successfully configured MCP server '...' for Gemini CLI", "Error configuring MCP server: {e}"). These must become _rich_error() and _rich_success() / _rich_info() calls from apm_cli.utils.console. The exception path also leaks str(e) verbatim — sanitize before displaying.

  2. (Optional) mcp_integrator.py stale-cleanup fallback path: _rich_info(f"+ Removed stale MCP server '{name}' from .gemini/settings.json"). The manual + prefix produces the rendered string [i] + Removed ... — the + symbol is semantically "success / confirmed" per STATUS_SYMBOLS, so either use _rich_success() or remove the manual prefix. Minor but inconsistent.

All other output paths (script_runner.py runtime detection, hook_integrator, instruction_integrator) are clean and follow the pattern correctly.


DevX UX Expert: The new apm runtime setup gemini / apm runtime remove gemini choices are idiomatic and consistent with the existing copilot/codex/llm surface. Opt-in detection via .gemini/ presence mirrors the OpenCode pattern, which is familiar.

Three gaps to close before merge:

  1. (Required) docs/src/content/docs/reference/cli-commands.md is not updated. Current text still reads apm runtime setup [OPTIONS] {copilot|codex|llm} (line 1641) and apm runtime remove [OPTIONS] {copilot|codex|llm} (line 1696). The --target flag enumerations throughout (lines 90, 558, 959, 1351, 1371) also omit gemini. The detection table at line 1371 is missing the .gemini/ row.

  2. (Required per Rule 4) packages/apm-guide/.apm/skills/apm-usage/commands.md has no mention of gemini. CLI command changes require updating this shipped skill resource in the same PR.

  3. (Optional) get_target_description("gemini") in target_detection.py returns ".gemini/commands/ + .gemini/rules/ + .gemini/skills/" but omits .gemini/settings.json (MCP + hooks). Minor accuracy gap.


Supply Chain Security Expert: No significant concerns.

  • All new file paths are hardcoded constants (not constructed from user input at the adapter level), so path traversal is not a concern for get_config_path() or the MCP cleanup code.
  • toml>=0.10.2 is already declared in pyproject.toml — the lazy import toml in _write_gemini_command does not introduce a new dependency.
  • shutil.which("gemini") runtime detection is standard and safe.
  • The npm package name @google/gemini-cli is hardcoded correctly in the uninstall path.
  • One minor finding: configure_mcp_server exception handler prints str(e) verbatim, which could expose internal path or registry details. Use a sanitized message format consistent with _rich_error().

Auth Expert: Not activated -- PR touches only file integration and runtime paths (adapters/client/gemini.py, integration/targets.py, integrators, script_runner.py, runtime/manager.py); no auth flow, token management, host classification, or credential resolution is modified. GeminiClientAdapter.configure_mcp_server delegates to the inherited self.registry_client unchanged.


OSS Growth Hacker: Strong strategic addition. Gemini CLI is Google-backed, MCP-native, and growing fast — expanding APM's "one manifest, every AI tool" narrative to include a Google product materially broadens the addressable audience and the cross-tool credibility story. The README hero line update (GitHub Copilot * Claude Code * Cursor * OpenCode * Codex * Gemini CLI) is correct and conversion-positive.

One required fix: CHANGELOG.md has no entry for this feature. Adding a 6th first-class runtime target is a significant Added entry. Suggested text:

- Added Google Gemini CLI as a first-class integration target: \apm install` deploys instructions to `.gemini/rules/`, commands to `.gemini/commands/` (TOML format), and MCP servers to `.gemini/settings.json`; `apm runtime setup gemini` installs `@google/gemini-cli` via npm (#917)`

Side-channel note to CEO: consider leading the next release post with the Google Gemini angle — "APM now manages five AI coding tools natively including Google Gemini CLI" is a stronger headline than any individual feature. Updated WIP/growth-strategy.md (maintainer-local) to log this as a multi-runtime parity milestone.


CEO arbitration

All five mandatory specialists agreed on the disposition and the blocking issues — no arbitration of disagreements needed. This PR is architecturally sound: the data-driven TargetProfile pattern absorbs the new target cleanly, the inheritance hierarchy is appropriate, and the opt-in presence-detection model is consistent with the OpenCode precedent. The four required actions below are mechanical fixes, not design problems, and none requires structural rethinking. Once addressed, this ships cleanly. The strategic read: Gemini CLI support is overdue and the timing — Google's MCP investment is accelerating — is right. Ratified as APPROVE pending the four fixes.


Required actions before merge

  1. Fix bare print() in GeminiClientAdapter.configure_mcp_server (src/apm_cli/adapters/client/gemini.py): replace all 4 print(...) calls with _rich_error() / _rich_success() / _rich_info() from apm_cli.utils.console. Sanitize the exception message so str(e) internals are not exposed verbatim.

  2. Add CHANGELOG.md entry under ## [Unreleased] > Added for the Gemini CLI integration (see suggested text in OSS Growth Hacker section above).

  3. Update docs/src/content/docs/reference/cli-commands.md: add gemini to apm runtime setup / apm runtime remove command synopses (lines 1641, 1696), add gemini to every --target flag enumeration (lines 90, 558, 959, 1351), and add a .gemini/ row to the target-detection table (line 1371).

  4. Update packages/apm-guide/.apm/skills/apm-usage/commands.md: add gemini to the apm runtime setup and apm runtime remove choice lists per Rule 4 (CLI command changes must sync this shipped skill resource in the same PR).


Optional follow-ups

  • get_target_description("gemini") in target_detection.py omits .gemini/settings.json from the description string — add + .gemini/settings.json (MCP/hooks) for accuracy in --help and diagnostic output.
  • should_integrate_gemini() in target_detection.py is defined but has no callers in src/ (same as all other should_integrate_*() siblings). Consider removing the whole family of unused functions or adding a tracking issue — dead utility code that accumulates per-target creates maintenance drift.
  • The _rich_info(f"+ Removed stale MCP server...") in mcp_integrator.py cleanup path: swap manual + prefix for _rich_success() so the symbol aligns with the "confirmed" semantic.
  • Docs page for ide-tool-integration.md and making-the-case.md enterprise scenarios now reference Gemini — verify enterprise docs also reflect the expanded five-tool story (quick read, not a blocker).

Generated by PR Review Panel for issue #917 · ● 2.2M ·

Copy link
Copy Markdown
Collaborator

@danielmeppiel danielmeppiel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix all required as well as the "optional" consistency issues. They are all required. Excited about this one!

stbenjam and others added 4 commits April 25, 2026 06:53
Add full Gemini CLI support including target profile, runtime setup
scripts, golden scenario E2E test, and offline integration tests
verifying command/instruction/skill/MCP deployment to .gemini/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e docs

Replace 4 bare print() calls in GeminiClientAdapter.configure_mcp_server
with _rich_error/_rich_success from apm_cli.utils.console. Sanitize the
exception handler so str(e) internals are not exposed to users. Add gemini
to --target enumerations, runtime setup/remove synopses, and target-detection
table in cli-commands.md and the shipped apm-usage skill resource. Add
CHANGELOG entry under [Unreleased] > Added.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ccess for stale cleanup

Add "+ .gemini/settings.json (MCP/hooks)" to get_target_description()
so --help and diagnostic output accurately reflect Gemini's config path.

Swap _rich_info() → _rich_success(symbol="check") for all 6 "Removed
stale MCP server" messages in mcp_integrator.py cleanup paths — removal
is a confirmed action, not informational.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Six should_integrate_{vscode,claude,opencode,cursor,codex,gemini}()
helpers were defined and tested but never called in production code.
Each target's integration decision is handled inline by the integrators
and compile pipeline. Remove the functions and their test classes to
prevent per-target maintenance drift.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@stbenjam
Copy link
Copy Markdown
Contributor Author

Got it, thanks!

get_target_description("gemini") in target_detection.py omits .gemini/settings.json from the description string — add + .gemini/settings.json (MCP/hooks) for accuracy in --help and diagnostic output.

Done

should_integrate_gemini() in target_detection.py is defined but has no callers in src/ (same as all other should_integrate_*() siblings). Consider removing the whole family of unused functions or adding a tracking issue — dead utility code that accumulates per-target creates maintenance drift.

Is this OK to remove? It is not called in the main code base, but it does have test coverage and is part of the API surface. I did add a commit that removes this dead code if you do want me to deal with it here.

The _rich_info(f"+ Removed stale MCP server...") in mcp_integrator.py cleanup path: swap manual + prefix for _rich_success() so the symbol aligns with the "confirmed" semantic.

This was pre-existing pattern for all assistants, but I have updated everyone to use _rich_success for this message

Docs page for ide-tool-integration.md and making-the-case.md enterprise scenarios now reference Gemini — verify enterprise docs also reflect the expanded five-tool story (quick read, not a blocker).

This was already done, unless I'm misunderstanding what the review panel asked for

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Gemini CLI as a first-class APM target, expanding the integration layer to deploy primitives into .gemini/, wire MCP servers, and provide runtime setup plus new unit/integration/E2E coverage.

Changes:

  • Add a new gemini target profile and runtime, including setup scripts and target auto-detection.
  • Implement Gemini-specific integration behavior for commands (.prompt.md -> .toml), instructions (frontmatter stripping), hooks, and MCP configuration.
  • Add extensive unit + offline integration tests, plus an E2E golden scenario for Gemini.
Show a summary per file
File Description
tests/unit/test_runtime_manager.py Updates runtime list expectations to include gemini.
tests/unit/test_global_mcp_scope.py Adjusts MCP scope-filtering test to account for new registry ops usage/mocking.
tests/unit/test_gemini_mcp.py New unit tests for GeminiClientAdapter and factory registration.
tests/unit/integration/test_targets.py Adds .gemini/ target detection tests.
tests/unit/integration/test_instruction_integrator.py Adds Gemini rules conversion + integration tests (frontmatter stripped, rename behavior).
tests/unit/integration/test_data_driven_dispatch.py Extends dispatch bucket expectations for Gemini commands/instructions.
tests/unit/integration/test_command_integrator.py Adds Gemini command integration tests and direct TOML writer unit tests.
tests/unit/core/test_target_detection.py Updates target detection expectations and removes tests for deleted helpers.
tests/unit/core/test_scope.py Updates known-target set to include gemini.
tests/integration/test_golden_scenario_e2e.py Adds Gemini golden-scenario E2E flow and HOME/ADC credential handling.
tests/integration/test_gemini_integration.py New offline integration test suite covering .gemini/ deploy behaviors.
src/apm_cli/runtime/manager.py Adds Gemini runtime metadata + npm uninstall support + preference ordering.
src/apm_cli/integration/targets.py Registers gemini as a TargetProfile with primitive mappings under .gemini/.
src/apm_cli/integration/mcp_integrator.py Adds Gemini to runtime detection/filtering and stale MCP cleanup for .gemini/settings.json.
src/apm_cli/integration/instruction_integrator.py Adds gemini_rules transform (strip frontmatter) + sync legacy handling.
src/apm_cli/integration/hook_integrator.py Adds Gemini hook merge config targeting settings.json.
src/apm_cli/integration/command_integrator.py Adds Gemini command transform path (.prompt.md -> .toml).
src/apm_cli/factory.py Registers GeminiClientAdapter in ClientFactory.
src/apm_cli/core/target_detection.py Adds .gemini/ auto-detection and includes gemini in canonical targets/descriptions.
src/apm_cli/core/script_runner.py Adds Gemini runtime command detection/transform + execution path (-p content).
src/apm_cli/compilation/agents_compiler.py Adjusts compile routing behavior to treat some targets as “no compilation needed”.
src/apm_cli/commands/runtime.py Extends runtime CLI choices to include gemini.
src/apm_cli/adapters/client/gemini.py New MCP adapter writing Gemini settings and configuring MCP servers.
scripts/runtime/setup-gemini.sh New Unix setup script to install @google/gemini-cli and initialize settings.
scripts/runtime/setup-gemini.ps1 New Windows setup script to install @google/gemini-cli and initialize settings.
packages/apm-guide/.apm/skills/apm-usage/commands.md Updates guide docs for runtime setup/remove to include Gemini.
docs/src/content/docs/reference/cli-commands.md Updates CLI reference to include Gemini target/runtime and related option lists.
docs/src/content/docs/introduction/why-apm.md Updates narrative to include Gemini in the “native integration” set.
docs/src/content/docs/integrations/ide-tool-integration.md Updates integration docs to reflect Gemini’s native per-file support and hooks coverage.
docs/src/content/docs/index.mdx Updates landing page copy to include Gemini in supported tool list.
docs/src/content/docs/guides/mcp-servers.md Updates MCP guide to include Gemini in supported clients list.
docs/src/content/docs/guides/compilation.md Updates compilation guidance/table to reflect full Gemini integration.
docs/src/content/docs/getting-started/quick-start.md Updates quick start narrative to include .gemini/ and Gemini in client list.
docs/src/content/docs/enterprise/making-the-case.md Updates enterprise framing/examples to include Gemini as an additional tool.
README.md Updates README supported-tools list and related copy to include Gemini.
CHANGELOG.md Adds an Unreleased entry announcing Gemini target + runtime support.

Copilot's findings

Comments suppressed due to low confidence (4)

src/apm_cli/adapters/client/gemini.py:60

  • GeminiClientAdapter sets supports_user_scope = True, but get_config_path() and related logic write to Path(os.getcwd())/.gemini/settings.json. That means a user-scope (--global) MCP install for Gemini would write to the current directory rather than ~/.gemini/settings.json (which the runtime setup script initializes). Consider either (1) making the adapter write to Path.home()/.gemini/settings.json when used in user scope, or (2) setting supports_user_scope = False until user-scope behavior is correctly implemented.
    supports_user_scope: bool = True

    def get_config_path(self):
        """Return the path to ``.gemini/settings.json`` in the repository root."""
        return str(Path(os.getcwd()) / ".gemini" / "settings.json")

    def update_config(self, config_updates):
        """Merge *config_updates* into the ``mcpServers`` section of settings.json.

        The ``.gemini/`` directory must already exist; if it does not, this
        method returns silently (opt-in behaviour).

        Preserves all other top-level keys in settings.json (theme, tools,
        hooks, etc.).
        """
        gemini_dir = Path(os.getcwd()) / ".gemini"
        if not gemini_dir.is_dir():
            return

src/apm_cli/compilation/agents_compiler.py:251

  • Returning success with an empty CompilationResult when no compilers ran makes apm compile -t gemini / -t cursor silently no-op (and can also hide accidental target typos that still pass _KNOWN_TARGETS). Consider either rejecting targets that don't produce compilation output, or emitting an explicit warning/summary indicating that compilation is not applicable for that target.
            # Some targets (e.g. gemini, cursor) use the data-driven
            # integration layer and don't need AGENTS.md/CLAUDE.md compilation.
            if not results:
                return CompilationResult(
                    success=True,
                    output_path="",
                    content="",
                    warnings=self.warnings.copy(),
                    errors=self.errors.copy(),
                    stats={},
                )

src/apm_cli/adapters/client/gemini.py:31

  • _rich_info is imported but never used in this module. Please drop the unused import to keep linting clean.
from .copilot import CopilotClientAdapter
from ...utils.console import _rich_error, _rich_info, _rich_success

src/apm_cli/adapters/client/gemini.py:131

  • _rich_success(..., symbol="success") maps to the "success" entry in STATUS_SYMBOLS, which currently renders as "[*]" (not the repo's [+] success/confirmed symbol). For a confirmed successful configuration, use the "check" symbol (or omit the symbol) so the output matches the documented status-symbol convention.
            _rich_success(
                f"Configured MCP server '{config_key}' for Gemini CLI", symbol="success"
            )
  • Files reviewed: 36/36 changed files
  • Comments generated: 5

Comment thread README.md
Comment thread src/apm_cli/adapters/client/gemini.py Outdated
Comment thread CHANGELOG.md Outdated
Comment thread docs/src/content/docs/reference/cli-commands.md Outdated
Comment thread tests/integration/test_golden_scenario_e2e.py Outdated
stbenjam and others added 5 commits April 25, 2026 07:20
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GEMINI.md now generates a thin stub containing `@./AGENTS.md` which
leverages Gemini CLI's native import preprocessor instead of
duplicating the full instruction roll-up. This ensures AGENTS.md is
always compiled alongside GEMINI.md via should_compile_agents_md().

Also fixes doc consistency: adds .gemini/ to CI drift checks, commit
guides, and auto-detection lists; removes inaccurate "no compile step"
claims for Gemini; clarifies compile vs install behavior; removes
broken test referencing deleted .gemini/rules/ support; and cleans up
an unused import.

Reviewed-by: APM Review Panel (Python Architect, CLI Logging Expert,
  DevX UX Expert, Supply Chain Security Expert, OSS Growth Hacker,
  CEO Arbiter)
Reviewed-by: CodeRabbit (coderabbit review --agent)
Reviewed-by: Claude Opus 4.7 (standalone code reviewer)
Reviewed-by: Gemini 2.5 Pro (gemini-cli --sandbox)
Reviewed-by: Technical Writer (documentation consistency reviewer)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@danielmeppiel
Copy link
Copy Markdown
Collaborator

New target mandatory test requirement before merge:

report back with a proof of integration test ran locally across all APM commands and functionalities touching the target, including:

  • install for all package types, including transitive dep
  • install for all primitive types, with a clear reference of primitives supported by the target, link to live documentation URL (e.g. instructions, subagents, skills, hooks, MCP)
  • marketplace additions
  • uninstall for all previous combinations
  • compile for AGENTS.md
  • user and global scopes for all combinations above

The test results are to be passed to the review panel for approval and posted here as a comment that shows a table of all cases tested above and the results

The integration tests in the repo should be extended to cover the above with Gemini (it is not guaranteed that current tests do cover all cases above)

- Reimplement _format_server_config for Gemini (no type/tools/id fields,
  httpUrl for HTTP remotes, url for SSE)
- Map hook event names per target (preToolUse->BeforeTool, Stop->SessionEnd)
- Transform flat Copilot hook entries to nested Gemini format
  (bash->command, timeoutSec->timeout in ms, hooks[] nesting)
- Add unit tests for MCP config format and hook transformations
- Fix hallucinated documentation links in test report

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@stbenjam
Copy link
Copy Markdown
Contributor Author

Gemini CLI Integration Test Report

All tests ran locally against apm 0.10.0 (a2599dd) on feat/gemini-cli.

Primitives supported by Gemini target

Primitive Format Deploy path Scope Doc reference
Commands .toml .gemini/commands/ project + user Custom commands
Skills SKILL.md .gemini/skills/ project + user Agent Skills
Hooks .json -> settings.json .gemini/settings.json project + user Hooks reference
MCP settings.json mcpServers .gemini/settings.json project + user MCP servers
Instructions compile-only (GEMINI.md -> @./AGENTS.md) root GEMINI.md project GEMINI.md

Manual test matrix

# Test Result
1 apm init + .gemini/ opt-in PASS
2 apm install direct dep (microsoft/apm-sample-package#v1.0.0) PASS -- 2 commands (.toml), 1 skill (SKILL.md)
3 Transitive dep resolution (review-and-refactor) PASS -- skill deployed to .gemini/skills/
4 apm install local package with hooks PASS -- hooks merged into .gemini/settings.json, preToolUse mapped to BeforeTool
5 apm install --mcp with --transport stdio PASS -- mcpServers written with Gemini-valid schema (no type/tools/id)
6 Marketplace add + install (frontend-web-dev@awesome-copilot) PASS -- 2 playwright skills deployed
7 apm uninstall marketplace plugin PASS -- 2 skills cleaned
8 apm uninstall hook package PASS -- hooks removed from settings.json, MCP preserved
9 apm uninstall sample package (with transitive dep) PASS -- commands, skills, transitive dep all cleaned
10 apm compile --target gemini PASS -- GEMINI.md = @./AGENTS.md stub, AGENTS.md compiled alongside
11 apm compile --target all PASS -- GEMINI.md + AGENTS.md + CLAUDE.md all generated
12 apm install --global (user scope) PASS -- 48 commands + 49 skills to ~/.gemini/
13 apm uninstall --global (user scope) PASS -- Gemini files cleaned from ~/.gemini/

Automated test coverage

5488 unit tests pass (0 failures).

Scenario Automated test location
Command deploy (.toml format) tests/integration/test_gemini_integration.py::TestGeminiCommandIntegration (3 tests)
Skill deploy (SKILL.md verbatim) tests/integration/test_gemini_integration.py::TestGeminiSkillIntegration (1 test)
MCP config merge (settings.json) tests/integration/test_gemini_integration.py::TestGeminiMCPIntegration (2 tests)
Opt-in behavior (no .gemini/ = no deploy) tests/integration/test_gemini_integration.py::TestGeminiOptInBehavior (3 tests)
Multi-target coexistence (.github/ + .gemini/) tests/integration/test_gemini_integration.py::TestGeminiMultiTargetCoexistence (1 test)
Hook integration (settings.json merge) tests/integration/test_gemini_integration.py::TestGeminiHookIntegration (4 tests)
Uninstall cleanup (commands + skills) tests/integration/test_gemini_integration.py::TestGeminiUninstallCleanup (3 tests)
Hook unit tests (merge, idempotent, sync, event/structure mapping) tests/unit/integration/test_hook_integrator.py::TestGeminiHookIntegration (10 tests)
Hook event name mapping (preToolUse->BeforeTool) tests/unit/integration/test_hook_integrator.py::TestGeminiHookIntegration::test_event_name_mapping_* (2 tests)
Hook structure transform (flat Copilot->nested Gemini) tests/unit/integration/test_hook_integrator.py::TestGeminiHookIntegration::test_flat_copilot_entries_become_nested_gemini (1 test)
MCP config format (no type/tools/id, httpUrl/url) tests/unit/test_gemini_mcp.py::TestGeminiFormatServerConfig (4 tests)
MCP adapter (config path, merge, opt-in, configure) tests/unit/test_gemini_mcp.py::TestGeminiConfigureMCPServer (7 tests)
GeminiFormatter (stub, AGENTS.md import, header) tests/unit/compilation/test_gemini_formatter.py (7 tests)
Global scope (user-scope dirs, output, uninstall) tests/integration/test_global_scope_e2e.py::TestGlobalGeminiScope (3 tests)

Summary

13/13 manual tests pass. All primitive types (commands, skills, hooks, MCP, instructions/compile), all package types (direct, transitive, local, marketplace), both scopes (project, user), and install/uninstall/compile all work correctly for the Gemini target.

47 automated Gemini tests across 6 test files cover commands, skills, MCP config format, hooks (event name mapping + structure transformation), opt-in, multi-target, uninstall cleanup, compilation, and global scope.

@danielmeppiel danielmeppiel added panel-review Trigger the apm-review-panel gh-aw workflow and removed panel-review Trigger the apm-review-panel gh-aw workflow labels Apr 25, 2026
@github-actions
Copy link
Copy Markdown

APM Review Panel Verdict

Disposition: REQUEST_CHANGES (one blocking bug: GitHub MCP server auth raises ValueError for Gemini target; all other findings are minor)


Per-persona findings

Python Architect:

The PR adds GeminiClientAdapter < CopilotClientAdapter and a standalone GeminiFormatter with two new dataclasses. Hook dispatch is data-driven through _MERGE_HOOK_TARGETS; compilation routing through should_compile_gemini_md(). The shape is consistent with every other target added to APM.

OO / class diagram

classDiagram
    direction TB
    class CopilotClientAdapter {
        <<Adapter>>
        +_format_server_config(server_info) dict
        +_select_best_package(packages) dict
        +_is_github_server(name, url) bool
        +_resolve_env_variable(name, value, overrides) str
    }
    class GeminiClientAdapter {
        <<Adapter>>
        +supports_user_scope bool
        +get_config_path() str
        +get_current_config() dict
        +update_config(config_updates) None
        +configure_mcp_server(server_url, ...) bool
        +_format_server_config(server_info) dict
    }
    class ClientFactory {
        <<Factory>>
        +_registry dict
        +create_client(name) BaseAdapter
    }
    class GeminiFormatter {
        <<Formatter>>
        +base_dir Path
        +format_distributed(primitives) GeminiCompilationResult
        +_generate_stub() str
    }
    class GeminiCompilationResult {
        <<ValueObject>>
        +success bool
        +placements List
        +content_map Dict
        +warnings List
        +errors List
        +stats Dict
    }
    class GeminiPlacement {
        <<ValueObject>>
        +gemini_path Path
        +instructions List
        +coverage_patterns Set
        +source_attribution Dict
    }
    class AgentsCompiler {
        +_compile_gemini_md(config, primitives) CompilationResult
    }
    class HookIntegrator {
        +integrate_hooks_for_target(target, ...) HookIntegrationResult
    }
    note for GeminiClientAdapter "Overrides _format_server_config:\nno type/tools/id; httpUrl vs url;\nBearer token via GitHubTokenManager"
    note for GeminiFormatter "Template Method: _generate_stub()\nproduces thin @./AGENTS.md import stub"
    CopilotClientAdapter <|-- GeminiClientAdapter : extends
    ClientFactory ..> GeminiClientAdapter : creates
    AgentsCompiler ..> GeminiFormatter : uses
    GeminiFormatter ..> GeminiCompilationResult : returns
    GeminiCompilationResult *-- GeminiPlacement : contains
    class GeminiClientAdapter:::touched
    class GeminiFormatter:::touched
    class GeminiCompilationResult:::touched
    class GeminiPlacement:::touched
    class AgentsCompiler:::touched
    classDef touched fill:#fff3b0,stroke:#d47600
Loading

Execution flow diagram

flowchart TD
    A["apm install --target gemini"] --> B["detect_target() -> 'gemini'"]
    B --> C{"has .gemini/ dir?"}
    C -- No --> D["[FS] skip Gemini integration"]
    C -- Yes --> E["CommandIntegrator: .prompt.md -> .gemini/commands/*.toml"]
    E --> F["_write_gemini_command(source, target)\n[FS] toml.dumps + target.write_text"]
    C -- Yes --> G["HookIntegrator: _MERGE_HOOK_TARGETS['gemini']"]
    G --> H["_integrate_merged_hooks()\n_HOOK_EVENT_MAP renaming\nPreToolUse->BeforeTool, Stop->SessionEnd"]
    H --> I["_to_gemini_hook_entries()\nbash->command, timeoutSec->timeout(ms)\n[FS] .gemini/settings.json updated"]
    C -- Yes --> J["MCPIntegrator: configure_mcp_server()\n[NET] registry lookup"]
    J --> K{"_is_github_server?"}
    K -- Yes --> L["GitHubTokenManager().get_token_for_purpose('gemini')\n[BUG] raises ValueError - not in TOKEN_PRECEDENCE"]
    L --> M["Exception caught -> _rich_error 'Failed to configure MCP server'"]
    K -- No --> N["_format_server_config: command/url/httpUrl transport\n[FS] .gemini/settings.json updated"]
    A --> O["apm compile --target gemini"]
    O --> P["should_compile_agents_md('gemini') -> True"]
    P --> Q["_compile_agents_md()\n[FS] AGENTS.md written"]
    O --> R["should_compile_gemini_md('gemini') -> True"]
    R --> S["GeminiFormatter._generate_stub()\n[FS] GEMINI.md written with @./AGENTS.md import"]
Loading

Design patterns

  • Used in this PR: Adapter (GeminiClientAdapter maps Gemini's config schema into APM's common adapter contract, overriding only _format_server_config + config path methods); Template Method (CopilotClientAdapter provides the _select_best_package / _resolve_env_variable / _is_github_server template, subclass overrides only the target-specific steps); Factory (ClientFactory.create_client("gemini") registration); Dataclass-as-value-object (GeminiPlacement, GeminiCompilationResult -- frozen would be better for thread safety, but consistent with the rest of the formatter layer).
  • Pragmatic suggestion: The GeminiPlacement.coverage_patterns and source_attribution fields are always set to empty set/dict and never populated. Remove them to keep the dataclass minimal, or document why they are reserved for future use.

CLI Logging Expert:

Output paths are clean throughout. The stale-MCP-cleanup changes from _rich_info("+ Removed stale...") to _rich_success("Removed stale...", symbol="check") in mcp_integrator.py are correct; the old + prefix was mimicking a success symbol inside an info-level call, which was the wrong routing.

One minor inconsistency: agents_compiler.py line 648 passes f"[+] Generated GEMINI.md (imports AGENTS.md)" to self._log("progress", ...). Other _log("progress", ...) call sites in that file do not embed a [+] symbol -- they pass the raw output string and let the logger add its own framing. The embedded symbol will double-stack if the logger adds its own action symbol. Non-blocking, but should be "Generated GEMINI.md (imports AGENTS.md)" (no f-prefix, no [+]).

All output strings are printable ASCII. No bare print() calls introduced. _rich_error / _rich_success always receive symbol=.


DevX UX Expert:

apm runtime setup gemini and apm runtime remove gemini follow the exact pattern of copilot and codex. --target gemini is consistent with all other targets. Opt-in detection (.gemini/ must exist) is the right UX default -- users who haven't set up Gemini CLI don't get unexpected writes.

One doc drift: in docs/src/content/docs/reference/cli-commands.md, the --runtime TEXT option under apm install still reads (copilot, codex, vscode) and does not mention gemini. Every other occurrence of the runtime list in the diff was updated. This should be fixed before merge.

The quick-start callout explaining that Gemini needs apm compile for instructions (GEMINI.md) while receiving commands/skills/hooks/MCP via apm install is accurate and clearly explained.


Supply Chain Security Expert:

No new path traversal surface. GeminiClientAdapter.update_config() constructs its path as Path(os.getcwd()) / ".gemini" / "settings.json" -- no user-controlled segment, no traversal risk. _write_gemini_command receives target: Path from CommandIntegrator which routes through BaseIntegrator.validate_deploy_path().

No credentials are logged or included in error messages. The token injection bug noted by Auth Expert means GitHub MCP server auth silently fails rather than leaking a token -- the failure mode is safe from a credential-exfiltration perspective, though the silent failure is a correctness bug.

The _apm_source marker is correctly placed on the outer Gemini hook entry dict, so sync_integration / _clean_apm_entries_from_json will find and remove APM-managed hooks during uninstall. The hook cleanup path is correct.


Auth Expert:

BLOCKING BUG -- required action before merge.

src/apm_cli/adapters/client/gemini.py, line 151:

token = _tm.get_token_for_purpose("gemini") or os.getenv("GITHUB_PERSONAL_ACCESS_TOKEN")

GitHubTokenManager.get_token_for_purpose() raises ValueError: Unknown purpose: gemini when the purpose is not in TOKEN_PRECEDENCE (which contains only 'copilot', 'models', 'modules', 'ado_modules'). The ValueError is caught by the outer try/except Exception block in configure_mcp_server() and silently converted to _rich_error("Failed to configure MCP server for Gemini CLI"). No stack trace, no mention of the root cause. The user cannot diagnose this.

Impact: configuring any GitHub-hosted MCP server for the Gemini target silently fails. Non-GitHub MCP servers (npm, pypi, docker, homebrew) are unaffected (that code path is only hit for remote endpoints flagged by _is_github_server).

Fix -- change line 151 in gemini.py:

# Before (broken):
token = _tm.get_token_for_purpose("gemini") or os.getenv("GITHUB_PERSONAL_ACCESS_TOKEN")

# After (correct -- same GitHub token purpose as the Copilot adapter):
token = _tm.get_token_for_purpose("copilot") or os.getenv("GITHUB_PERSONAL_ACCESS_TOKEN")

This matches src/apm_cli/adapters/client/copilot.py line 227 exactly. Gemini CLI uses the same GitHub PAT as Copilot for GitHub MCP servers. A test for the GitHub-server auth path should be added to tests/unit/test_gemini_mcp.py.


OSS Growth Hacker:

Gemini CLI is Google's open-source AI assistant (MIT license, 250+ GitHub stars in its first weeks, Node 20+ install via npm). Adding it as an APM target positions APM as the cross-ecosystem package manager for AI development -- not tied to any single vendor. This is a strong differentiation story.

The opt-in .gemini/ detection is growth-friendly: it does not impose Gemini config on Copilot or Claude users. The apm runtime setup gemini command lowers the "try Gemini with APM" barrier to one command.

The CHANGELOG entry is well-scoped and searchable. The quick-start note distinguishing apm install (commands/hooks/MCP) from apm compile (GEMINI.md) reduces first-run confusion.

Side-channel to CEO: This is the right time to update why-apm.md and what-is-apm.md to reflect multi-ecosystem breadth (Copilot, Claude, Cursor, Codex, OpenCode, Gemini) as a first-class positioning point. The six-target coverage is now a competitive moat worth naming.


CEO arbitration

The Gemini CLI target is strategically sound and architecturally consistent. Every specialist agrees the implementation follows established APM patterns. The Auth Expert surfaced one blocking bug (get_token_for_purpose("gemini") raises ValueError) that must be fixed before merge -- it is a one-line change with high confidence, not a design question. The remaining findings (vestigial dataclass fields, embedded symbol in log call, --runtime doc drift) are minor and can be addressed in this PR or deferred to a follow-up at the author's discretion. I ratify REQUEST_CHANGES on the single blocking item; the PR is otherwise ready.


Required actions before merge

  1. Fix auth bug (src/apm_cli/adapters/client/gemini.py, line 151): change get_token_for_purpose("gemini") to get_token_for_purpose("copilot"). Then add a test to tests/unit/test_gemini_mcp.py that mocks _is_github_server to return True and asserts get_token_for_purpose is called with "copilot" (not "gemini") and that configure_mcp_server succeeds.
  2. Fix doc drift (docs/src/content/docs/reference/cli-commands.md, apm install --runtime TEXT description): add gemini to the list (copilot, codex, vscode).

Optional follow-ups

  • Remove vestigial coverage_patterns and source_attribution fields from GeminiPlacement (they are always empty; removing them makes the dataclass honest about what GEMINI.md compilation actually produces).
  • Remove the f-string prefix and embedded [+] from agents_compiler.py line 648: "Generated GEMINI.md (imports AGENTS.md)" to stay consistent with other _log("progress", ...) call sites.
  • Update docs/src/content/docs/introduction/why-apm.md and what-is-apm.md to surface the six-ecosystem breadth (Copilot, Claude, Cursor, Codex, OpenCode, Gemini) as a competitive differentiator (Growth Hacker recommendation; out of scope for this PR).

Generated by PR Review Panel for issue #917 · ● 2.3M ·

- Remove get_token_for_purpose("gemini") call that raises ValueError
  (no "gemini" key in TOKEN_PRECEDENCE); Gemini adapter doesn't need
  GitHub token injection
- Remove vestigial coverage_patterns/source_attribution from GeminiPlacement
- Fix _log("progress") call to match other call sites (no f-string, no [+])
- Add gemini to --runtime TEXT list in cli-commands.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@stbenjam
Copy link
Copy Markdown
Contributor Author

Fix auth bug (src/apm_cli/adapters/client/gemini.py, line 151): change get_token_for_purpose("gemini") to get_token_for_purpose("copilot"). Then add a test to tests/unit/test_gemini_mcp.py that mocks _is_github_server to return True and asserts get_token_for_purpose is called with "copilot" (not "gemini") and that configure_mcp_server succeeds.

I removed this since it was just a port from Copilot, I don't think we need to special-case github token handling, no other provider does it

Fix doc drift (docs/src/content/docs/reference/cli-commands.md, apm install --runtime TEXT description): add gemini to the list (copilot, codex, vscode).

Done

Remove vestigial coverage_patterns and source_attribution fields from GeminiPlacement (they are always empty; removing them makes the dataclass honest about what GEMINI.md compilation actually produces).

Done

Remove the f-string prefix and embedded [+] from agents_compiler.py line 648: "Generated GEMINI.md (imports AGENTS.md)" to stay consistent with other _log("progress", ...) call sites.

Done

Update docs/src/content/docs/introduction/why-apm.md and what-is-apm.md to surface the six-ecosystem breadth (Copilot, Claude, Cursor, Codex, OpenCode, Gemini) as a competitive differentiator (Growth Hacker recommendation; out of scope for this PR).

This is already there

@danielmeppiel danielmeppiel added panel-review Trigger the apm-review-panel gh-aw workflow and removed panel-review Trigger the apm-review-panel gh-aw workflow labels Apr 25, 2026
@danielmeppiel
Copy link
Copy Markdown
Collaborator

re-running the panel. @stbenjam hint: you can run the panel locally, it's just a skill (apm-review-panel).

@stbenjam
Copy link
Copy Markdown
Contributor Author

re-running the panel. @stbenjam hint: you can run the panel locally, it's just a skill (apm-review-panel).

Yup, I have been! I love the panel's review style. I pass it through Gemini CLI, coderabbit, apm-review-panel (with Opus 4.6, so I get different results than yours on Github), and Opus 4.7 with a "Technical Writer" persona (this helped surface some inconsistencies, similar to your doc writer but as a reviewer)

@github-actions
Copy link
Copy Markdown

APM Review Panel Verdict

Disposition: APPROVE (one optional UX follow-up, no required pre-merge actions)


Per-persona findings

Python Architect: This is a cross-cutting target addition spanning six layers (adapter, compilation, integration, runtime, detection, factory) that faithfully follows the established target-extension pattern used for Cursor and Codex. No new architectural patterns are introduced; existing patterns are extended cleanly.

1. OO / class diagram

classDiagram
    direction LR
    class CopilotClientAdapter {
        <<Adapter>>
        +configure_mcp_server(url, ...) bool
        +_format_server_config(info, ...) dict
        +get_config_path() str
        +update_config(updates)
        +get_current_config() dict
    }
    class GeminiClientAdapter {
        <<Adapter>>
        +supports_user_scope bool
        +get_config_path() str
        +update_config(updates)
        +_format_server_config(info, ...) dict
        +configure_mcp_server(url, ...) bool
    }
    class GeminiFormatter {
        <<Pure>>
        +base_dir Path
        +format_distributed(primitives, ...) GeminiCompilationResult
        -_generate_stub() str
    }
    class GeminiCompilationResult {
        <<ValueObject>>
        +success bool
        +placements List
        +content_map Dict
        +warnings List
        +errors List
        +stats Dict
    }
    class GeminiPlacement {
        <<ValueObject>>
        +gemini_path Path
        +instructions List
    }
    class AgentsCompiler {
        +compile_package(config) CompilationResult
        -_compile_gemini_md(config, primitives) CompilationResult
        -_compile_agents_md(config, primitives) CompilationResult
        -_compile_claude_md(config, primitives) CompilationResult
    }
    class ClientFactory {
        <<Factory>>
        +create_client(target) BaseClientAdapter
    }
    class RuntimeManager {
        +runtimes Dict
        +remove_runtime(name) bool
        +get_runtime_preference() List
    }
    CopilotClientAdapter <|-- GeminiClientAdapter : extends
    AgentsCompiler *-- GeminiFormatter : creates
    GeminiFormatter ..> GeminiCompilationResult : returns
    GeminiCompilationResult *-- GeminiPlacement : contains
    ClientFactory ..> GeminiClientAdapter : creates
    note for GeminiClientAdapter "Adapter: inherits Copilot helpers,\noverrides _format_server_config\nfor Gemini schema (no type/tools/id)"
    class GeminiClientAdapter:::touched
    class GeminiFormatter:::touched
    class GeminiCompilationResult:::touched
    class GeminiPlacement:::touched
    class AgentsCompiler:::touched
    class ClientFactory:::touched
    class RuntimeManager:::touched
    classDef touched fill:#fff3b0,stroke:#d47600
Loading

2. Execution flow diagram

flowchart TD
    A[apm install / apm compile --target gemini] --> B[target_detection.detect_target]
    B --> C{.gemini/ dir exists?}
    C -->|yes| D[target = gemini]
    C -->|no| E[other target or minimal]
    D --> F[GeminiClientAdapter.configure_mcp_server]
    F --> G{.gemini/ dir check}
    G -->|absent| H[return True silently -- opt-in guard]
    G -->|present| I[NET: registry_client.find_server_by_reference]
    I --> J[_format_server_config -- transport inferred from key]
    J --> K[update_config]
    K --> L[FS: write .gemini/settings.json mcpServers]
    D --> M[hook_integrator.merge_hooks]
    M --> N[_to_gemini_hook_entries -- flatten Copilot -> Gemini nested format]
    N --> O[_copilot_keys_to_gemini -- rename bash->command, timeoutSec->timeout ms]
    O --> P[FS: write .gemini/settings.json hooks]
    D --> Q[command_integrator.integrate_package_commands]
    Q --> R{format_id == gemini_command?}
    R -->|yes| S[_write_gemini_command]
    S --> T[FS: write .gemini/commands/*.toml via toml.dumps]
    D --> U[AgentsCompiler: should_compile_gemini_md -- true]
    U --> V[GeminiFormatter.format_distributed]
    V --> W[_generate_stub: GEMINI.md with @./AGENTS.md import]
    W --> X[FS: write GEMINI.md]
Loading

3. Design patterns

Design patterns

  • Used in this PR: Adapter -- GeminiClientAdapter extends CopilotClientAdapter, inheriting package-selection, env-var resolution, and argument-processing helpers while overriding _format_server_config and config-path methods for the Gemini schema.
  • Used in this PR: Dataclass-as-value-object -- GeminiPlacement and GeminiCompilationResult are frozen-compatible dataclasses that carry compilation state between GeminiFormatter and AgentsCompiler._compile_gemini_md.
  • Pragmatic suggestion: none -- the current shape is the simplest correct design at this scope. GeminiFormatter as a standalone class (not a BaseIntegrator subclass) is justified because it only generates a stub at the project root, not deploying package files; adding the full integrator lifecycle would over-engineer for the current need.

Additional observations:

  • The set = builtins.set / list = builtins.list / dict = builtins.dict pattern in gemini_formatter.py is pre-existing across claude_formatter.py and link_resolver.py -- not introduced by this PR. Not a blocker, but the entire pattern warrants a tech-debt note since shadowing builtins is surprising and the original dict annotation in these files is what actually collides with Click, not the builtins.
  • The change from success=False to success=True for targets that produce no compiled output (line 238 region) is a correct fix for Cursor-type targets that use the data-driven integration layer and never enter _compile_agents_md.

CLI Logging Expert: Output paths are consistent with existing adapter conventions. GeminiClientAdapter.configure_mcp_server uses _rich_error/_rich_success directly -- this matches the pre-existing pattern across all adapters (CopilotClientAdapter, CodexClientAdapter) rather than routing through CommandLogger. No new violation is introduced here. The mcp_integrator Gemini stale-cleanup correctly uses logger.progress() when a CommandLogger is present and falls back to _rich_success when called without one -- the established dual-path pattern. No bare print() calls. No new output surfaces bypass the logger pipeline.


DevX UX Expert: The CLI surface is clean and consistent. apm runtime setup gemini / apm runtime remove gemini align with the existing copilot/codex rhythm. --target gemini is unsurprising to users familiar with the flag. Auto-detection via .gemini/ directory presence is excellent -- users who do not use Gemini see zero impact. Setup scripts provide three authentication paths (OAuth browser flow, GOOGLE_API_KEY, Vertex AI) with clear "Next steps" text. The GEMINI.md @./AGENTS.md import design is smart: minimal footprint, no instruction duplication.

One UX gap: when apm install runs against a project where .gemini/ does not exist (or was deleted after APM wrote a dependency), GeminiClientAdapter.configure_mcp_server silently returns True with no output. A user who expected Gemini MCP setup gets no signal explaining the skip. A one-line warning ("Gemini MCP config skipped -- no .gemini/ directory; run gemini first") would improve discoverability without breaking the opt-in contract. Flagged as optional follow-up, not a blocker.


Supply Chain Security Expert: No new attack surfaces introduced.

  • toml import in _write_gemini_command is a lazy import toml as _toml -- verified present in pyproject.toml (line 33: "toml>=0.10.2"). Safe.
  • update_config opt-in guard (if not gemini_dir.is_dir(): return) prevents any writes to .gemini/settings.json unless the user already created the directory -- effective scope-limiting control.
  • Transport type in _format_server_config is validated against an explicit allowlist (sse, http, streamable-http) before any URL is written. Raises ValueError on unknown transport. Good fail-closed design.
  • @google/gemini-cli npm install carries the same supply-chain trust assumptions as the existing @github/copilot install script. No new trust boundary introduced; the pattern is established.
  • Hook key transformation (bash/powershell -> command, timeoutSec * 1000) is pure dict manipulation with no shell expansion. Timeout arithmetic is safe Python int multiplication with no overflow risk.
  • No new credential paths, token sources, or AuthResolver bypasses. No path traversal risk -- the .gemini/ path is hardcoded, not derived from user input.

Auth Expert: Not activated -- no fast-path auth files (core/auth.py, core/token_manager.py, core/azure_cli.py, deps/github_downloader.py, marketplace/client.py, utils/github_host.py, install/validation.py, deps/registry_proxy.py) appear in the diff. GeminiClientAdapter inherits CopilotClientAdapter's credential resolution path without modification; no new token sources, host-classification rules, or AuthResolver bypass paths are introduced.


OSS Growth Hacker: Strong growth signal. With Gemini CLI support, APM now integrates with all six major AI coding runtimes: Copilot, Claude, Cursor, Codex, OpenCode, and Gemini. This is the "universal cross-agent package manager" narrative made concrete -- a compelling one-liner for README, blog posts, and conference talks.

Google's @google/gemini-cli is open source and growing rapidly. Being an early APM integrator means early visibility to a large developer population who may not yet have encountered APM. The opt-in .gemini/ detection is also growth-positive: zero-friction for existing users, discoverable path for new ones.

Side-channel to CEO: the README hero section and feature table list targets but do not yet lead with the "works across all six major AI coding tools" hook. That count (now six targets) is the most memorable differentiator in the package-manager space and could materially improve first-visit conversion if surfaced higher. Suggest a follow-up README polish pass.


CEO arbitration

The panel is aligned and the analysis is convergent. This is a well-scoped, cleanly executed Gemini integration that extends APM's existing multi-target architecture without bending it. Every specialist reviewed their domain and found no blocking issue: the adapter correctly inherits Copilot auth logic unchanged, logging follows established conventions, supply-chain exposure is bounded by the opt-in directory guard, and the CLI surface is unsurprising.

The two items worth noting: (1) the silent no-op when .gemini/ is absent is a minor DevX gap -- the opt-in design is correct, but a single warning line would improve discoverability; (2) the builtins shadowing pattern is pre-existing and consistent with claude_formatter.py -- it is unusual but not new debt introduced by this PR. Neither warrants blocking.

From a positioning standpoint, six-target breadth is now APM's strongest differentiator. The Growth Hacker's side-channel is received: a README polish pass to lead with the six-runtime count is a quick follow-up with outsized conversion impact.

Disposition: APPROVE. Merge when ready.


Required actions before merge

  1. None.

Optional follow-ups

  • DevX gap (UX): In GeminiClientAdapter.configure_mcp_server (and update_config), emit a one-line info message when .gemini/ is absent so users know why Gemini MCP setup was skipped. Example: _rich_info("Gemini MCP config skipped -- no .gemini/ directory found. Run 'gemini' once to create it.", symbol="info"). This keeps the opt-in contract while improving discoverability.
  • README polish: Add a "works across 6 runtimes: Copilot, Claude, Cursor, Codex, OpenCode, Gemini" hook near the top of README.md to sharpen the differentiator story and improve first-visit conversion.
  • Tech debt: The set = builtins.set / list = builtins.list / dict = builtins.dict pattern in the compilation module (gemini_formatter.py, claude_formatter.py, link_resolver.py) should be documented in a code comment that explains the actual collision being guarded (the dict return-type annotation alias from typing in older Python, not a Click collision), or removed if the underlying cause has been resolved by the minimum Python version bump.

Generated by PR Review Panel for issue #917 · ● 848.4K ·

@stbenjam
Copy link
Copy Markdown
Contributor Author

DevX gap (UX): In GeminiClientAdapter.configure_mcp_server (and update_config), emit a one-line info message when .gemini/ is absent so users know why Gemini MCP setup was skipped. Example: _rich_info("Gemini MCP config skipped -- no .gemini/ directory found. Run 'gemini' once to create it.", symbol="info"). This keeps the opt-in contract while improving discoverability.

Hm, no other provider does it. It is a nice suggestion but maybe should apply to everyone? But then it'd get noisy.

README polish: Add a "works across 6 runtimes: Copilot, Claude, Cursor, Codex, OpenCode, Gemini" hook near the top of README.md to sharpen the differentiator story and improve first-visit conversion.

This is there

Tech debt: The set = builtins.set / list = builtins.list / dict = builtins.dict pattern in the compilation module (gemini_formatter.py, claude_formatter.py, link_resolver.py) should be documented in a code comment that explains the actual collision being guarded (the dict return-type annotation alias from typing in older Python, not a Click collision), or removed if the underlying cause has been resolved by the minimum Python version bump.

This is probably true the comments can be removed everywhere but I need to look closer, I need to wrap up for the day so I can pick it up on Monday if needs to get addressed

@danielmeppiel danielmeppiel self-requested a review April 25, 2026 16:29
@danielmeppiel danielmeppiel added this pull request to the merge queue Apr 25, 2026
@danielmeppiel
Copy link
Copy Markdown
Collaborator

Those were really nits and don't make sense - LGTM! Great contribution @stbenjam !

Merged via the queue into microsoft:main with commit 898fb2a Apr 25, 2026
18 checks passed
danielmeppiel added a commit that referenced this pull request Apr 26, 2026
Document apm runtime setup gemini and the Gemini CLI runtime
introduced in #917. Updates:
- Overview table: three -> four runtimes, add Gemini CLI row
- Quick Setup: add gemini to runtime management examples
- New 'Google Gemini CLI Runtime' section with setup,
  authentication options, usage examples, and MCP integration note
- Troubleshooting: add gemini to runtime-not-found list and add
  'Command not found: gemini' entry

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel danielmeppiel mentioned this pull request Apr 26, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

panel-review Trigger the apm-review-panel gh-aw workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants