From cc362e474f625c6cab59f0c33b7b76a45e43dbee Mon Sep 17 00:00:00 2001 From: Behruz Nassre Esfahani Date: Mon, 22 Jun 2026 08:01:28 -0700 Subject: [PATCH] test(#1394): assert Gemini tool exclusion via exported converter, not internal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The back-merge of next carried #1559/#1565 (audit installer compatibility exports), which removed convertGeminiToolName from bin/install.js's module.exports. This PR's regression test destructured it from require('../bin/install.js'), so after the merge it was undefined → "TypeError: convertGeminiToolName is not a function" across all test lanes. Rewrite the regression to assert the user-visible behavior through the still-exported convertClaudeToGeminiAgent: a Skill/SlashCommand/AskUserQuestion tools entry must not appear in the emitted Gemini frontmatter (the lowercase fallback would emit invalid tool names that abort agent load — #1394/#3362), while mapped tools (Read→read_file, WebFetch→web_fetch) survive. Folds the dropped AskUserQuestion/ask_user coverage into the behavior test and removes the internal-function import, so the test no longer depends on a private export #1559 intentionally pruned. The exclusion fix itself is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/runtime-converters.test.cjs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/tests/runtime-converters.test.cjs b/tests/runtime-converters.test.cjs index bb0cad3d9..667ec290c 100644 --- a/tests/runtime-converters.test.cjs +++ b/tests/runtime-converters.test.cjs @@ -18,7 +18,6 @@ const { convertClaudeToOpencodeFrontmatter, convertClaudeToKiloFrontmatter, convertClaudeToGeminiAgent, - convertGeminiToolName, convertClaudeAgentToAntigravityAgent, convertClaudeCommandToOpencodeSkill, convertClaudeCommandToKiloSkill, @@ -302,25 +301,17 @@ Offer choices via AskUserQuestion when user input is needed. // validation (tools.N: Invalid tool name) and aborts the entire agent load — // previously killing 22 of 34 GSD agents on Gemini. - // Direct unit assertion against the LIVE install path (bin/install.js copy — - // the one that actually generates agents), not the tsc build artifact, so a - // stale build can't give a false-green while the live copy is broken. - test('convertGeminiToolName returns null for Skill and SlashCommand', () => { - assert.equal(convertGeminiToolName('Skill'), null, 'Skill is excluded, not lowercased to "skill"'); - assert.equal(convertGeminiToolName('SlashCommand'), null, 'SlashCommand is excluded, not lowercased to "slashcommand"'); - // Existing AskUserQuestion/ask_user exclusion (the same if-block this PR extends) must still hold. - assert.equal(convertGeminiToolName('AskUserQuestion'), null, 'AskUserQuestion remains excluded'); - assert.equal(convertGeminiToolName('ask_user'), null, 'ask_user remains excluded'); - // Sanity: a mapped tool still converts. - assert.equal(convertGeminiToolName('Read'), 'read_file', 'mapped tools still convert'); - }); - // Agent-level assertion against the live install path (criterion 2/3). - test('a Skill tools entry produces no skill/slashcommand in emitted frontmatter', () => { + // Asserts the emitted frontmatter rather than the internal converter so the + // test exercises the public, install-path-exported API (convertGeminiToolName + // is an internal helper, deliberately not exported per #1559). The Skill, + // SlashCommand, and AskUserQuestion inputs all exercise the exclusion if-block; + // Read/WebFetch exercise the mapped-tool path that must survive. + test('Skill/SlashCommand/AskUserQuestion are dropped from emitted Gemini frontmatter', () => { const input = `--- name: gsd-planner description: Creates executable phase plans. -tools: Read, Write, Bash, Glob, Grep, Skill, WebFetch, SlashCommand +tools: Read, Write, Bash, Glob, Grep, Skill, WebFetch, SlashCommand, AskUserQuestion --- @@ -330,10 +321,15 @@ Plan the phase. const result = convertClaudeToGeminiAgent(input); const frontmatter = result.split('---')[1] || ''; + // Mapped tools still convert (the exclusion must not break the happy path). assert.ok(frontmatter.includes(' - read_file'), 'maps Read -> read_file'); assert.ok(frontmatter.includes(' - web_fetch'), 'maps WebFetch -> web_fetch'); + // Claude-only tools with no Gemini equivalent are excluded, not lowercased + // into invalid names that would fail frontmatter validation (#1394 / #3362). assert.ok(!frontmatter.includes(' - skill'), 'does not emit invalid Gemini skill tool'); assert.ok(!frontmatter.includes(' - slashcommand'), 'does not emit invalid Gemini slashcommand tool'); + assert.ok(!frontmatter.includes(' - ask_user'), 'AskUserQuestion remains excluded'); + assert.ok(!frontmatter.includes(' - askuserquestion'), 'AskUserQuestion is not lowercased into an invalid tool'); }); // Antigravity reuses convertGeminiToolName (it runs on the Gemini backend),