fix: address code review findings for gemini target

- Extract named GeminiMcpServer type (eliminates NonNullable indexing)
- Deep-merge mcpServers in settings.json (preserves existing entries)
- Warn when existing settings.json cannot be parsed
- Add test for uniqueName dedup (agent/skill name collision)
- Add test for TOML triple-quote escaping

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kieran Klaassen
2026-02-14 20:46:31 -08:00
parent e113d20126
commit d487915f0f
5 changed files with 52 additions and 14 deletions

View File

@@ -1,6 +1,6 @@
import { formatFrontmatter } from "../utils/frontmatter"
import type { ClaudeAgent, ClaudeCommand, ClaudeMcpServer, ClaudePlugin } from "../types/claude"
import type { GeminiBundle, GeminiCommand, GeminiSkill } from "../types/gemini"
import type { GeminiBundle, GeminiCommand, GeminiMcpServer, GeminiSkill } from "../types/gemini"
import type { ClaudeToOpenCodeOptions } from "./claude-to-opencode"
export type ClaudeToGeminiOptions = ClaudeToOpenCodeOptions
@@ -109,12 +109,12 @@ export function transformContentForGemini(body: string): string {
function convertMcpServers(
servers?: Record<string, ClaudeMcpServer>,
): GeminiBundle["mcpServers"] | undefined {
): Record<string, GeminiMcpServer> | undefined {
if (!servers || Object.keys(servers).length === 0) return undefined
const result: NonNullable<GeminiBundle["mcpServers"]> = {}
const result: Record<string, GeminiMcpServer> = {}
for (const [name, server] of Object.entries(servers)) {
const entry: NonNullable<GeminiBundle["mcpServers"]>[string] = {}
const entry: GeminiMcpServer = {}
if (server.command) {
entry.command = server.command
if (server.args && server.args.length > 0) entry.args = server.args

View File

@@ -37,11 +37,14 @@ export async function writeGeminiBundle(outputRoot: string, bundle: GeminiBundle
try {
existingSettings = await readJson<Record<string, unknown>>(settingsPath)
} catch {
// If existing file is invalid JSON, start fresh
console.warn("Warning: existing settings.json could not be parsed and will be replaced.")
}
}
const merged = { ...existingSettings, mcpServers: bundle.mcpServers }
const existingMcp = (existingSettings.mcpServers && typeof existingSettings.mcpServers === "object")
? existingSettings.mcpServers as Record<string, unknown>
: {}
const merged = { ...existingSettings, mcpServers: { ...existingMcp, ...bundle.mcpServers } }
await writeJson(settingsPath, merged)
}
}

View File

@@ -13,15 +13,17 @@ export type GeminiCommand = {
content: string // Full TOML content
}
export type GeminiMcpServer = {
command?: string
args?: string[]
env?: Record<string, string>
url?: string
headers?: Record<string, string>
}
export type GeminiBundle = {
generatedSkills: GeminiSkill[] // From agents
skillDirs: GeminiSkillDir[] // From skills (pass-through)
commands: GeminiCommand[]
mcpServers?: Record<string, {
command?: string
args?: string[]
env?: Record<string, string>
url?: string
headers?: Record<string, string>
}>
mcpServers?: Record<string, GeminiMcpServer>
}