From 0f38e3467ecf9f9346d42d520e1fd6ad19a8ee99 Mon Sep 17 00:00:00 2001 From: Frank <97429702+tsubasakong@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:36:43 -0700 Subject: [PATCH] fix: strip unsupported Gemini agent skills frontmatter (#971) --- bin/install.js | 16 ++++++++++++ tests/gemini-config.test.cjs | 47 ++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 tests/gemini-config.test.cjs diff --git a/bin/install.js b/bin/install.js index 4d297d2a1..3f50bf660 100755 --- a/bin/install.js +++ b/bin/install.js @@ -757,6 +757,7 @@ function stripSubTags(content) { * - tools: must be a YAML array (not comma-separated string) * - tool names: must use Gemini built-in names (read_file, not Read) * - color: must be removed (causes validation error) + * - skills: must be removed (causes validation error) * - mcp__* tools: must be excluded (auto-discovered at runtime) */ function convertClaudeToGeminiAgent(content) { @@ -771,11 +772,19 @@ function convertClaudeToGeminiAgent(content) { const lines = frontmatter.split('\n'); const newLines = []; let inAllowedTools = false; + let inSkippedArrayField = false; const tools = []; for (const line of lines) { const trimmed = line.trim(); + if (inSkippedArrayField) { + if (!trimmed || trimmed.startsWith('- ')) { + continue; + } + inSkippedArrayField = false; + } + // Convert allowed-tools YAML array to tools list if (trimmed.startsWith('allowed-tools:')) { inAllowedTools = true; @@ -801,6 +810,12 @@ function convertClaudeToGeminiAgent(content) { // Strip color field (not supported by Gemini CLI, causes validation error) if (trimmed.startsWith('color:')) continue; + // Strip skills field (not supported by Gemini CLI, causes validation error) + if (trimmed.startsWith('skills:')) { + inSkippedArrayField = true; + continue; + } + // Collect allowed-tools/tools array items if (inAllowedTools) { if (trimmed.startsWith('- ')) { @@ -2414,6 +2429,7 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) { if (process.env.GSD_TEST_MODE) { module.exports = { getCodexSkillAdapterHeader, + convertClaudeToGeminiAgent, convertClaudeAgentToCodexAgent, generateCodexAgentToml, generateCodexConfigBlock, diff --git a/tests/gemini-config.test.cjs b/tests/gemini-config.test.cjs new file mode 100644 index 000000000..794208427 --- /dev/null +++ b/tests/gemini-config.test.cjs @@ -0,0 +1,47 @@ +/** + * GSD Tools Tests - Gemini agent conversion + * + * Verifies Gemini-specific agent frontmatter conversion removes + * unsupported fields while preserving converted tools and body text. + */ + +process.env.GSD_TEST_MODE = '1'; + +const { test, describe } = require('node:test'); +const assert = require('node:assert'); + +const { convertClaudeToGeminiAgent } = require('../bin/install.js'); + +describe('convertClaudeToGeminiAgent', () => { + test('drops unsupported skills frontmatter while keeping converted tools', () => { + const input = `--- +name: gsd-codebase-mapper +description: Explores codebase and writes structured analysis documents. +tools: Read, Bash, Grep, Glob, Write +color: cyan +skills: + - gsd-mapper-workflow +--- + + +Use \${PHASE} in shell examples. +`; + + const result = convertClaudeToGeminiAgent(input); + const frontmatter = result.split('---')[1] || ''; + + assert.ok(frontmatter.includes('name: gsd-codebase-mapper'), 'keeps name'); + assert.ok(frontmatter.includes('description: Explores codebase and writes structured analysis documents.'), 'keeps description'); + assert.ok(frontmatter.includes('tools:'), 'adds Gemini tools array'); + assert.ok(frontmatter.includes(' - read_file'), 'maps Read -> read_file'); + assert.ok(frontmatter.includes(' - run_shell_command'), 'maps Bash -> run_shell_command'); + assert.ok(frontmatter.includes(' - search_file_content'), 'maps Grep -> search_file_content'); + assert.ok(frontmatter.includes(' - glob'), 'maps Glob -> glob'); + assert.ok(frontmatter.includes(' - write_file'), 'maps Write -> write_file'); + assert.ok(!frontmatter.includes('color:'), 'drops unsupported color field'); + assert.ok(!frontmatter.includes('skills:'), 'drops unsupported skills field'); + assert.ok(!frontmatter.includes('gsd-mapper-workflow'), 'drops skills list items'); + assert.ok(result.includes('$PHASE'), 'escapes ${PHASE} shell variable for Gemini'); + assert.ok(!result.includes('${PHASE}'), 'removes Gemini template-string pattern'); + }); +});