* fix: universal agent name replacement for non-Claude runtimes (#766) Adds neutralizeAgentReferences() shared function that all non-Claude runtime converters call to replace Claude-specific references: - 'Claude' (standalone agent name) → 'the agent' - 'CLAUDE.md' → runtime-specific file (AGENTS.md, GEMINI.md, COPILOT.md) - Removes 'Do NOT load full AGENTS.md' (harmful for AGENTS.md runtimes) Preserves: 'Claude Code' (product), 'Claude Opus/Sonnet/Haiku' (models), 'claude-' prefixes (packages, CSS classes). Integrated into: OpenCode, Gemini, Copilot, Antigravity, and Codex converters. Claude Code converter unchanged (references are correct). Includes 7 new tests covering all replacement rules. Closes #766 * fix: use copilot-instructions.md instead of COPILOT.md for Copilot runtime Addresses review from Solvely-Colin: Copilot's actual instruction file is copilot-instructions.md, not COPILOT.md. The neutralizer was mapping CLAUDE.md -> COPILOT.md which would reference a non-existent file. - install.js: pass 'copilot-instructions.md' to neutralizeAgentReferences - runtime-converters.test.cjs: update test to validate correct filename
This commit is contained in:
@@ -567,6 +567,8 @@ function convertClaudeToCopilotContent(content, isGlobal = false) {
|
||||
c = c.replace(/\.claude\//g, '.github/');
|
||||
// CONV-07: Command name conversion (all gsd: references → gsd-)
|
||||
c = c.replace(/gsd:/g, 'gsd-');
|
||||
// Runtime-neutral agent name replacement (#766)
|
||||
c = neutralizeAgentReferences(c, 'copilot-instructions.md');
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -657,6 +659,8 @@ function convertClaudeToAntigravityContent(content, isGlobal = false) {
|
||||
c = c.replace(/\.claude\//g, '.agent/');
|
||||
// Command name conversion (all gsd: references → gsd-)
|
||||
c = c.replace(/gsd:/g, 'gsd-');
|
||||
// Runtime-neutral agent name replacement (#766)
|
||||
c = neutralizeAgentReferences(c, 'GEMINI.md');
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -867,6 +871,8 @@ function convertSlashCommandsToCodexSkillMentions(content) {
|
||||
function convertClaudeToCodexMarkdown(content) {
|
||||
let converted = convertSlashCommandsToCodexSkillMentions(content);
|
||||
converted = converted.replace(/\$ARGUMENTS\b/g, '{{GSD_ARGS}}');
|
||||
// Runtime-neutral agent name replacement (#766)
|
||||
converted = neutralizeAgentReferences(converted, 'AGENTS.md');
|
||||
return converted;
|
||||
}
|
||||
|
||||
@@ -1175,6 +1181,36 @@ function installCodexConfig(targetDir, agentsSrc) {
|
||||
* Terminals don't support subscript — Gemini renders these as raw HTML.
|
||||
* Converts <sub>text</sub> to italic *(text)* for readable terminal output.
|
||||
*/
|
||||
/**
|
||||
* Runtime-neutral agent name and instruction file replacement.
|
||||
* Used by ALL non-Claude runtime converters to avoid Claude-specific
|
||||
* references in workflow prompts, agent definitions, and documentation.
|
||||
*
|
||||
* Replaces:
|
||||
* - Standalone "Claude" (agent name) → "the agent"
|
||||
* Preserves: "Claude Code" (product), "Claude Opus/Sonnet/Haiku" (models),
|
||||
* "claude-" (prefixes), "CLAUDE.md" (handled separately)
|
||||
* - "CLAUDE.md" → runtime-appropriate instruction file
|
||||
* - "Do NOT load full AGENTS.md" → removed (harmful for AGENTS.md runtimes)
|
||||
*
|
||||
* @param {string} content - File content to neutralize
|
||||
* @param {string} instructionFile - Runtime's instruction file ('AGENTS.md', 'GEMINI.md', etc.)
|
||||
* @returns {string} Content with runtime-neutral references
|
||||
*/
|
||||
function neutralizeAgentReferences(content, instructionFile) {
|
||||
let c = content;
|
||||
// Replace standalone "Claude" (the agent) but preserve product/model names.
|
||||
// Negative lookahead avoids: Claude Code, Claude Opus/Sonnet/Haiku, Claude native, Claude-based
|
||||
c = c.replace(/\bClaude(?! Code| Opus| Sonnet| Haiku| native| based|-)\b(?!\.md)/g, 'the agent');
|
||||
// Replace CLAUDE.md with runtime-appropriate instruction file
|
||||
if (instructionFile) {
|
||||
c = c.replace(/CLAUDE\.md/g, instructionFile);
|
||||
}
|
||||
// Remove instructions that conflict with AGENTS.md-based runtimes
|
||||
c = c.replace(/Do NOT load full `AGENTS\.md` files[^\n]*/g, '');
|
||||
return c;
|
||||
}
|
||||
|
||||
function stripSubTags(content) {
|
||||
return content.replace(/<sub>(.*?)<\/sub>/g, '*($1)*');
|
||||
}
|
||||
@@ -1279,7 +1315,9 @@ function convertClaudeToGeminiAgent(content) {
|
||||
// is equivalent bash and invisible to Gemini's /\$\{(\w+)\}/g regex.
|
||||
const escapedBody = body.replace(/\$\{(\w+)\}/g, '$$$1');
|
||||
|
||||
return `---\n${newFrontmatter}\n---${stripSubTags(escapedBody)}`;
|
||||
// Runtime-neutral agent name replacement (#766)
|
||||
const neutralBody = neutralizeAgentReferences(escapedBody, 'GEMINI.md');
|
||||
return `---\n${newFrontmatter}\n---${stripSubTags(neutralBody)}`;
|
||||
}
|
||||
|
||||
function convertClaudeToOpencodeFrontmatter(content, { isAgent = false } = {}) {
|
||||
@@ -1295,6 +1333,8 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false } = {}) {
|
||||
convertedContent = convertedContent.replace(/\$HOME\/\.claude\b/g, '$HOME/.config/opencode');
|
||||
// Replace general-purpose subagent type with OpenCode's equivalent "general"
|
||||
convertedContent = convertedContent.replace(/subagent_type="general-purpose"/g, 'subagent_type="general"');
|
||||
// Runtime-neutral agent name replacement (#766)
|
||||
convertedContent = neutralizeAgentReferences(convertedContent, 'AGENTS.md');
|
||||
|
||||
// Check if content has frontmatter
|
||||
if (!convertedContent.startsWith('---')) {
|
||||
@@ -3319,6 +3359,7 @@ if (process.env.GSD_TEST_MODE) {
|
||||
installCodexConfig,
|
||||
convertClaudeCommandToCodexSkill,
|
||||
convertClaudeToOpencodeFrontmatter,
|
||||
neutralizeAgentReferences,
|
||||
GSD_CODEX_MARKER,
|
||||
CODEX_AGENT_SANDBOX,
|
||||
getDirName,
|
||||
|
||||
@@ -17,6 +17,7 @@ process.env.GSD_TEST_MODE = '1';
|
||||
const {
|
||||
convertClaudeToOpencodeFrontmatter,
|
||||
convertClaudeToGeminiAgent,
|
||||
neutralizeAgentReferences,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
// Sample Claude agent frontmatter (matches actual GSD agent format)
|
||||
@@ -183,3 +184,56 @@ Use \${PHASE} in shell examples.
|
||||
assert.ok(!result.includes('${PHASE}'), 'removes Gemini template-string pattern');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── neutralizeAgentReferences (#766) ─────────────────────────────────────────
|
||||
|
||||
describe('neutralizeAgentReferences', () => {
|
||||
test('replaces standalone Claude with "the agent"', () => {
|
||||
const input = 'Claude handles these decisions. Claude should read the file.';
|
||||
const result = neutralizeAgentReferences(input, 'AGENTS.md');
|
||||
assert.ok(!result.includes('Claude handles'), 'standalone Claude replaced');
|
||||
assert.ok(result.includes('the agent handles'), 'replaced with "the agent"');
|
||||
});
|
||||
|
||||
test('preserves Claude Code (product name)', () => {
|
||||
const input = 'This is a Claude Code bug. Use Claude Code settings.';
|
||||
const result = neutralizeAgentReferences(input, 'AGENTS.md');
|
||||
assert.ok(result.includes('Claude Code bug'), 'Claude Code preserved');
|
||||
assert.ok(result.includes('Claude Code settings'), 'Claude Code preserved');
|
||||
});
|
||||
|
||||
test('preserves Claude model names', () => {
|
||||
const input = 'Use Claude Opus for planning. Claude Sonnet for execution. Claude Haiku for research.';
|
||||
const result = neutralizeAgentReferences(input, 'AGENTS.md');
|
||||
assert.ok(result.includes('Claude Opus'), 'Opus preserved');
|
||||
assert.ok(result.includes('Claude Sonnet'), 'Sonnet preserved');
|
||||
assert.ok(result.includes('Claude Haiku'), 'Haiku preserved');
|
||||
});
|
||||
|
||||
test('replaces CLAUDE.md with runtime instruction file', () => {
|
||||
const input = 'Read CLAUDE.md for project instructions. Check ./CLAUDE.md if exists.';
|
||||
const result = neutralizeAgentReferences(input, 'AGENTS.md');
|
||||
assert.ok(result.includes('AGENTS.md'), 'CLAUDE.md -> AGENTS.md');
|
||||
assert.ok(!result.includes('CLAUDE.md'), 'no CLAUDE.md remains');
|
||||
});
|
||||
|
||||
test('uses different instruction file per runtime', () => {
|
||||
const input = 'Read CLAUDE.md for instructions.';
|
||||
assert.ok(neutralizeAgentReferences(input, 'GEMINI.md').includes('GEMINI.md'));
|
||||
assert.ok(neutralizeAgentReferences(input, 'copilot-instructions.md').includes('copilot-instructions.md'));
|
||||
assert.ok(neutralizeAgentReferences(input, 'AGENTS.md').includes('AGENTS.md'));
|
||||
});
|
||||
|
||||
test('removes AGENTS.md load-blocking instruction', () => {
|
||||
const input = 'Do NOT load full `AGENTS.md` files — they contain agent definitions.';
|
||||
const result = neutralizeAgentReferences(input, 'AGENTS.md');
|
||||
assert.ok(!result.includes('Do NOT load full'), 'blocking instruction removed');
|
||||
});
|
||||
|
||||
test('preserves claude- prefixes (CSS classes, package names)', () => {
|
||||
const input = 'The claude-ctx session and claude-code package.';
|
||||
const result = neutralizeAgentReferences(input, 'AGENTS.md');
|
||||
assert.ok(result.includes('claude-ctx'), 'claude- prefix preserved');
|
||||
assert.ok(result.includes('claude-code'), 'claude-code preserved');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user