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
JavaScript
#!/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