feat(cli): add Gemini CLI as a build target and registry citizen - #623
Draft
tkman59 wants to merge 23 commits into
Draft
feat(cli): add Gemini CLI as a build target and registry citizen#623tkman59 wants to merge 23 commits into
tkman59 wants to merge 23 commits into
Conversation
Extends AiToolId/AI_TOOL_IDS/FrameworkBuildTarget, adds the gemini CONFIG_ASSETS entry and settings.json seed, and writes domain/tools/ai/gemini.ts (agents, skills, mcp, plugins:unsupported) so the id lands atomically with a registered tool — AI_TOOL_IDS is the runtime source of truth for "install all tools", so registering the id without the tool would have crashed real installs. Phase 1 of part-1 (feat/511-gemini-flat-build-target), pulling forward what was originally Phase 4 task 1; see the plan's Amendments for the reasoning and the resulting Phase 4 trim. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
domain/formats/gemini-settings-merge.ts becomes the single authority on .gemini/settings.json: mergeGeminiSettingsHooks translates Claude hook event names to Gemini's own vocabulary (verified against the shipped gemini-cli 0.52.0 bundle's hooks-migrate table) and warns on anything unmapped rather than writing an invalid event name; mergeGeminiSettingsSeed does an idempotent, order-preserving context.fileName array union so a pre-existing user value is never lost. Both preserve every key they don't own, so hooks, mcpServers (reusing mergeVscodeMcp, no new code needed there) and the seed can write into the same file in sequence without clobbering each other. Phase 2 of part-1 (feat/511-gemini-flat-build-target). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets a ToolBuildContract exclude a plugin by name (excludedPlugins), consumed via a new shouldBuildPlugin(name) predicate on BuildOutputStrategy — always true for marketplace, contract-driven for flat — with zero per-tool/per-artifact branching added to either orchestrator. FrameworkBuildUseCase's plugin loop skips a rejected plugin with one warn and excludes it from the build result entirely. This is what will let gemini's flat contract exclude aidd-orchestrator (Claude-Code-coupled, no Gemini equivalent) in Phase 4 without any tool-specific branch in the orchestrators. Phase 3 of part-1 (feat/511-gemini-flat-build-target). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
buildGeminiFlatContract() declares all six artifact kinds: skills reuse codex's exact .agents/skills/ tree (extracted AGENTS_SKILLS_PREFIX into flat-paths.ts as a shared constant so this is byte-identical by construction, not convention), agents are rebuilt to name+description only (Gemini's strict Zod schema rejects unknown frontmatter keys), mcp reuses mergeVscodeMcp against the mcpServers key (no shape transform needed), hooks reuse Phase 2's event-translating merge, and the settings seed lands via emitConfigArtifact. excludedPlugins applies Phase 3's mechanism to drop aidd-orchestrator (Claude-Code- coupled, no Gemini equivalent). No gemini:marketplace row exists. Wired into deps.ts (gemini:flat registry row), the framework/ai commands' target lists and help text, the interactive menu hint, and the CI build matrix (10 cells). biome.json and package.json updated for the new tool directory/keyword. Phase 4 of part-1 (feat/511-gemini-flat-build-target). The tool definition itself landed in Phase 1; see that phase's Amendments. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Empirically proved the flat archive against the real gemini-cli 0.52.0 binary in a sandboxed HOME: skills discovered via `gemini skills list --all`, agents parse cleanly (AgentRegistry loaded, zero AgentLoadError), both hooks fire with translated event names, and AGENTS.md is picked up via context.fileName. Standalone (no codex present), never touching the repo root or real user config. Adds gemini to the golden framework-build snapshot's FLAT_TARGETS (9-cell matrix -> 10-cell). Regenerated additively: diffed old vs. new snapshot to confirm all 9 pre-existing cells are byte-identical, only the new gemini:flat cell (188 files) was added. Phase 5 of part-1 (feat/511-gemini-flat-build-target) — the last phase of this child plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All 5 phases done, success_condition (cd cli && pnpm typecheck && pnpm lint && pnpm test) green modulo 2 pre-existing environment- coupled auth test failures unrelated to this work (documented in the plan's Amendments). Master plan's Part 1 checkpoint checked, Part 2 (shared tree safety) unblocked. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The repair itself is folded into the two commits that opened the gaps, so this carries the plan and its phases only. The fold was done by rebuilding the branch with cherry-picks rather than the interactive rebase the plan names: interactive git is not available in the environment this ran in. Same end state, verified by diffing the rebuilt branch against a pre-fold backup — identical but for the two-line amendment in part-1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removing one owner of a co-owned tree destroyed files another owner still needed. Skills reach a project through the plugin path, so the plugin routes were the ones that mattered, and none of them was guarded: - plugin uninstall deleted every tracked file unconditionally - tool uninstall deleted all of the departing tool's plugin files with no guard at all - its shared-path computation built the retained set from the surviving tools' own files and merge files only, never their plugins, so even the route that looked guarded would delete a co-owned tree One function now answers "may this uninstall delete that file" for all three, spanning tool files, merge files and plugin files, with the departing claim expressed as a tool plus an optional plugin name so whole-tool and single-plugin removals share it. Each retained path is reported once, naming the owners that still claim it. Mutation-checked: neutralizing the guard fails 3 of the 5 new cases, the two that assert deletion staying green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The manifest could already express two tools claiming one path; nothing could read it that way. Every consumer walked tool-and-path pairs, which is how one owner came to delete a tree another still needed. getPathOwners() derives the inverse view on read: path to owners, each owner carrying how it claims the path (the tool itself, a merge file, or one of its plugins) and its hash. Hashes travel per owner so a divergence between two owners of one path stays visible instead of collapsing into a single entry. No schema change. The version stays 6 and no migration is added: co-ownership was always representable, only unreadable. The uninstall guard now reads this instead of walking tools, merge files and plugins itself, leaving the departing-claim match as the only logic in the application layer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both reported on a surface they could not see. They walked tool-and-path pairs and each tool's own directory, so the shared skills tree, which belongs to no single tool and arrives through plugins, was either invisible or counted twice. - a co-owned file now yields one issue, not one per owner - owners that expect different bytes for one path are reported on their own: the file cannot satisfy both, and restoring it will not settle the disagreement - a directory claimed only through plugins no longer looks untracked - a shared tree abandoned by its last owner is reported orphaned, which the per-tool-directory check structurally could not do - status scans every directory a tool actually claims files in The golden command-matrix baseline did not move, so nothing was re-baselined. Mutation-checked against the previous source: 4 of the 6 new doctor cases fail there, the 2 survivors being negative assertions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two tools rendering different bytes to one shared path is the failure co-ownership cannot survive. The guards added earlier keep such a file alive; this keeps it from diverging in the first place. The golden suite now builds the codex and gemini flat cells and requires every shared skill path gemini writes to carry codex's exact hash, naming the first offending path and both hashes on failure. It also requires gemini to write at least one such file, so the assertion cannot pass by finding nothing to compare. Mutation-checked by flipping gemini's skill-name rewrite: the suite fails naming the diverging SKILL.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The translator asked which tool it was dealing with twice: once to pick the built tree's layout, once to decide which files in a flat build belonged to the plugin. Both answers were already derivable. The layout comes from the plugins capability, which resolves to "flat" for flat tools and was simply never read. Ownership comes from the tool's own directory plus the shared .agents/ root, matching a plugin-namespaced segment anywhere below it rather than at a fixed depth: gemini's agents sit one level shallower than opencode's skills. The hooks-skip reason moves onto the capability too, so a tool explains its own limitation instead of borrowing opencode's. Gemini declares flat plugins, which it needs to materialize skills and agents at all. That put it back under a conformance guard demanding a marketplace probe, so the guard is rekeyed on PluginFormat membership: writing plugin content and reading a marketplace are different claims, and only the second needs a probe. Every opencode translator suite passes unmodified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two tools now share the capability shape that routes plugin MCP merging, and they keep their servers under different keys: mcp in opencode.json, mcpServers in .gemini/settings.json. Both sides of the merge assumed opencode's, so installing or removing a plugin for gemini would have written a stray section into a user-owned settings file and left the real one untouched. The section key now travels with the merge, and the qualification helper returns it instead of a boolean, which is what forced callers to re-derive it from the tool name. A tool that qualifies but declares no section raises a typed error: defaulting there writes one tool's section into another tool's file. Collision messages name the config actually being written rather than always naming opencode.json. Mutation-checked by pinning the key back: 2 of 4 gemini cases fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gemini joins the smoke tool loop, the e2e install/uninstall matrix, the flat-target translation matrix, and three new integration suites: flat materialization, MCP merge, and built-tree materialization. Smoke goes from 73 to 78 passes at unchanged 100% coverage, so every per-tool ai leaf command now executes for gemini against the built binary. The flat materialization suite records something the plan did not know: install and the archive do not write to the same places. The archive namespaces skills as .agents/skills/<plugin>-<skill>/, install nests them at .gemini/skills/<plugin>/<skill>/. The suite asserts what install actually does and says it is not the archive's layout, leaving to the real-binary phase whether a skill is discovered at that depth. Restore gains no gemini config reference: gemini consumes only the already-listed config/mcp.json, its settings seed being a bundled asset rather than a framework-sourced file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nary Marketplace-sourced install reaches parity with the archive: 88 skills discovered and enabled, 3 agents loaded with no AgentLoadError. Two gaps found, both recorded rather than fixed. The raw local-path install fallback writes skills nested one level deeper than the vendor's layout, and the binary discovers zero of them; the cause is that flat skill translation never consults the capability's install path, unlike agents and rules, which predates gemini and would move every flat tool's output. And .gemini/settings.json is never written by any install path, so hooks and the AGENTS.md context wiring are absent. Also corrects the previous phase's claim that install and the archive diverge: only the fallback path does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The memory hook rejected `gemini` outright, and eleven reference tables enumerated five tools. Both now know the sixth. Every edit is a row added beside an unchanged claude row, except two hook tables that gain a Gemini column and so rewrite every row without changing a single claude cell. Hook event names come from the mapping shipped in `gemini hooks migrate`, read out of the 0.52.0 bundle: Gemini renames most moments and folds turn stop and subagent stop onto one event. The golden snapshot is red from here until the re-baseline phase, which is what that phase exists for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gemini moves to supported flat in the compatibility table, with an install block that carries both gating constraints inside it rather than in a general prerequisites section a reader skips: the 0.28.0 minimum, below which no skill is ever discovered, and the folder-trust step, without which Gemini lists nothing and explains nothing. The block also steers users to the archive rather than `aidd ai install gemini`, because the install path writes no .gemini/settings.json and so carries neither hooks nor the AGENTS.md context wiring. Every version claim states how it was established: the minimum is derived from the CLI's own source, since the vendor publishes none, and 0.52.0 is what was actually verified. Five targets and nine build cells become six and ten across the CLI docs, the architecture rule and the project memory. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The plan expected this part's content edits to move the golden snapshot. They do not: the suite builds from a committed fixture copy of the plugins, not from the live tree, so regenerating produces a byte- identical file and the subset invariant still holds. The fixture is frozen on purpose — that is what makes the golden a test of build behaviour rather than of plugin content — so it is left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The matrix documents `plugin sync` and `ai sync` runs from May against v4.1.0-beta.11. Neither command exists in the CLI today, so the ten gemini pairs cannot be run, and writing PASS beside them would be inventing results. The document stays as the accurate record of what was true when it was written. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Issue #511 now carries the verified mapping, the two prerequisites that silently produce an empty result, and the install-path limitation. The original mapping is preserved and superseded rather than overwritten. All four parts are done. Two checkpoints remained unticked and both are now met: the command matrix is green for gemini with the smoke coverage gate held at 100%, and a Gemini-only project consumes the archive end to end against the real 0.52.0 binary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The lone auth status failure came from an expired AIDD_TOKEN in the developer's shell, which the e2e sandbox redirects HOME around but does not scrub. Without it the suite is 2237/2237. Part 3's chain still does not exit 0: it also runs smoke, whose four corrupt-cache cases fail on published-catalog drift, not on the environment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎯 What & why
Gemini CLI users had no supported path to AIDD. This adds
geminias the sixth tool: a flat build target, and the full install/uninstall/update/restore/doctor/status lifecycle.It also closes a multi-owner bug that gemini would have made worse, and that already affected codex.
🛠️ How it works
The build target declares all six artifact kinds. Skills go to
.agents/skills/— the same shared tree codex writes — agents to.gemini/agents/, and MCP, hooks and theAGENTS.mdcontext wiring merge into.gemini/settings.jsonthrough a single authority module, since a real Gemini user already owns that file.aidd-orchestratoris excluded: it is built on Claude Code's plugin activation and GitHub Action, so shipping it would ship a plugin that cannot run. That needed a general exclusion mechanism on the build contract rather than a per-tool branch.Two tools writing one tree is the failure co-ownership cannot survive, so it is eliminated rather than managed. Gemini reuses codex's skills rendering verbatim, and framework-build-golden.e2e.test.ts now asserts gemini's shared paths carry codex's exact hashes.
The uninstall guard was wider than expected. Plugin uninstall deleted tracked files unconditionally, tool uninstall deleted the departing tool's plugin files with no guard at all, and its shared-path computation ignored the surviving tools' plugins entirely. Since skills reach a project through the plugin path, an owners view that skips plugins was the bug, not a subset of it. One function now answers "may this uninstall delete that file" for all three routes, reading a new
Manifest.getPathOwners()— the manifest could always express co-ownership, nothing could read it that way. No schema change.Two
opencodeliterals were removed from the plugin translation path. The built tree's layout comes from the plugins capability, which already resolved toflatand was simply never read; file ownership comes from the tool's directory plus the shared.agents/root. MCP merging now uses the key each tool declares (mcpfor opencode,mcpServersfor gemini) instead of assuming opencode's — running one tool's unmerge against the other's file would have left a stray section in a user-owned config. Every opencode translator suite passes unmodified.🧪 How to verify
cd cli && pnpm typecheck && pnpm lint && pnpm test→ 2237/2237cd cli && pnpm smoke→ 78 pass, coverage 100%, 4 pre-existing failures unrelated to this branch (see Heads-up)AgentLoadError,AGENTS.mdpicked up as context.Two install-path gaps, measured and documented, not fixed here. The archive is complete;
aidd ai install geminiis not..gemini/settings.jsonis never written by any install path, so an installed-only setup has no hooks and noAGENTS.mdwiring. Closing it needs aSettingsCapabilitychange: it supports merge-strategy enums or static content, not the idempotentcontext.fileNamearray union.translateSkillnever consultsSkillsCapability.buildInstallPath, unlike agents and rules. This predates gemini and fixing it moves codex's and opencode's flat install output too.Both are recorded in the plan amendments and in the issue. The README steers users to the archive meanwhile.
Pre-existing failures, not from this branch.
pnpm smoke's four corrupt-cache cases fail becausesetup --plugins recommendednow installsaidd-dev, so the fault injection hits "already installed" before any catalog read — published-catalog drift, fixable by picking a non-recommended plugin. Andauth statusfails locally whenever anAIDD_TOKENis exported: the e2e sandbox redirectsHOMEbut does not scrub credential env vars.Scope. Four planned parts are stacked on one branch: the build target, the shared-tree fix, the registry work and the docs. Say the word and I'll split it.
🔗 Linked issue
Closes #511
✅ I certify
🤖 Generated with Claude Code