fix(#1182): extract agent converters + tool-name tables into conversion module (#1220)

Extracts all 9 convertClaudeAgentTo* functions together with their full
dependency closure (claudeToCopilotTools, claudeToGeminiTools,
convertCopilotToolName, convertGeminiToolName) from bin/install.js into
src/runtime-artifact-conversion.cts and adds them to the module's export=
block.

Adds 6 regression tests in tests/copilot-install.test.cjs including a
DEFECT.GENERATIVE-FIX parity guard that asserts claudeToCopilotTools is
identical in module and bin/install.js.

Inline copies in bin/install.js are retained (#1175 will remove them).
This unblocks #1173 (descriptor-driven dispatch).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-14 11:44:42 -04:00
committed by GitHub
parent 8c6a0d65e6
commit 82a0f561e1
2 changed files with 343 additions and 0 deletions

View File

@@ -1808,3 +1808,61 @@ describe('Claude uninstall preserves user-generated files (#1423)', () => {
assert.ok(!fs.existsSync(cmdDir), 'commands/gsd/ should not exist after clean uninstall');
});
});
// ─── #1182 regression: agent converters accessible via module path ────────────
// These tests require convertClaudeAgentToCopilotAgent and its dependency closure
// (claudeToCopilotTools, convertCopilotToolName) THROUGH the runtime-artifact-conversion
// module export — not via bin/install.js. Before the fix, the module returned
// undefined for all three, causing ReferenceError when called.
describe('#1182 convertClaudeAgentToCopilotAgent exported from runtime-artifact-conversion module', () => {
const _gsdLibDirModule = path.join(__dirname, '..', 'gsd-core', 'bin', 'lib');
const conversionModule = require(path.join(_gsdLibDirModule, 'runtime-artifact-conversion.cjs'));
test('module exports claudeToCopilotTools table', () => {
assert.strictEqual(typeof conversionModule.claudeToCopilotTools, 'object', 'claudeToCopilotTools must be exported');
assert.ok(conversionModule.claudeToCopilotTools !== null, 'not null');
assert.strictEqual(conversionModule.claudeToCopilotTools['Read'], 'read', 'Read maps to read');
assert.strictEqual(conversionModule.claudeToCopilotTools['Bash'], 'execute', 'Bash maps to execute');
});
test('module exports convertCopilotToolName function', () => {
assert.strictEqual(typeof conversionModule.convertCopilotToolName, 'function', 'convertCopilotToolName must be exported');
assert.strictEqual(conversionModule.convertCopilotToolName('Read'), 'read', 'maps Read -> read');
assert.strictEqual(conversionModule.convertCopilotToolName('Bash'), 'execute', 'maps Bash -> execute');
assert.strictEqual(conversionModule.convertCopilotToolName('mcp__context7__resolve-library-id'), 'io.github.upstash/context7/resolve-library-id', 'mcp__context7__ prefix mapped');
});
test('module exports convertClaudeAgentToCopilotAgent function', () => {
assert.strictEqual(typeof conversionModule.convertClaudeAgentToCopilotAgent, 'function', 'convertClaudeAgentToCopilotAgent must be exported');
});
test('convertClaudeAgentToCopilotAgent via module produces correct output (local mode)', () => {
const input = `---\nname: gsd-executor\ndescription: Executes GSD plans\ntools: Read, Write, Edit, Bash, Grep, Glob\ncolor: yellow\n---\n\nAgent body.`;
const result = conversionModule.convertClaudeAgentToCopilotAgent(input);
// Tools must be mapped and deduplicated
assert.ok(result.includes("tools: ['read', 'edit', 'execute', 'search']"), `expected mapped tools in: ${result}`);
assert.ok(result.includes('name: gsd-executor'), 'name preserved');
assert.ok(result.includes('color: yellow'), 'color preserved');
});
test('convertClaudeAgentToCopilotAgent via module applies path/command conversions (global mode)', () => {
const input = `---\nname: gsd-test\ndescription: Test\ntools: Read\n---\n\nCheck ~/.claude/settings and run gsd:health.`;
const result = conversionModule.convertClaudeAgentToCopilotAgent(input, true);
assert.ok(result.includes('~/.copilot/settings'), 'CONV-06 applied in global mode');
assert.ok(result.includes('gsd-health'), 'CONV-07 applied');
});
// Parity assertion: claudeToCopilotTools in module matches the table in bin/install.js
// Per DEFECT.GENERATIVE-FIX: shared constant across two surfaces needs a parity guard.
test('claudeToCopilotTools parity: module table matches bin/install.js table', () => {
const installJs = require('../bin/install.js');
const moduleTable = conversionModule.claudeToCopilotTools;
const installTable = installJs.claudeToCopilotTools;
assert.deepStrictEqual(
moduleTable,
installTable,
'claudeToCopilotTools must be identical in module and bin/install.js',
);
});
});