UNPKG

rulesync

Version:

Unified AI rules management CLI tool that generates configuration files for various AI development tools

1,545 lines (1,498 loc) 148 kB
#!/usr/bin/env node "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // src/types/tool-targets.ts function isToolTarget(target) { if (!target) return false; return ALL_TOOL_TARGETS.some((validTarget) => validTarget === target); } var import_mini, ALL_TOOL_TARGETS, ToolTargetSchema, ToolTargetsSchema, WildcardTargetSchema, RulesyncTargetsSchema; var init_tool_targets = __esm({ "src/types/tool-targets.ts"() { "use strict"; import_mini = require("zod/mini"); ALL_TOOL_TARGETS = [ "augmentcode", "augmentcode-legacy", "copilot", "cursor", "cline", "claudecode", "codexcli", "roo", "geminicli", "kiro", "junie" ]; ToolTargetSchema = import_mini.z.enum(ALL_TOOL_TARGETS); ToolTargetsSchema = import_mini.z.array(ToolTargetSchema); WildcardTargetSchema = import_mini.z.tuple([import_mini.z.literal("*")]); RulesyncTargetsSchema = import_mini.z.union([ToolTargetsSchema, WildcardTargetSchema]); } }); // src/utils/mcp-helpers.ts function shouldIncludeServer(server, targetTool) { if (!server.targets || server.targets.length === 0) { return true; } const parsedTargets = RulesyncTargetsSchema.parse(server.targets); if (parsedTargets.length === 1 && parsedTargets[0] === "*") { return true; } const validatedTool = ToolTargetSchema.parse(targetTool); for (const target of parsedTargets) { if (target === validatedTool) { return true; } } return false; } var init_mcp_helpers = __esm({ "src/utils/mcp-helpers.ts"() { "use strict"; init_tool_targets(); } }); // src/generators/mcp/augmentcode.ts function generateAugmentcodeMcp(config) { const augmentSettings = { mcpServers: [] }; const shouldInclude = (server) => { return shouldIncludeServer(server, "augmentcode"); }; for (const [serverName, server] of Object.entries(config.mcpServers)) { if (!shouldInclude(server)) continue; const augmentServer = { name: serverName }; if (server.command) { augmentServer.command = server.command; if (server.args) { augmentServer.args = server.args; } } else if (server.url || server.httpUrl) { const url = server.httpUrl || server.url; if (url) { augmentServer.url = url; } if (server.httpUrl || server.transport === "http") { augmentServer.transport = "http"; } else if (server.transport === "sse") { augmentServer.transport = "sse"; } if (server.env) { augmentServer.headers = server.env; } } if (server.env && server.command) { augmentServer.env = server.env; } if (server.timeout) { augmentServer.timeout = server.timeout; } if (server.disabled !== void 0) { augmentServer.enabled = !server.disabled; } if (server.networkTimeout && server.networkTimeout > 0) { augmentServer.retries = Math.max(1, Math.floor(server.networkTimeout / 3e4)); } if (augmentSettings.mcpServers) { augmentSettings.mcpServers.push(augmentServer); } } return JSON.stringify(augmentSettings, null, 2); } var init_augmentcode = __esm({ "src/generators/mcp/augmentcode.ts"() { "use strict"; init_mcp_helpers(); } }); // src/generators/mcp/shared-factory.ts function generateMcpConfig(config, toolConfig) { const servers = {}; for (const [serverName, server] of Object.entries(config.mcpServers)) { if (!shouldIncludeServer(server, toolConfig.target)) continue; servers[serverName] = toolConfig.serverTransform(server, serverName); } const finalConfig = toolConfig.configWrapper(servers); return JSON.stringify(finalConfig, null, 2); } var configWrappers; var init_shared_factory = __esm({ "src/generators/mcp/shared-factory.ts"() { "use strict"; init_mcp_helpers(); configWrappers = { /** * Standard mcpServers wrapper */ mcpServers: (servers) => ({ mcpServers: servers }), /** * Servers-only wrapper (for tools that use "servers" instead of "mcpServers") */ servers: (servers) => ({ servers }) }; } }); // src/generators/mcp/claudecode.ts function generateClaudeMcp(config) { return generateMcpConfig(config, { target: "claudecode", configPaths: [".claude/settings.json"], serverTransform: (server) => { const claudeServer = {}; if (server.command) { claudeServer.command = server.command; if (server.args) claudeServer.args = server.args; } else if (server.url || server.httpUrl) { const url = server.httpUrl || server.url; if (url) { claudeServer.url = url; } if (server.httpUrl) { claudeServer.transport = "http"; } else if (server.transport === "sse") { claudeServer.transport = "sse"; } } if (server.env) { claudeServer.env = server.env; } return claudeServer; }, configWrapper: configWrappers.mcpServers }); } var init_claudecode = __esm({ "src/generators/mcp/claudecode.ts"() { "use strict"; init_shared_factory(); } }); // src/generators/mcp/cline.ts function generateClineMcp(config) { return generateMcpConfig(config, { target: "cline", configPaths: [".cline/mcp.json"], serverTransform: (server) => { const clineServer = {}; if (server.command) { clineServer.command = server.command; if (server.args) clineServer.args = server.args; } else if (server.url) { clineServer.url = server.url; } if (server.env) { clineServer.env = server.env; } if (server.disabled !== void 0) { clineServer.disabled = server.disabled; } if (server.alwaysAllow) { clineServer.alwaysAllow = server.alwaysAllow; } if (server.networkTimeout !== void 0) { clineServer.networkTimeout = server.networkTimeout; } return clineServer; }, configWrapper: configWrappers.mcpServers }); } var init_cline = __esm({ "src/generators/mcp/cline.ts"() { "use strict"; init_shared_factory(); } }); // src/generators/mcp/codexcli.ts function generateCodexMcp(config) { return generateMcpConfig(config, { target: "codexcli", configPaths: [".codex/mcp-config.json"], serverTransform: (server) => { const codexServer = {}; if (server.command) { codexServer.command = server.command; if (server.args) codexServer.args = server.args; codexServer.transport = server.transport || "stdio"; } else if (server.url || server.httpUrl) { const url = server.httpUrl || server.url; if (url) { codexServer.url = url; } if (server.httpUrl) { codexServer.transport = "http"; } else if (server.transport === "sse") { codexServer.transport = "sse"; } else if (server.transport === "http") { codexServer.transport = "http"; } else { codexServer.transport = "stdio"; } } else { codexServer.transport = "stdio"; } if (server.env) { codexServer.env = { ...server.env }; if (!codexServer.env.CODEX_DEFAULT_MODEL) { codexServer.env.CODEX_DEFAULT_MODEL = "gpt-4o-mini"; } } if (server.cwd) { codexServer.cwd = server.cwd; codexServer.workingDirectory = server.cwd; } if (server.timeout) { codexServer.timeout = server.timeout; } if (server.headers) { codexServer.headers = server.headers; } return codexServer; }, configWrapper: (servers) => ({ // Configuration format for MCP wrapper servers that expose Codex CLI functionality servers, _comment: "Configuration for MCP wrapper servers like openai-codex-mcp that integrate with Codex CLI", _usage: "This file is intended for use with third-party MCP servers that wrap Codex CLI functionality", _examples: { python_server: "python -m mcp_server or uvicorn codex_server:app", nodejs_server: "node dist/server.js or npm start", docker_server: "docker run -i --rm custom/codex-mcp:latest" }, _security_note: "Store API keys in environment variables, not in this configuration file" }) }); } var init_codexcli = __esm({ "src/generators/mcp/codexcli.ts"() { "use strict"; init_shared_factory(); } }); // src/generators/mcp/copilot.ts function generateCopilotMcp(config, target) { const servers = {}; const inputs = []; const inputMap = /* @__PURE__ */ new Map(); for (const [serverName, server] of Object.entries(config.mcpServers)) { if (!shouldIncludeServer(server, "copilot")) continue; const copilotServer = {}; if (server.command) { copilotServer.command = server.command; if (server.args) copilotServer.args = server.args; } else if (server.url || server.httpUrl) { const url = server.httpUrl || server.url; if (url) { copilotServer.url = url; } } if (server.env) { copilotServer.env = {}; for (const [key, value] of Object.entries(server.env)) { if (target === "editor" && value.includes("SECRET")) { const inputId = `${serverName}_${key}`; inputMap.set(inputId, value); copilotServer.env[key] = `\${input:${inputId}}`; inputs.push({ id: inputId, type: "password", description: `${key} for ${serverName}` }); } else { copilotServer.env[key] = value; } } } if (server.tools) { copilotServer.tools = server.tools; } else if (server.alwaysAllow) { copilotServer.tools = server.alwaysAllow; } servers[serverName] = copilotServer; } if (target === "codingAgent") { const config2 = { mcpServers: servers }; return JSON.stringify(config2, null, 2); } else { const config2 = { servers }; if (inputs.length > 0) { config2.inputs = inputs; } return JSON.stringify(config2, null, 2); } } var init_copilot = __esm({ "src/generators/mcp/copilot.ts"() { "use strict"; init_mcp_helpers(); } }); // src/generators/mcp/cursor.ts function generateCursorMcp(config) { const cursorConfig = { mcpServers: {} }; for (const [serverName, server] of Object.entries(config.mcpServers)) { if (!shouldIncludeServer(server, "cursor")) continue; const cursorServer = {}; if (server.command) { cursorServer.command = server.command; if (server.args) cursorServer.args = server.args; } else if (server.url || server.httpUrl) { const url = server.httpUrl || server.url; if (url) { cursorServer.url = url; } if (server.httpUrl || server.transport === "http") { cursorServer.type = "streamable-http"; } else if (server.transport === "sse" || server.type === "sse") { cursorServer.type = "sse"; } } if (server.env) { cursorServer.env = server.env; } if (server.cwd) { cursorServer.cwd = server.cwd; } cursorConfig.mcpServers[serverName] = cursorServer; } return JSON.stringify(cursorConfig, null, 2); } var init_cursor = __esm({ "src/generators/mcp/cursor.ts"() { "use strict"; init_mcp_helpers(); } }); // src/generators/mcp/geminicli.ts function generateGeminiCliMcp(config) { const geminiSettings = { mcpServers: {} }; for (const [serverName, server] of Object.entries(config.mcpServers)) { if (!shouldIncludeServer(server, "geminicli")) continue; const geminiServer = {}; if (server.command) { geminiServer.command = server.command; if (server.args) geminiServer.args = server.args; } else if (server.url || server.httpUrl) { if (server.httpUrl) { geminiServer.httpUrl = server.httpUrl; } else if (server.url) { geminiServer.url = server.url; } } if (server.env) { geminiServer.env = {}; for (const [key, value] of Object.entries(server.env)) { if (value.startsWith("${") && value.endsWith("}")) { geminiServer.env[key] = value; } else { geminiServer.env[key] = `\${${value}}`; } } } if (server.timeout !== void 0) { geminiServer.timeout = server.timeout; } if (server.trust !== void 0) { geminiServer.trust = server.trust; } geminiSettings.mcpServers[serverName] = geminiServer; } return JSON.stringify(geminiSettings, null, 2); } var init_geminicli = __esm({ "src/generators/mcp/geminicli.ts"() { "use strict"; init_mcp_helpers(); } }); // src/generators/mcp/junie.ts function generateJunieMcp(config) { const junieConfig = { mcpServers: {} }; for (const [serverName, server] of Object.entries(config.mcpServers)) { if (!shouldIncludeServer(server, "junie")) continue; const junieServer = { name: serverName }; if (server.command) { junieServer.command = server.command; if (server.args) junieServer.args = server.args; } else if (server.url || server.httpUrl) { if (server.httpUrl) { junieServer.httpUrl = server.httpUrl; } else if (server.url) { junieServer.url = server.url; } } if (server.env) { junieServer.env = server.env; } if (server.cwd) { junieServer.workingDirectory = server.cwd; } if (server.timeout !== void 0) { junieServer.timeout = server.timeout; } if (server.trust !== void 0) { junieServer.trust = server.trust; } if (server.transport) { if (String(server.transport) === "streamable-http") { junieServer.transport = "http"; } else if (server.transport === "stdio" || server.transport === "http" || server.transport === "sse") { junieServer.transport = server.transport; } } else if (server.command) { junieServer.transport = "stdio"; } junieConfig.mcpServers[serverName] = junieServer; } return JSON.stringify(junieConfig, null, 2); } var init_junie = __esm({ "src/generators/mcp/junie.ts"() { "use strict"; init_mcp_helpers(); } }); // src/generators/mcp/kiro.ts function generateKiroMcp(config) { const kiroConfig = { mcpServers: {} }; for (const [serverName, server] of Object.entries(config.mcpServers)) { if (!shouldIncludeServer(server, "kiro")) continue; const kiroServer = {}; if (server.command) { kiroServer.command = server.command; if (server.args) kiroServer.args = server.args; } else if (server.url || server.httpUrl) { const url = server.httpUrl || server.url; if (url) { kiroServer.url = url; } if (server.httpUrl || server.transport === "http") { kiroServer.transport = "streamable-http"; } else if (server.transport === "sse" || server.type === "sse") { kiroServer.transport = "sse"; } } if (server.env) { kiroServer.env = server.env; } if (server.timeout) { kiroServer.timeout = server.timeout; } if (server.disabled !== void 0) { kiroServer.disabled = server.disabled; } if (server.transport) { kiroServer.transport = server.transport; } if (server.kiroAutoApprove) { kiroServer.autoApprove = server.kiroAutoApprove; } if (server.kiroAutoBlock) { kiroServer.autoBlock = server.kiroAutoBlock; } kiroConfig.mcpServers[serverName] = kiroServer; } return JSON.stringify(kiroConfig, null, 2); } var init_kiro = __esm({ "src/generators/mcp/kiro.ts"() { "use strict"; init_mcp_helpers(); } }); // src/generators/mcp/roo.ts function generateRooMcp(config) { const rooConfig = { mcpServers: {} }; for (const [serverName, server] of Object.entries(config.mcpServers)) { if (!shouldIncludeServer(server, "roo")) continue; const rooServer = {}; if (server.command) { rooServer.command = server.command; if (server.args) rooServer.args = server.args; } else if (server.url || server.httpUrl) { const url = server.httpUrl || server.url; if (url) { rooServer.url = url; } if (server.httpUrl || server.transport === "http") { rooServer.type = "streamable-http"; } else if (server.transport === "sse" || server.type === "sse") { rooServer.type = "sse"; } } if (server.env) { rooServer.env = {}; for (const [key, value] of Object.entries(server.env)) { if (value.startsWith("${env:") && value.endsWith("}")) { rooServer.env[key] = value; } else { rooServer.env[key] = `\${env:${value}}`; } } } if (server.disabled !== void 0) { rooServer.disabled = server.disabled; } if (server.alwaysAllow) { rooServer.alwaysAllow = server.alwaysAllow; } if (server.networkTimeout !== void 0) { rooServer.networkTimeout = Math.max(3e4, Math.min(3e5, server.networkTimeout)); } rooConfig.mcpServers[serverName] = rooServer; } return JSON.stringify(rooConfig, null, 2); } var init_roo = __esm({ "src/generators/mcp/roo.ts"() { "use strict"; init_mcp_helpers(); } }); // src/cli/index.ts var import_commander = require("commander"); // src/cli/commands/add.ts var import_promises = require("fs/promises"); var path = __toESM(require("path"), 1); // src/utils/config.ts init_tool_targets(); function getDefaultConfig() { return { aiRulesDir: ".rulesync", outputPaths: { augmentcode: ".", "augmentcode-legacy": ".", copilot: ".github/instructions", cursor: ".cursor/rules", cline: ".clinerules", claudecode: ".", codexcli: ".", roo: ".roo/rules", geminicli: ".gemini/memories", kiro: ".kiro/steering", junie: "." }, watchEnabled: false, defaultTargets: ALL_TOOL_TARGETS.filter((tool) => tool !== "augmentcode-legacy") }; } function resolveTargets(targets, config) { if (targets.length === 1 && targets[0] === "*") { return config.defaultTargets; } const validatedTargets = ToolTargetsSchema.parse(targets); return validatedTargets; } // src/cli/commands/add.ts function sanitizeFilename(filename) { return filename.endsWith(".md") ? filename.slice(0, -3) : filename; } function generateRuleTemplate(filename) { return `--- root: false targets: ["*"] description: "Rules for ${filename}" globs: [] --- # ${filename.charAt(0).toUpperCase() + filename.slice(1)} Rules Add your rules here. `; } async function addCommand(filename) { try { const config = getDefaultConfig(); const sanitizedFilename = sanitizeFilename(filename); const rulesDir = config.aiRulesDir; const filePath = path.join(rulesDir, `${sanitizedFilename}.md`); await (0, import_promises.mkdir)(rulesDir, { recursive: true }); const template = generateRuleTemplate(sanitizedFilename); await (0, import_promises.writeFile)(filePath, template, "utf8"); console.log(`\u2705 Created rule file: ${filePath}`); console.log(`\u{1F4DD} Edit the file to customize your rules.`); } catch (error) { console.error( `\u274C Failed to create rule file: ${error instanceof Error ? error.message : String(error)}` ); process.exit(3); } } // src/cli/commands/config.ts var import_node_fs = require("fs"); var import_node_path2 = __toESM(require("path"), 1); // src/types/claudecode.ts var import_mini2 = require("zod/mini"); var ClaudeSettingsSchema = import_mini2.z.looseObject({ permissions: import_mini2.z._default( import_mini2.z.looseObject({ deny: import_mini2.z._default(import_mini2.z.array(import_mini2.z.string()), []) }), { deny: [] } ) }); // src/types/config.ts var import_mini3 = require("zod/mini"); init_tool_targets(); var ConfigSchema = import_mini3.z.object({ aiRulesDir: import_mini3.z.string(), outputPaths: import_mini3.z.record(ToolTargetSchema, import_mini3.z.string()), watchEnabled: import_mini3.z.boolean(), defaultTargets: ToolTargetsSchema }); // src/types/config-options.ts var import_mini4 = require("zod/mini"); init_tool_targets(); var OutputPathsSchema = import_mini4.z.object({ augmentcode: import_mini4.z.optional(import_mini4.z.string()), "augmentcode-legacy": import_mini4.z.optional(import_mini4.z.string()), copilot: import_mini4.z.optional(import_mini4.z.string()), cursor: import_mini4.z.optional(import_mini4.z.string()), cline: import_mini4.z.optional(import_mini4.z.string()), claudecode: import_mini4.z.optional(import_mini4.z.string()), codexcli: import_mini4.z.optional(import_mini4.z.string()), roo: import_mini4.z.optional(import_mini4.z.string()), geminicli: import_mini4.z.optional(import_mini4.z.string()), kiro: import_mini4.z.optional(import_mini4.z.string()), junie: import_mini4.z.optional(import_mini4.z.string()) }); var ConfigOptionsSchema = import_mini4.z.object({ aiRulesDir: import_mini4.z.optional(import_mini4.z.string()), outputPaths: import_mini4.z.optional(OutputPathsSchema), watchEnabled: import_mini4.z.optional(import_mini4.z.boolean()), defaultTargets: import_mini4.z.optional(ToolTargetsSchema), targets: import_mini4.z.optional(import_mini4.z.array(ToolTargetSchema)), exclude: import_mini4.z.optional(import_mini4.z.array(ToolTargetSchema)), verbose: import_mini4.z.optional(import_mini4.z.boolean()), delete: import_mini4.z.optional(import_mini4.z.boolean()), baseDir: import_mini4.z.optional(import_mini4.z.union([import_mini4.z.string(), import_mini4.z.array(import_mini4.z.string())])), watch: import_mini4.z.optional( import_mini4.z.object({ enabled: import_mini4.z.optional(import_mini4.z.boolean()), interval: import_mini4.z.optional(import_mini4.z.number()), ignore: import_mini4.z.optional(import_mini4.z.array(import_mini4.z.string())) }) ) }); var MergedConfigSchema = import_mini4.z.object({ aiRulesDir: import_mini4.z.string(), outputPaths: import_mini4.z.record(ToolTargetSchema, import_mini4.z.string()), watchEnabled: import_mini4.z.boolean(), defaultTargets: ToolTargetsSchema, targets: import_mini4.z.optional(import_mini4.z.array(ToolTargetSchema)), exclude: import_mini4.z.optional(import_mini4.z.array(ToolTargetSchema)), verbose: import_mini4.z.optional(import_mini4.z.boolean()), delete: import_mini4.z.optional(import_mini4.z.boolean()), baseDir: import_mini4.z.optional(import_mini4.z.union([import_mini4.z.string(), import_mini4.z.array(import_mini4.z.string())])), configPath: import_mini4.z.optional(import_mini4.z.string()), watch: import_mini4.z.optional( import_mini4.z.object({ enabled: import_mini4.z.optional(import_mini4.z.boolean()), interval: import_mini4.z.optional(import_mini4.z.number()), ignore: import_mini4.z.optional(import_mini4.z.array(import_mini4.z.string())) }) ) }); // src/types/mcp.ts var import_mini5 = require("zod/mini"); init_tool_targets(); var McpTransportTypeSchema = import_mini5.z.enum(["stdio", "sse", "http"]); var McpServerBaseSchema = import_mini5.z.object({ command: import_mini5.z.optional(import_mini5.z.string()), args: import_mini5.z.optional(import_mini5.z.array(import_mini5.z.string())), url: import_mini5.z.optional(import_mini5.z.string()), httpUrl: import_mini5.z.optional(import_mini5.z.string()), env: import_mini5.z.optional(import_mini5.z.record(import_mini5.z.string(), import_mini5.z.string())), disabled: import_mini5.z.optional(import_mini5.z.boolean()), networkTimeout: import_mini5.z.optional(import_mini5.z.number()), timeout: import_mini5.z.optional(import_mini5.z.number()), trust: import_mini5.z.optional(import_mini5.z.boolean()), cwd: import_mini5.z.optional(import_mini5.z.string()), transport: import_mini5.z.optional(McpTransportTypeSchema), type: import_mini5.z.optional(import_mini5.z.enum(["sse", "streamable-http"])), alwaysAllow: import_mini5.z.optional(import_mini5.z.array(import_mini5.z.string())), tools: import_mini5.z.optional(import_mini5.z.array(import_mini5.z.string())), kiroAutoApprove: import_mini5.z.optional(import_mini5.z.array(import_mini5.z.string())), kiroAutoBlock: import_mini5.z.optional(import_mini5.z.array(import_mini5.z.string())), headers: import_mini5.z.optional(import_mini5.z.record(import_mini5.z.string(), import_mini5.z.string())) }); var RulesyncMcpServerSchema = import_mini5.z.extend(McpServerBaseSchema, { targets: import_mini5.z.optional(RulesyncTargetsSchema) }); var McpConfigSchema = import_mini5.z.object({ mcpServers: import_mini5.z.record(import_mini5.z.string(), McpServerBaseSchema) }); var RulesyncMcpConfigSchema = import_mini5.z.object({ mcpServers: import_mini5.z.record(import_mini5.z.string(), RulesyncMcpServerSchema) }); // src/types/rules.ts var import_mini6 = require("zod/mini"); init_tool_targets(); var RuleFrontmatterSchema = import_mini6.z.object({ root: import_mini6.z.boolean(), targets: RulesyncTargetsSchema, description: import_mini6.z.string(), globs: import_mini6.z.array(import_mini6.z.string()), cursorRuleType: import_mini6.z.optional(import_mini6.z.enum(["always", "manual", "specificFiles", "intelligently"])), tags: import_mini6.z.optional(import_mini6.z.array(import_mini6.z.string())) }); var ParsedRuleSchema = import_mini6.z.object({ frontmatter: RuleFrontmatterSchema, content: import_mini6.z.string(), filename: import_mini6.z.string(), filepath: import_mini6.z.string() }); var GeneratedOutputSchema = import_mini6.z.object({ tool: ToolTargetSchema, filepath: import_mini6.z.string(), content: import_mini6.z.string() }); var GenerateOptionsSchema = import_mini6.z.object({ targetTools: import_mini6.z.optional(ToolTargetsSchema), outputDir: import_mini6.z.optional(import_mini6.z.string()), watch: import_mini6.z.optional(import_mini6.z.boolean()) }); // src/types/index.ts init_tool_targets(); // src/utils/config-loader.ts var import_c12 = require("c12"); var import_core = require("zod/v4/core"); var MODULE_NAME = "rulesync"; async function loadConfig(options = {}) { const defaultConfig = getDefaultConfig(); if (options.noConfig) { return { config: defaultConfig, isEmpty: true }; } try { const loadOptions = { name: MODULE_NAME, cwd: options.cwd || process.cwd(), rcFile: false, // Disable rc file lookup configFile: "rulesync", // Will look for rulesync.jsonc, rulesync.ts, etc. defaults: defaultConfig }; if (options.configPath) { loadOptions.configFile = options.configPath; } const { config, configFile } = await (0, import_c12.loadConfig)(loadOptions); if (!config || Object.keys(config).length === 0) { return { config: defaultConfig, isEmpty: true }; } try { ConfigOptionsSchema.parse(config); } catch (error) { if (error instanceof import_core.$ZodError) { const issues = error.issues.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`).join("\n"); throw new Error(`Invalid configuration in ${configFile}: ${issues}`); } throw error; } const processedConfig = postProcessConfig(config); const result = { config: processedConfig, isEmpty: false }; if (configFile) { result.filepath = configFile; } return result; } catch (error) { throw new Error( `Failed to load configuration: ${error instanceof Error ? error.message : String(error)}` ); } } function postProcessConfig(config) { const processed = { ...config }; if (processed.baseDir && !Array.isArray(processed.baseDir)) { processed.baseDir = [processed.baseDir]; } if (config.targets || config.exclude) { const baseTargets = config.targets || processed.defaultTargets; if (config.exclude && config.exclude.length > 0) { processed.defaultTargets = baseTargets.filter( (target) => config.exclude && !config.exclude.includes(target) ); } else { processed.defaultTargets = baseTargets; } } return processed; } function generateMinimalConfig(options) { if (!options || Object.keys(options).length === 0) { return generateSampleConfig(); } const lines = ["{"]; if (options.targets || options.exclude) { lines.push(` // Available tools: ${ALL_TOOL_TARGETS.join(", ")}`); } if (options.targets) { lines.push(` "targets": ${JSON.stringify(options.targets)}`); } if (options.exclude) { const comma = lines.length > 1 ? "," : ""; if (comma) lines[lines.length - 1] += comma; lines.push(` "exclude": ${JSON.stringify(options.exclude)}`); } if (options.aiRulesDir) { const comma = lines.length > 1 ? "," : ""; if (comma) lines[lines.length - 1] += comma; lines.push(` "aiRulesDir": "${options.aiRulesDir}"`); } if (options.outputPaths) { const comma = lines.length > 1 ? "," : ""; if (comma) lines[lines.length - 1] += comma; lines.push( ` "outputPaths": ${JSON.stringify(options.outputPaths, null, 4).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n")}` ); } if (options.baseDir) { const comma = lines.length > 1 ? "," : ""; if (comma) lines[lines.length - 1] += comma; lines.push(` "baseDir": ${JSON.stringify(options.baseDir)}`); } if (options.delete !== void 0) { const comma = lines.length > 1 ? "," : ""; if (comma) lines[lines.length - 1] += comma; lines.push(` "delete": ${options.delete}`); } if (options.verbose !== void 0) { const comma = lines.length > 1 ? "," : ""; if (comma) lines[lines.length - 1] += comma; lines.push(` "verbose": ${options.verbose}`); } if (options.watch) { const comma = lines.length > 1 ? "," : ""; if (comma) lines[lines.length - 1] += comma; lines.push( ` "watch": ${JSON.stringify(options.watch, null, 4).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n")}` ); } lines.push("}"); return lines.join("\n"); } function generateSampleConfig(options) { const targets = options?.targets || ALL_TOOL_TARGETS; const excludeValue = options?.exclude ? JSON.stringify(options.exclude) : null; const aiRulesDir = options?.aiRulesDir || null; const baseDir = options?.baseDir || null; const deleteFlag = options?.delete || false; const verbose = options?.verbose !== void 0 ? options.verbose : true; return `{ // List of tools to generate configurations for // Available: ${ALL_TOOL_TARGETS.join(", ")} "targets": ${JSON.stringify(targets)}, // Tools to exclude from generation (overrides targets) ${excludeValue ? `"exclude": ${excludeValue},` : '// "exclude": ["roo"],'} ${aiRulesDir ? ` // Directory containing AI rule files "aiRulesDir": "${aiRulesDir}",` : ""} // Custom output paths for specific tools "outputPaths": { "copilot": ".github/copilot-instructions.md" }, ${baseDir ? ` // Base directory for generation "baseDir": "${baseDir}",` : ` // Base directory or directories for generation // "baseDir": "./packages", // "baseDir": ["./packages/frontend", "./packages/backend"],`} // Delete existing files before generating "delete": ${deleteFlag}, // Enable verbose output "verbose": ${verbose}, // Watch configuration "watch": { "enabled": false, "interval": 1000, "ignore": ["node_modules/**", "dist/**"] } } `; } function mergeWithCliOptions(config, cliOptions) { const merged = { ...config }; if (cliOptions.verbose !== void 0) { merged.verbose = cliOptions.verbose; } if (cliOptions.delete !== void 0) { merged.delete = cliOptions.delete; } if (cliOptions.baseDirs && cliOptions.baseDirs.length > 0) { merged.baseDir = cliOptions.baseDirs; } if (cliOptions.tools && cliOptions.tools.length > 0) { merged.defaultTargets = cliOptions.tools; merged.exclude = void 0; } return merged; } // src/utils/error.ts function getErrorMessage(error) { return error instanceof Error ? error.message : String(error); } function formatErrorWithContext(error, context) { const errorMessage = getErrorMessage(error); return `${context}: ${errorMessage}`; } function createErrorResult(error, context) { const errorMessage = context ? formatErrorWithContext(error, context) : getErrorMessage(error); return { success: false, error: errorMessage }; } function createSuccessResult(result) { return { success: true, result }; } async function safeAsyncOperation(operation, errorContext) { try { const result = await operation(); return createSuccessResult(result); } catch (error) { return createErrorResult(error, errorContext); } } // src/utils/file.ts var import_promises2 = require("fs/promises"); var import_node_path = require("path"); async function ensureDir(dirPath) { try { await (0, import_promises2.stat)(dirPath); } catch { await (0, import_promises2.mkdir)(dirPath, { recursive: true }); } } function resolvePath(relativePath, baseDir) { return baseDir ? (0, import_node_path.join)(baseDir, relativePath) : relativePath; } async function readFileContent(filepath) { return (0, import_promises2.readFile)(filepath, "utf-8"); } async function writeFileContent(filepath, content) { await ensureDir((0, import_node_path.dirname)(filepath)); await (0, import_promises2.writeFile)(filepath, content, "utf-8"); } async function fileExists(filepath) { try { await (0, import_promises2.stat)(filepath); return true; } catch { return false; } } async function findFiles(dir, extension = ".md") { try { const files = await (0, import_promises2.readdir)(dir); return files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path.join)(dir, file)); } catch { return []; } } async function removeDirectory(dirPath) { const dangerousPaths = [".", "/", "~", "src", "node_modules"]; if (dangerousPaths.includes(dirPath) || dirPath === "") { console.warn(`Skipping deletion of dangerous path: ${dirPath}`); return; } try { if (await fileExists(dirPath)) { await (0, import_promises2.rm)(dirPath, { recursive: true, force: true }); } } catch (error) { console.warn(`Failed to remove directory ${dirPath}:`, error); } } async function removeFile(filepath) { try { if (await fileExists(filepath)) { await (0, import_promises2.rm)(filepath); } } catch (error) { console.warn(`Failed to remove file ${filepath}:`, error); } } async function removeClaudeGeneratedFiles() { const filesToRemove = ["CLAUDE.md", ".claude/memories"]; for (const fileOrDir of filesToRemove) { if (fileOrDir.endsWith("/memories")) { await removeDirectory(fileOrDir); } else { await removeFile(fileOrDir); } } } // src/utils/rules.ts function isToolSpecificRule(rule, targetTool) { const filename = rule.filename; const toolPatterns = { "augmentcode-legacy": /^specification-augmentcode-legacy-/i, augmentcode: /^specification-augmentcode-/i, copilot: /^specification-copilot-/i, cursor: /^specification-cursor-/i, cline: /^specification-cline-/i, claudecode: /^specification-claudecode-/i, roo: /^specification-roo-/i, geminicli: /^specification-geminicli-/i, kiro: /^specification-kiro-/i }; for (const [tool, pattern] of Object.entries(toolPatterns)) { if (pattern.test(filename)) { return tool === targetTool; } } return true; } // src/cli/commands/config.ts async function configCommand(options = {}) { if (options.init) { await initConfig(options); return; } await showConfig(); } async function showConfig() { console.log("Loading configuration...\n"); try { const result = await loadConfig(); if (result.isEmpty) { console.log("No configuration file found. Using default configuration.\n"); } else { console.log(`Configuration loaded from: ${result.filepath} `); } console.log("Current configuration:"); console.log("====================="); const config = result.config; console.log(` AI Rules Directory: ${config.aiRulesDir}`); console.log(` Default Targets: ${config.defaultTargets.join(", ")}`); if (config.exclude && config.exclude.length > 0) { console.log(`Excluded Targets: ${config.exclude.join(", ")}`); } console.log("\nOutput Paths:"); for (const [tool, outputPath] of Object.entries(config.outputPaths)) { console.log(` ${tool}: ${outputPath}`); } if (config.baseDir) { const dirs = Array.isArray(config.baseDir) ? config.baseDir : [config.baseDir]; console.log(` Base Directories: ${dirs.join(", ")}`); } console.log(` Verbose: ${config.verbose || false}`); console.log(`Delete before generate: ${config.delete || false}`); if (config.watch) { console.log("\nWatch Configuration:"); console.log(` Enabled: ${config.watch.enabled || false}`); if (config.watch.interval) { console.log(` Interval: ${config.watch.interval}ms`); } if (config.watch.ignore && config.watch.ignore.length > 0) { console.log(` Ignore patterns: ${config.watch.ignore.join(", ")}`); } } console.log("\nTip: Use 'rulesync config init' to create a configuration file."); } catch (error) { console.error( "\u274C Failed to load configuration:", error instanceof Error ? error.message : String(error) ); process.exit(1); } } var FORMAT_CONFIG = { jsonc: { filename: "rulesync.jsonc", generator: generateJsoncConfig }, ts: { filename: "rulesync.ts", generator: generateTsConfig } }; async function initConfig(options) { const validFormats = Object.keys(FORMAT_CONFIG); const selectedFormat = options.format || "jsonc"; if (!validFormats.includes(selectedFormat)) { console.error( `\u274C Invalid format: ${selectedFormat}. Valid formats are: ${validFormats.join(", ")}` ); process.exit(1); } const formatConfig = FORMAT_CONFIG[selectedFormat]; const filename = formatConfig.filename; const configOptions = {}; if (options.targets) { const targets = options.targets.split(",").map((t) => t.trim()); const validTargets = []; for (const target of targets) { const result = ToolTargetSchema.safeParse(target); if (result.success) { validTargets.push(result.data); } else { console.error(`\u274C Invalid target: ${target}`); process.exit(1); } } configOptions.targets = validTargets; } if (options.exclude) { const excludes = options.exclude.split(",").map((t) => t.trim()); const validExcludes = []; for (const exclude of excludes) { const result = ToolTargetSchema.safeParse(exclude); if (result.success) { validExcludes.push(result.data); } else { console.error(`\u274C Invalid exclude target: ${exclude}`); process.exit(1); } } configOptions.exclude = validExcludes; } if (options.aiRulesDir) { configOptions.aiRulesDir = options.aiRulesDir; } if (options.baseDir) { configOptions.baseDir = options.baseDir; } if (options.verbose !== void 0) { configOptions.verbose = options.verbose; } if (options.delete !== void 0) { configOptions.delete = options.delete; } const content = formatConfig.generator(configOptions); const filepath = import_node_path2.default.join(process.cwd(), filename); try { const fs2 = await import("fs/promises"); await fs2.access(filepath); console.error(`\u274C Configuration file already exists: ${filepath}`); console.log("Remove the existing file or choose a different format."); process.exit(1); } catch { } try { (0, import_node_fs.writeFileSync)(filepath, content, "utf-8"); console.log(`\u2705 Created configuration file: ${filepath}`); console.log("\nYou can now customize the configuration to fit your needs."); console.log("Run 'rulesync generate' to use the new configuration."); } catch (error) { console.error( `\u274C Failed to create configuration file: ${error instanceof Error ? error.message : String(error)}` ); process.exit(1); } } function generateJsoncConfig(options) { if (options && Object.keys(options).length > 0) { return generateMinimalConfig(options); } return generateSampleConfig(options); } function generateTsConfig(options) { if (!options || Object.keys(options).length === 0) { return `import type { ConfigOptions } from "rulesync"; const config: ConfigOptions = { // List of tools to generate configurations for // Available: ${ALL_TOOL_TARGETS.join(", ")} targets: ${JSON.stringify(ALL_TOOL_TARGETS)}, // Custom output paths for specific tools // outputPaths: { // copilot: ".github/copilot-instructions.md", // }, // Delete existing files before generating // delete: false, // Enable verbose output verbose: true, }; export default config;`; } const configLines = []; if (options.targets) { configLines.push(` targets: ${JSON.stringify(options.targets)}`); } if (options.exclude) { configLines.push(` exclude: ${JSON.stringify(options.exclude)}`); } if (options.aiRulesDir) { configLines.push(` aiRulesDir: "${options.aiRulesDir}"`); } if (options.outputPaths) { const pathsStr = JSON.stringify(options.outputPaths, null, 4).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n"); configLines.push(` outputPaths: ${pathsStr}`); } if (options.baseDir) { configLines.push(` baseDir: ${JSON.stringify(options.baseDir)}`); } if (options.delete !== void 0) { configLines.push(` delete: ${options.delete}`); } if (options.verbose !== void 0) { configLines.push(` verbose: ${options.verbose}`); } if (options.watch) { const watchStr = JSON.stringify(options.watch, null, 4).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n"); configLines.push(` watch: ${watchStr}`); } const configContent = `import type { ConfigOptions } from "rulesync"; const config: ConfigOptions = { ${configLines.join(",\n")}, }; export default config; `; return configContent; } // src/cli/commands/generate.ts var import_node_path12 = require("path"); // src/generators/ignore/shared-factory.ts var import_node_path3 = require("path"); // src/generators/ignore/shared-helpers.ts function extractIgnorePatternsFromRules(rules) { const patterns = []; for (const rule of rules) { if (rule.frontmatter.globs && rule.frontmatter.globs.length > 0) { for (const glob of rule.frontmatter.globs) { if (shouldExcludeFromAI(glob)) { patterns.push(`# Exclude: ${rule.frontmatter.description}`); patterns.push(glob); } } } const contentPatterns = extractIgnorePatternsFromContent(rule.content); patterns.push(...contentPatterns); } return patterns; } function shouldExcludeFromAI(glob) { const excludePatterns = [ // Large generated files that slow indexing "**/assets/generated/**", "**/public/build/**", // Test fixtures with potentially sensitive data "**/tests/fixtures/**", "**/test/fixtures/**", "**/*.fixture.*", // Build outputs that provide little value for AI context "**/dist/**", "**/build/**", "**/coverage/**", // Configuration that might contain sensitive data "**/config/production/**", "**/config/secrets/**", "**/config/prod/**", "**/deploy/prod/**", "**/*.prod.*", // Internal documentation that might be sensitive "**/internal/**", "**/internal-docs/**", "**/proprietary/**", "**/personal-notes/**", "**/private/**", "**/confidential/**" ]; return excludePatterns.some((pattern) => { const regex = new RegExp(pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*")); return regex.test(glob); }); } function extractIgnorePatternsFromContent(content) { const patterns = []; const lines = content.split("\n"); for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith("# IGNORE:") || trimmed.startsWith("# aiignore:")) { const pattern = trimmed.replace(/^# (IGNORE|aiignore):\s*/, "").trim(); if (pattern) { patterns.push(pattern); } } if (trimmed.startsWith("# AUGMENT_IGNORE:") || trimmed.startsWith("# augmentignore:")) { const pattern = trimmed.replace(/^# (AUGMENT_IGNORE|augmentignore):\s*/, "").trim(); if (pattern) { patterns.push(pattern); } } if (trimmed.startsWith("# AUGMENT_INCLUDE:") || trimmed.startsWith("# augmentinclude:")) { const pattern = trimmed.replace(/^# (AUGMENT_INCLUDE|augmentinclude):\s*/, "").trim(); if (pattern) { patterns.push(`!${pattern}`); } } if (trimmed.includes("exclude") || trimmed.includes("ignore")) { const matches = trimmed.match(/['"`]([^'"`]+\.(log|tmp|cache|temp))['"`]/g); if (matches) { patterns.push(...matches.map((m) => m.replace(/['"`]/g, ""))); } } } return patterns; } function extractAugmentCodeIgnorePatternsFromContent(content) { const patterns = []; const lines = content.split("\n"); for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith("# AUGMENT_IGNORE:") || trimmed.startsWith("# augmentignore:")) { const pattern = trimmed.replace(/^# (AUGMENT_IGNORE|augmentignore):\s*/, "").trim(); if (pattern) { patterns.push(pattern); } } if (trimmed.startsWith("# AUGMENT_INCLUDE:") || trimmed.startsWith("# augmentinclude:")) { const pattern = trimmed.replace(/^# (AUGMENT_INCLUDE|augmentinclude):\s*/, "").trim(); if (pattern) { patterns.push(`!${pattern}`); } } if (trimmed.includes("large file") || trimmed.includes("binary") || trimmed.includes("media")) { const regex = /['"`]([^'"`]+\.(mp4|avi|zip|tar\.gz|rar|pdf|doc|xlsx))['"`]/g; let match; while ((match = regex.exec(trimmed)) !== null) { if (match[1]) { patterns.push(match[1]); } } } } return patterns; } // src/generators/ignore/shared-factory.ts function generateIgnoreFile(rules, config, ignoreConfig, baseDir) { const outputs = []; const content = generateIgnoreContent(rules, ignoreConfig); const outputPath = baseDir || process.cwd(); const filepath = (0, import_node_path3.join)(outputPath, ignoreConfig.filename); outputs.push({ tool: ignoreConfig.tool, filepath, content }); return outputs; } function generateIgnoreContent(rules, config) { const lines = []; lines.push(...config.header); lines.push(""); if (config.includeCommonPatterns) { lines.push(...getCommonIgnorePatterns()); } if (config.corePatterns.length > 0) { lines.push(...config.corePatterns); lines.push(""); } const rulePatterns = extractIgnorePatternsFromRules(rules); const customPatterns = config.customPatternProcessor ? config.customPatternProcessor(rules) : []; const allPatterns = [...rulePatterns, ...customPatterns]; if (allPatterns.length > 0) { const headerText = config.projectPatternsHeader || "# \u2500\u2500\u2500\u2500\u2500 Project-specific exclusions from rulesync rules \u2500\u2500\u2500\u2500\u2500"; lines.push(headerText); lines.push(...allPatterns); lines.push(""); } return lines.join("\n"); } function getCommonIgnorePatterns() { return [ "# \u2500\u2500\u2500\u2500\u2500 Source Control Metadata \u2500\u2500\u2500\u2500\u2500", ".git/", ".svn/", ".hg/", ".idea/", "*.iml", ".vscode/settings.json", "", "# \u2500\u2500\u2500\u2500\u2500 Build Artifacts \u2500\u2500\u2500\u2500\u2500", "/out/", "/dist/", "/target/", "/build/", "*.class", "*.jar", "*.war", "", "# \u2500\u2500\u2500\u2500\u2500 Secrets & Credentials \u2500\u2500\u2500\u2500\u2500", "# Environment files", ".env", ".env.*", "!.env.example", "", "# Key material", "*.pem", "*.key", "*.crt", "*.p12", "*.pfx", "*.der", "id_rsa*", "id_dsa*", "*.ppk", "", "# Cloud and service configs", "aws-credentials.json", "gcp-service-account*.json", "azure-credentials.json", "secrets/**", "config/secrets/", "**/secrets/", "", "# Database credentials", "database.yml", "**/databa