feat: Add Zed editor rule profile with agent rules and MCP config (#974)

* zed profile

* add changeset

* update changeset
This commit is contained in:
Joe Danziger
2025-07-16 12:13:21 -04:00
committed by GitHub
parent 6d05e8622c
commit 5b0eda07f2
9 changed files with 516 additions and 5 deletions

View File

@@ -1,5 +1,5 @@
/**
* @typedef {'amp' | 'claude' | 'cline' | 'codex' | 'cursor' | 'gemini' | 'roo' | 'trae' | 'windsurf' | 'vscode'} RulesProfile
* @typedef {'amp' | 'claude' | 'cline' | 'codex' | 'cursor' | 'gemini' | 'roo' | 'trae' | 'windsurf' | 'vscode' | 'zed'} RulesProfile
*/
/**
@@ -20,6 +20,7 @@
* - trae: Trae IDE rules
* - vscode: VS Code with GitHub Copilot integration
* - windsurf: Windsurf IDE rules
* - zed: Zed IDE rules
*
* To add a new rule profile:
* 1. Add the profile name to this array
@@ -36,7 +37,8 @@ export const RULE_PROFILES = [
'roo',
'trae',
'vscode',
'windsurf'
'windsurf',
'zed'
];
/**

View File

@@ -9,3 +9,4 @@ export { rooProfile } from './roo.js';
export { traeProfile } from './trae.js';
export { vscodeProfile } from './vscode.js';
export { windsurfProfile } from './windsurf.js';
export { zedProfile } from './zed.js';

178
src/profiles/zed.js Normal file
View File

@@ -0,0 +1,178 @@
// Zed profile for rule-transformer
import path from 'path';
import fs from 'fs';
import { isSilentMode, log } from '../../scripts/modules/utils.js';
import { createProfile } from './base-profile.js';
/**
* Transform standard MCP config format to Zed format
* @param {Object} mcpConfig - Standard MCP configuration object
* @returns {Object} - Transformed Zed configuration object
*/
function transformToZedFormat(mcpConfig) {
const zedConfig = {};
// Transform mcpServers to context_servers
if (mcpConfig.mcpServers) {
zedConfig['context_servers'] = mcpConfig.mcpServers;
}
// Preserve any other existing settings
for (const [key, value] of Object.entries(mcpConfig)) {
if (key !== 'mcpServers') {
zedConfig[key] = value;
}
}
return zedConfig;
}
// Lifecycle functions for Zed profile
function onAddRulesProfile(targetDir, assetsDir) {
// MCP transformation will be handled in onPostConvertRulesProfile
// File copying is handled by the base profile via fileMap
}
function onRemoveRulesProfile(targetDir) {
// Clean up .rules (Zed uses .rules directly in root)
const userRulesFile = path.join(targetDir, '.rules');
try {
// Remove Task Master .rules
if (fs.existsSync(userRulesFile)) {
fs.rmSync(userRulesFile, { force: true });
log('debug', `[Zed] Removed ${userRulesFile}`);
}
} catch (err) {
log('error', `[Zed] Failed to remove Zed instructions: ${err.message}`);
}
// MCP Removal: Remove context_servers section
const mcpConfigPath = path.join(targetDir, '.zed', 'settings.json');
if (!fs.existsSync(mcpConfigPath)) {
log('debug', '[Zed] No .zed/settings.json found to clean up');
return;
}
try {
// Read the current config
const configContent = fs.readFileSync(mcpConfigPath, 'utf8');
const config = JSON.parse(configContent);
// Check if it has the context_servers section and task-master-ai server
if (
config['context_servers'] &&
config['context_servers']['task-master-ai']
) {
// Remove task-master-ai server
delete config['context_servers']['task-master-ai'];
// Check if there are other MCP servers in context_servers
const remainingServers = Object.keys(config['context_servers']);
if (remainingServers.length === 0) {
// No other servers, remove entire context_servers section
delete config['context_servers'];
log('debug', '[Zed] Removed empty context_servers section');
}
// Check if config is now empty
const remainingKeys = Object.keys(config);
if (remainingKeys.length === 0) {
// Config is empty, remove entire file
fs.rmSync(mcpConfigPath, { force: true });
log('info', '[Zed] Removed empty settings.json file');
// Check if .zed directory is empty
const zedDirPath = path.join(targetDir, '.zed');
if (fs.existsSync(zedDirPath)) {
const remainingContents = fs.readdirSync(zedDirPath);
if (remainingContents.length === 0) {
fs.rmSync(zedDirPath, { recursive: true, force: true });
log('debug', '[Zed] Removed empty .zed directory');
}
}
} else {
// Write back the modified config
fs.writeFileSync(
mcpConfigPath,
JSON.stringify(config, null, '\t') + '\n'
);
log(
'info',
'[Zed] Removed TaskMaster from settings.json, preserved other configurations'
);
}
} else {
log('debug', '[Zed] TaskMaster not found in context_servers');
}
} catch (error) {
log('error', `[Zed] Failed to clean up settings.json: ${error.message}`);
}
}
function onPostConvertRulesProfile(targetDir, assetsDir) {
// Handle .rules setup (same as onAddRulesProfile)
onAddRulesProfile(targetDir, assetsDir);
// Transform MCP config to Zed format
const mcpConfigPath = path.join(targetDir, '.zed', 'settings.json');
if (!fs.existsSync(mcpConfigPath)) {
log('debug', '[Zed] No .zed/settings.json found to transform');
return;
}
try {
// Read the generated standard MCP config
const mcpConfigContent = fs.readFileSync(mcpConfigPath, 'utf8');
const mcpConfig = JSON.parse(mcpConfigContent);
// Check if it's already in Zed format (has context_servers)
if (mcpConfig['context_servers']) {
log(
'info',
'[Zed] settings.json already in Zed format, skipping transformation'
);
return;
}
// Transform to Zed format
const zedConfig = transformToZedFormat(mcpConfig);
// Write back the transformed config with proper formatting
fs.writeFileSync(
mcpConfigPath,
JSON.stringify(zedConfig, null, '\t') + '\n'
);
log('info', '[Zed] Transformed settings.json to Zed format');
log('debug', '[Zed] Renamed mcpServers to context_servers');
} catch (error) {
log('error', `[Zed] Failed to transform settings.json: ${error.message}`);
}
}
// Create and export zed profile using the base factory
export const zedProfile = createProfile({
name: 'zed',
displayName: 'Zed',
url: 'zed.dev',
docsUrl: 'zed.dev/docs',
profileDir: '.zed',
rulesDir: '.',
mcpConfig: true,
mcpConfigName: 'settings.json',
includeDefaultRules: false,
fileMap: {
'AGENTS.md': '.rules'
},
onAdd: onAddRulesProfile,
onRemove: onRemoveRulesProfile,
onPostConvert: onPostConvertRulesProfile
});
// Export lifecycle functions separately to avoid naming conflicts
export { onAddRulesProfile, onRemoveRulesProfile, onPostConvertRulesProfile };

View File

@@ -113,12 +113,12 @@ export async function runInteractiveProfilesSetup() {
const hasMcpConfig = profile.mcpConfig === true;
if (!profile.includeDefaultRules) {
// Integration guide profiles (claude, codex, gemini, amp) - don't include standard coding rules
// Integration guide profiles (claude, codex, gemini, zed, amp) - don't include standard coding rules
if (profileName === 'claude') {
description = 'Integration guide with Task Master slash commands';
} else if (profileName === 'codex') {
description = 'Comprehensive Task Master integration guide';
} else if (profileName === 'gemini') {
} else if (profileName === 'gemini' || profileName === 'zed') {
description = 'Integration guide and MCP config';
} else if (profileName === 'amp') {
description = 'Integration guide and MCP config';