UNPKG

rulesync

Version:

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

1,685 lines (1,647 loc) 194 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 __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/cli/index.ts var import_node_path57 = require("path"); var import_node_url = require("url"); var import_commander = require("commander"); // src/constants/announcements.ts var ANNOUNCEMENT = "".trim(); // src/types/features.ts var import_mini = require("zod/mini"); var ALL_FEATURES = ["rules", "ignore", "mcp", "subagents", "commands"]; var ALL_FEATURES_WITH_WILDCARD = [...ALL_FEATURES, "*"]; var FeatureSchema = import_mini.z.enum(ALL_FEATURES); var FeaturesSchema = import_mini.z.array(FeatureSchema); var RulesyncFeaturesSchema = import_mini.z.array(import_mini.z.enum(ALL_FEATURES_WITH_WILDCARD)); // src/utils/file.ts var import_node_fs = require("fs"); var import_promises = require("fs/promises"); var import_node_path = require("path"); // src/utils/logger.ts var import_consola = require("consola"); var isEnvTest = process.env.NODE_ENV === "test"; var Logger = class { _verbose = false; console = import_consola.consola.withDefaults({ tag: "rulesync" }); setVerbose(verbose) { this._verbose = verbose; } get verbose() { return this._verbose; } info(message, ...args) { if (isEnvTest) return; this.console.info(message, ...args); } // Success (always shown) success(message, ...args) { if (isEnvTest) return; this.console.success(message, ...args); } // Warning (always shown) warn(message, ...args) { if (isEnvTest) return; this.console.warn(message, ...args); } // Error (always shown) error(message, ...args) { if (isEnvTest) return; this.console.error(message, ...args); } // Debug level (shown only in verbose mode) debug(message, ...args) { if (isEnvTest) return; if (this._verbose) { this.console.info(message, ...args); } } }; var logger = new Logger(); // src/utils/file.ts async function ensureDir(dirPath) { try { await (0, import_promises.stat)(dirPath); } catch { await (0, import_promises.mkdir)(dirPath, { recursive: true }); } } async function readJsonFile(filepath, defaultValue) { try { const content = await readFileContent(filepath); const parsed = JSON.parse(content); return parsed; } catch (error) { if (defaultValue !== void 0) { return defaultValue; } throw error; } } async function directoryExists(dirPath) { try { const stats = await (0, import_promises.stat)(dirPath); return stats.isDirectory(); } catch { return false; } } async function readFileContent(filepath) { return (0, import_promises.readFile)(filepath, "utf-8"); } async function writeFileContent(filepath, content) { logger.debug(`Writing file: ${filepath}`); await ensureDir((0, import_node_path.dirname)(filepath)); await (0, import_promises.writeFile)(filepath, content, "utf-8"); } async function fileExists(filepath) { try { await (0, import_promises.stat)(filepath); return true; } catch { return false; } } async function listDirectoryFiles(dir) { try { return await (0, import_promises.readdir)(dir); } catch { return []; } } async function findFilesByGlobs(globs) { return (0, import_node_fs.globSync)(globs); } async function removeFile(filepath) { logger.debug(`Removing file: ${filepath}`); try { if (await fileExists(filepath)) { await (0, import_promises.rm)(filepath); } } catch (error) { logger.warn(`Failed to remove file ${filepath}:`, error); } } // src/cli/commands/config.ts async function configCommand(options) { if (options.init) { await initConfig(); return; } logger.info(`Please run \`rulesync config --init\` to create a new configuration file`); } async function initConfig() { logger.info("Initializing configuration..."); if (await fileExists("rulesync.jsonc")) { logger.error("rulesync.jsonc already exists"); process.exit(1); } await writeFileContent( "rulesync.jsonc", JSON.stringify( { targets: ["copilot", "cursor", "claudecode", "codexcli"], features: ["rules", "ignore", "mcp", "commands", "subagents"], baseDirs: ["."], delete: true, verbose: false, experimentalSimulateCommands: false, experimentalSimulateSubagents: false }, null, 2 ) ); logger.success("Configuration file created successfully!"); } // src/cli/commands/generate.ts var import_es_toolkit = require("es-toolkit"); // src/commands/commands-processor.ts var import_node_path11 = require("path"); var import_mini8 = require("zod/mini"); // src/types/feature-processor.ts var FeatureProcessor = class { baseDir; constructor({ baseDir = process.cwd() }) { this.baseDir = baseDir; } /** * Return tool targets that this feature supports. */ static getToolTargets(_params = {}) { throw new Error("Not implemented"); } /** * Once converted to rulesync/tool files, write them to the filesystem. * Returns the number of files written. */ async writeAiFiles(aiFiles) { for (const aiFile of aiFiles) { await writeFileContent(aiFile.getFilePath(), aiFile.getFileContent()); } return aiFiles.length; } async removeAiFiles(aiFiles) { for (const aiFile of aiFiles) { await removeFile(aiFile.getFilePath()); } } }; // src/commands/claudecode-command.ts var import_node_path4 = require("path"); var import_mini4 = require("zod/mini"); // src/utils/frontmatter.ts var import_gray_matter = __toESM(require("gray-matter"), 1); function isPlainObject(value) { if (value === null || typeof value !== "object") return false; const prototype = Object.getPrototypeOf(value); return prototype === Object.prototype || prototype === null; } function deepRemoveNullishValue(value) { if (value === null || value === void 0) { return void 0; } if (Array.isArray(value)) { const cleanedArray = value.map((item) => deepRemoveNullishValue(item)).filter((item) => item !== void 0); return cleanedArray; } if (isPlainObject(value)) { const result = {}; for (const [key, val] of Object.entries(value)) { const cleaned = deepRemoveNullishValue(val); if (cleaned !== void 0) { result[key] = cleaned; } } return result; } return value; } function deepRemoveNullishObject(obj) { if (!obj || typeof obj !== "object") { return {}; } const result = {}; for (const [key, val] of Object.entries(obj)) { const cleaned = deepRemoveNullishValue(val); if (cleaned !== void 0) { result[key] = cleaned; } } return result; } function stringifyFrontmatter(body, frontmatter) { const cleanFrontmatter = deepRemoveNullishObject(frontmatter); return import_gray_matter.default.stringify(body, cleanFrontmatter); } function parseFrontmatter(content) { const { data: frontmatter, content: body } = (0, import_gray_matter.default)(content); return { frontmatter, body }; } // src/commands/rulesync-command.ts var import_node_path3 = require("path"); var import_mini3 = require("zod/mini"); // src/types/ai-file.ts var import_node_path2 = __toESM(require("path"), 1); var AiFile = class { /** * @example "." */ baseDir; /** * @example ".claude/agents" */ relativeDirPath; /** * @example "planner.md" */ relativeFilePath; /** * Whole raw file content */ fileContent; constructor({ baseDir = ".", relativeDirPath, relativeFilePath, fileContent, validate = true }) { this.baseDir = baseDir; this.relativeDirPath = relativeDirPath; this.relativeFilePath = relativeFilePath; this.fileContent = fileContent; if (validate) { const result = this.validate(); if (!result.success) { throw result.error; } } } static async fromFile(_params) { throw new Error("Please implement this method in the subclass."); } getBaseDir() { return this.baseDir; } getRelativeDirPath() { return this.relativeDirPath; } getRelativeFilePath() { return this.relativeFilePath; } getFilePath() { return import_node_path2.default.join(this.baseDir, this.relativeDirPath, this.relativeFilePath); } getFileContent() { return this.fileContent; } getRelativePathFromCwd() { return import_node_path2.default.join(this.relativeDirPath, this.relativeFilePath); } setFileContent(newFileContent) { this.fileContent = newFileContent; } }; // src/types/rulesync-file.ts var RulesyncFile = class extends AiFile { static async fromFile(_params) { throw new Error("Please implement this method in the subclass."); } static async fromFileLegacy(_params) { throw new Error("Please implement this method in the subclass."); } }; // src/types/tool-targets.ts var import_mini2 = require("zod/mini"); var ALL_TOOL_TARGETS = [ "agentsmd", "amazonqcli", "augmentcode", "augmentcode-legacy", "copilot", "cursor", "cline", "claudecode", "codexcli", "opencode", "qwencode", "roo", "geminicli", "kiro", "junie", "warp", "windsurf" ]; var ALL_TOOL_TARGETS_WITH_WILDCARD = [...ALL_TOOL_TARGETS, "*"]; var ToolTargetSchema = import_mini2.z.enum(ALL_TOOL_TARGETS); var ToolTargetsSchema = import_mini2.z.array(ToolTargetSchema); var RulesyncTargetsSchema = import_mini2.z.array(import_mini2.z.enum(ALL_TOOL_TARGETS_WITH_WILDCARD)); // src/commands/rulesync-command.ts var RulesyncCommandFrontmatterSchema = import_mini3.z.object({ targets: RulesyncTargetsSchema, description: import_mini3.z.string() }); var RulesyncCommand = class _RulesyncCommand extends RulesyncFile { frontmatter; body; constructor({ frontmatter, body, ...rest }) { if (rest.validate) { const result = RulesyncCommandFrontmatterSchema.safeParse(frontmatter); if (!result.success) { throw result.error; } } super({ ...rest, fileContent: stringifyFrontmatter(body, frontmatter) }); this.frontmatter = frontmatter; this.body = body; } static getSettablePaths() { return { relativeDirPath: ".rulesync/commands" }; } getFrontmatter() { return this.frontmatter; } getBody() { return this.body; } validate() { if (!this.frontmatter) { return { success: true, error: null }; } const result = RulesyncCommandFrontmatterSchema.safeParse(this.frontmatter); if (result.success) { return { success: true, error: null }; } else { return { success: false, error: result.error }; } } static async fromFile({ relativeFilePath }) { const fileContent = await readFileContent( (0, import_node_path3.join)(_RulesyncCommand.getSettablePaths().relativeDirPath, relativeFilePath) ); const { frontmatter, body: content } = parseFrontmatter(fileContent); const result = RulesyncCommandFrontmatterSchema.safeParse(frontmatter); if (!result.success) { throw new Error(`Invalid frontmatter in ${relativeFilePath}: ${result.error.message}`); } const filename = (0, import_node_path3.basename)(relativeFilePath); return new _RulesyncCommand({ baseDir: ".", relativeDirPath: _RulesyncCommand.getSettablePaths().relativeDirPath, relativeFilePath: filename, frontmatter: result.data, body: content.trim(), fileContent }); } }; // src/commands/tool-command.ts var ToolCommand = class extends AiFile { static getSettablePaths() { throw new Error("Please implement this method in the subclass."); } /** * Load a command from a tool-specific file path. * * This method should: * 1. Read the file content * 2. Parse tool-specific frontmatter format * 3. Validate the parsed data * 4. Return a concrete ToolCommand instance * * @param params - Parameters including the file path to load * @returns Promise resolving to a concrete ToolCommand instance */ static async fromFile(_params) { throw new Error("Please implement this method in the subclass."); } /** * Convert a RulesyncCommand to the tool-specific command format. * * This method should: * 1. Extract relevant data from the RulesyncCommand * 2. Transform frontmatter to tool-specific format * 3. Transform body content if needed * 4. Return a concrete ToolCommand instance * * @param params - Parameters including the RulesyncCommand to convert * @returns A concrete ToolCommand instance */ static fromRulesyncCommand(_params) { throw new Error("Please implement this method in the subclass."); } /** * Check if this tool is targeted by a RulesyncCommand based on its targets field. * Subclasses should override this to provide specific targeting logic. * * @param rulesyncCommand - The RulesyncCommand to check * @returns True if this tool is targeted by the command */ static isTargetedByRulesyncCommand(_rulesyncCommand) { throw new Error("Please implement this method in the subclass."); } /** * Default implementation for checking if a tool is targeted by a RulesyncCommand. * Checks if the command's targets include the tool target or a wildcard. * * @param params - Parameters including the RulesyncCommand and tool target * @returns True if the tool target is included in the command's targets */ static isTargetedByRulesyncCommandDefault({ rulesyncCommand, toolTarget }) { const targets = rulesyncCommand.getFrontmatter().targets; if (!targets) { return true; } if (targets.includes("*")) { return true; } if (targets.includes(toolTarget)) { return true; } return false; } }; // src/commands/claudecode-command.ts var ClaudecodeCommandFrontmatterSchema = import_mini4.z.object({ description: import_mini4.z.string() }); var ClaudecodeCommand = class _ClaudecodeCommand extends ToolCommand { frontmatter; body; constructor({ frontmatter, body, ...rest }) { if (rest.validate) { const result = ClaudecodeCommandFrontmatterSchema.safeParse(frontmatter); if (!result.success) { throw result.error; } } super({ ...rest, fileContent: stringifyFrontmatter(body, frontmatter) }); this.frontmatter = frontmatter; this.body = body; } static getSettablePaths() { return { relativeDirPath: ".claude/commands" }; } getBody() { return this.body; } getFrontmatter() { return this.frontmatter; } toRulesyncCommand() { const rulesyncFrontmatter = { targets: ["*"], description: this.frontmatter.description }; const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter); return new RulesyncCommand({ baseDir: this.baseDir, frontmatter: rulesyncFrontmatter, body: this.body, relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath, relativeFilePath: this.relativeFilePath, fileContent, validate: true }); } static fromRulesyncCommand({ baseDir = ".", rulesyncCommand, validate = true }) { const rulesyncFrontmatter = rulesyncCommand.getFrontmatter(); const claudecodeFrontmatter = { description: rulesyncFrontmatter.description }; const body = rulesyncCommand.getBody(); return new _ClaudecodeCommand({ baseDir, frontmatter: claudecodeFrontmatter, body, relativeDirPath: _ClaudecodeCommand.getSettablePaths().relativeDirPath, relativeFilePath: rulesyncCommand.getRelativeFilePath(), validate }); } validate() { if (!this.frontmatter) { return { success: true, error: null }; } const result = ClaudecodeCommandFrontmatterSchema.safeParse(this.frontmatter); if (result.success) { return { success: true, error: null }; } else { return { success: false, error: result.error }; } } static isTargetedByRulesyncCommand(rulesyncCommand) { return this.isTargetedByRulesyncCommandDefault({ rulesyncCommand, toolTarget: "claudecode" }); } static async fromFile({ baseDir = ".", relativeFilePath, validate = true }) { const filePath = (0, import_node_path4.join)( baseDir, _ClaudecodeCommand.getSettablePaths().relativeDirPath, relativeFilePath ); const fileContent = await readFileContent(filePath); const { frontmatter, body: content } = parseFrontmatter(fileContent); const result = ClaudecodeCommandFrontmatterSchema.safeParse(frontmatter); if (!result.success) { throw new Error(`Invalid frontmatter in ${filePath}: ${result.error.message}`); } return new _ClaudecodeCommand({ baseDir, relativeDirPath: _ClaudecodeCommand.getSettablePaths().relativeDirPath, relativeFilePath: (0, import_node_path4.basename)(relativeFilePath), frontmatter: result.data, body: content.trim(), validate }); } }; // src/commands/codexcli-command.ts var import_node_path6 = require("path"); // src/commands/simulated-command.ts var import_node_path5 = require("path"); var import_mini5 = require("zod/mini"); var SimulatedCommandFrontmatterSchema = import_mini5.z.object({ description: import_mini5.z.string() }); var SimulatedCommand = class _SimulatedCommand extends ToolCommand { frontmatter; body; constructor({ frontmatter, body, ...rest }) { if (rest.validate) { const result = SimulatedCommandFrontmatterSchema.safeParse(frontmatter); if (!result.success) { throw result.error; } } super({ ...rest, fileContent: stringifyFrontmatter(body, frontmatter) }); this.frontmatter = frontmatter; this.body = body; } getBody() { return this.body; } getFrontmatter() { return this.frontmatter; } toRulesyncCommand() { throw new Error("Not implemented because it is a SIMULATED file."); } static fromRulesyncCommandDefault({ baseDir = ".", rulesyncCommand, validate = true }) { const rulesyncFrontmatter = rulesyncCommand.getFrontmatter(); const claudecodeFrontmatter = { description: rulesyncFrontmatter.description }; const body = rulesyncCommand.getBody(); return { baseDir, frontmatter: claudecodeFrontmatter, body, relativeDirPath: this.getSettablePaths().relativeDirPath, relativeFilePath: rulesyncCommand.getRelativeFilePath(), validate }; } validate() { if (!this.frontmatter) { return { success: true, error: null }; } const result = SimulatedCommandFrontmatterSchema.safeParse(this.frontmatter); if (result.success) { return { success: true, error: null }; } else { return { success: false, error: result.error }; } } static async fromFileDefault({ baseDir = ".", relativeFilePath, validate = true }) { const filePath = (0, import_node_path5.join)( baseDir, _SimulatedCommand.getSettablePaths().relativeDirPath, relativeFilePath ); const fileContent = await readFileContent(filePath); const { frontmatter, body: content } = parseFrontmatter(fileContent); const result = SimulatedCommandFrontmatterSchema.safeParse(frontmatter); if (!result.success) { throw new Error(`Invalid frontmatter in ${filePath}: ${result.error.message}`); } return { baseDir, relativeDirPath: _SimulatedCommand.getSettablePaths().relativeDirPath, relativeFilePath: (0, import_node_path5.basename)(relativeFilePath), frontmatter: result.data, body: content.trim(), validate }; } }; // src/commands/codexcli-command.ts var CodexCliCommand = class _CodexCliCommand extends SimulatedCommand { static getSettablePaths() { return { relativeDirPath: ".codex/commands" }; } static fromRulesyncCommand({ baseDir = ".", rulesyncCommand, validate = true }) { return new _CodexCliCommand( this.fromRulesyncCommandDefault({ baseDir, rulesyncCommand, validate }) ); } static async fromFile({ baseDir = ".", relativeFilePath, validate = true }) { const filePath = (0, import_node_path6.join)( baseDir, _CodexCliCommand.getSettablePaths().relativeDirPath, relativeFilePath ); const fileContent = await readFileContent(filePath); const { frontmatter, body: content } = parseFrontmatter(fileContent); const result = SimulatedCommandFrontmatterSchema.safeParse(frontmatter); if (!result.success) { throw new Error(`Invalid frontmatter in ${filePath}: ${result.error.message}`); } return new _CodexCliCommand({ baseDir, relativeDirPath: _CodexCliCommand.getSettablePaths().relativeDirPath, relativeFilePath: (0, import_node_path6.basename)(relativeFilePath), frontmatter: result.data, body: content.trim(), validate }); } static isTargetedByRulesyncCommand(rulesyncCommand) { return this.isTargetedByRulesyncCommandDefault({ rulesyncCommand, toolTarget: "codexcli" }); } }; // src/commands/copilot-command.ts var import_node_path7 = require("path"); var CopilotCommand = class _CopilotCommand extends SimulatedCommand { static getSettablePaths() { return { relativeDirPath: ".github/commands" }; } static fromRulesyncCommand({ baseDir = ".", rulesyncCommand, validate = true }) { return new _CopilotCommand( this.fromRulesyncCommandDefault({ baseDir, rulesyncCommand, validate }) ); } static async fromFile({ baseDir = ".", relativeFilePath, validate = true }) { const filePath = (0, import_node_path7.join)( baseDir, _CopilotCommand.getSettablePaths().relativeDirPath, relativeFilePath ); const fileContent = await readFileContent(filePath); const { frontmatter, body: content } = parseFrontmatter(fileContent); const result = SimulatedCommandFrontmatterSchema.safeParse(frontmatter); if (!result.success) { throw new Error(`Invalid frontmatter in ${filePath}: ${result.error.message}`); } return new _CopilotCommand({ baseDir, relativeDirPath: _CopilotCommand.getSettablePaths().relativeDirPath, relativeFilePath: (0, import_node_path7.basename)(relativeFilePath), frontmatter: result.data, body: content.trim(), validate }); } static isTargetedByRulesyncCommand(rulesyncCommand) { return this.isTargetedByRulesyncCommandDefault({ rulesyncCommand, toolTarget: "copilot" }); } }; // src/commands/cursor-command.ts var import_node_path8 = require("path"); var CursorCommand = class _CursorCommand extends SimulatedCommand { static getSettablePaths() { return { relativeDirPath: ".cursor/commands" }; } static fromRulesyncCommand({ baseDir = ".", rulesyncCommand, validate = true }) { return new _CursorCommand( this.fromRulesyncCommandDefault({ baseDir, rulesyncCommand, validate }) ); } static async fromFile({ baseDir = ".", relativeFilePath, validate = true }) { const filePath = (0, import_node_path8.join)( baseDir, _CursorCommand.getSettablePaths().relativeDirPath, relativeFilePath ); const fileContent = await readFileContent(filePath); const { frontmatter, body: content } = parseFrontmatter(fileContent); const result = SimulatedCommandFrontmatterSchema.safeParse(frontmatter); if (!result.success) { throw new Error(`Invalid frontmatter in ${filePath}: ${result.error.message}`); } return new _CursorCommand({ baseDir, relativeDirPath: _CursorCommand.getSettablePaths().relativeDirPath, relativeFilePath: (0, import_node_path8.basename)(relativeFilePath), frontmatter: result.data, body: content.trim(), validate }); } static isTargetedByRulesyncCommand(rulesyncCommand) { return this.isTargetedByRulesyncCommandDefault({ rulesyncCommand, toolTarget: "cursor" }); } }; // src/commands/geminicli-command.ts var import_node_path9 = require("path"); var import_smol_toml = require("smol-toml"); var import_mini6 = require("zod/mini"); var GeminiCliCommandFrontmatterSchema = import_mini6.z.object({ description: import_mini6.z.optional(import_mini6.z.string()), prompt: import_mini6.z.string() }); var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand { frontmatter; body; constructor(params) { super(params); const parsed = this.parseTomlContent(this.fileContent); this.frontmatter = parsed; this.body = parsed.prompt; } static getSettablePaths() { return { relativeDirPath: ".gemini/commands" }; } parseTomlContent(content) { try { const parsed = (0, import_smol_toml.parse)(content); const validated = GeminiCliCommandFrontmatterSchema.parse(parsed); return { description: validated.description || "", prompt: validated.prompt }; } catch (error) { throw new Error(`Failed to parse TOML command file: ${error}`); } } getBody() { return this.body; } getFrontmatter() { return { description: this.frontmatter.description, prompt: this.frontmatter.prompt }; } toRulesyncCommand() { const rulesyncFrontmatter = { targets: ["geminicli"], description: this.frontmatter.description }; const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter); return new RulesyncCommand({ baseDir: this.baseDir, frontmatter: rulesyncFrontmatter, body: this.body, relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath, relativeFilePath: this.relativeFilePath, fileContent, validate: true }); } static fromRulesyncCommand({ baseDir = ".", rulesyncCommand, validate = true }) { const rulesyncFrontmatter = rulesyncCommand.getFrontmatter(); const geminiFrontmatter = { description: rulesyncFrontmatter.description, prompt: rulesyncCommand.getBody() }; const tomlContent = `description = "${geminiFrontmatter.description}" prompt = """ ${geminiFrontmatter.prompt} """`; return new _GeminiCliCommand({ baseDir, relativeDirPath: _GeminiCliCommand.getSettablePaths().relativeDirPath, relativeFilePath: rulesyncCommand.getRelativeFilePath().replace(".md", ".toml"), fileContent: tomlContent, validate }); } static async fromFile({ baseDir = ".", relativeFilePath, validate = true }) { const filePath = (0, import_node_path9.join)( baseDir, _GeminiCliCommand.getSettablePaths().relativeDirPath, relativeFilePath ); const fileContent = await readFileContent(filePath); return new _GeminiCliCommand({ baseDir, relativeDirPath: _GeminiCliCommand.getSettablePaths().relativeDirPath, relativeFilePath: (0, import_node_path9.basename)(relativeFilePath), fileContent, validate }); } validate() { try { this.parseTomlContent(this.fileContent); return { success: true, error: null }; } catch (error) { return { success: false, error: error instanceof Error ? error : new Error(String(error)) }; } } static isTargetedByRulesyncCommand(rulesyncCommand) { return this.isTargetedByRulesyncCommandDefault({ rulesyncCommand, toolTarget: "geminicli" }); } }; // src/commands/roo-command.ts var import_node_path10 = require("path"); var import_mini7 = require("zod/mini"); var RooCommandFrontmatterSchema = import_mini7.z.object({ description: import_mini7.z.string(), "argument-hint": (0, import_mini7.optional)(import_mini7.z.string()) }); var RooCommand = class _RooCommand extends ToolCommand { frontmatter; body; static getSettablePaths() { return { relativeDirPath: ".roo/commands" }; } constructor({ frontmatter, body, ...rest }) { if (rest.validate) { const result = RooCommandFrontmatterSchema.safeParse(frontmatter); if (!result.success) { throw result.error; } } super({ ...rest, fileContent: stringifyFrontmatter(body, frontmatter) }); this.frontmatter = frontmatter; this.body = body; } getBody() { return this.body; } getFrontmatter() { return this.frontmatter; } toRulesyncCommand() { const rulesyncFrontmatter = { targets: ["roo"], description: this.frontmatter.description }; const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter); return new RulesyncCommand({ baseDir: this.baseDir, frontmatter: rulesyncFrontmatter, body: this.body, relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath, relativeFilePath: this.relativeFilePath, fileContent, validate: true }); } static fromRulesyncCommand({ baseDir = ".", rulesyncCommand, validate = true }) { const rulesyncFrontmatter = rulesyncCommand.getFrontmatter(); const rooFrontmatter = { description: rulesyncFrontmatter.description }; const body = rulesyncCommand.getBody(); const fileContent = stringifyFrontmatter(body, rooFrontmatter); return new _RooCommand({ baseDir, frontmatter: rooFrontmatter, body, relativeDirPath: _RooCommand.getSettablePaths().relativeDirPath, relativeFilePath: rulesyncCommand.getRelativeFilePath(), fileContent, validate }); } validate() { if (!this.frontmatter) { return { success: true, error: null }; } const result = RooCommandFrontmatterSchema.safeParse(this.frontmatter); if (result.success) { return { success: true, error: null }; } else { return { success: false, error: result.error }; } } static isTargetedByRulesyncCommand(rulesyncCommand) { return this.isTargetedByRulesyncCommandDefault({ rulesyncCommand, toolTarget: "roo" }); } static async fromFile({ baseDir = ".", relativeFilePath, validate = true }) { const filePath = (0, import_node_path10.join)(baseDir, _RooCommand.getSettablePaths().relativeDirPath, relativeFilePath); const fileContent = await readFileContent(filePath); const { frontmatter, body: content } = parseFrontmatter(fileContent); const result = RooCommandFrontmatterSchema.safeParse(frontmatter); if (!result.success) { throw new Error(`Invalid frontmatter in ${filePath}: ${result.error.message}`); } return new _RooCommand({ baseDir, relativeDirPath: _RooCommand.getSettablePaths().relativeDirPath, relativeFilePath: (0, import_node_path10.basename)(relativeFilePath), frontmatter: result.data, body: content.trim(), fileContent, validate }); } }; // src/commands/commands-processor.ts var commandsProcessorToolTargets = [ "claudecode", "geminicli", "roo", "copilot", "cursor", "codexcli" ]; var CommandsProcessorToolTargetSchema = import_mini8.z.enum(commandsProcessorToolTargets); var commandsProcessorToolTargetsSimulated = ["copilot", "cursor", "codexcli"]; var CommandsProcessor = class extends FeatureProcessor { toolTarget; constructor({ baseDir = process.cwd(), toolTarget }) { super({ baseDir }); this.toolTarget = CommandsProcessorToolTargetSchema.parse(toolTarget); } async convertRulesyncFilesToToolFiles(rulesyncFiles) { const rulesyncCommands = rulesyncFiles.filter( (file) => file instanceof RulesyncCommand ); const toolCommands = rulesyncCommands.map((rulesyncCommand) => { switch (this.toolTarget) { case "claudecode": if (!ClaudecodeCommand.isTargetedByRulesyncCommand(rulesyncCommand)) { return null; } return ClaudecodeCommand.fromRulesyncCommand({ baseDir: this.baseDir, rulesyncCommand }); case "geminicli": if (!GeminiCliCommand.isTargetedByRulesyncCommand(rulesyncCommand)) { return null; } return GeminiCliCommand.fromRulesyncCommand({ baseDir: this.baseDir, rulesyncCommand }); case "roo": if (!RooCommand.isTargetedByRulesyncCommand(rulesyncCommand)) { return null; } return RooCommand.fromRulesyncCommand({ baseDir: this.baseDir, rulesyncCommand }); case "copilot": if (!CopilotCommand.isTargetedByRulesyncCommand(rulesyncCommand)) { return null; } return CopilotCommand.fromRulesyncCommand({ baseDir: this.baseDir, rulesyncCommand }); case "cursor": if (!CursorCommand.isTargetedByRulesyncCommand(rulesyncCommand)) { return null; } return CursorCommand.fromRulesyncCommand({ baseDir: this.baseDir, rulesyncCommand }); case "codexcli": if (!CodexCliCommand.isTargetedByRulesyncCommand(rulesyncCommand)) { return null; } return CodexCliCommand.fromRulesyncCommand({ baseDir: this.baseDir, rulesyncCommand }); default: throw new Error(`Unsupported tool target: ${this.toolTarget}`); } }).filter( (command) => command !== null ); return toolCommands; } async convertToolFilesToRulesyncFiles(toolFiles) { const toolCommands = toolFiles.filter( (file) => file instanceof ToolCommand ); const rulesyncCommands = toolCommands.map((toolCommand) => { return toolCommand.toRulesyncCommand(); }); return rulesyncCommands; } /** * Implementation of abstract method from FeatureProcessor * Load and parse rulesync command files from .rulesync/commands/ directory */ async loadRulesyncFiles() { const rulesyncCommandPaths = await findFilesByGlobs( (0, import_node_path11.join)(RulesyncCommand.getSettablePaths().relativeDirPath, "*.md") ); const rulesyncCommands = (await Promise.allSettled( rulesyncCommandPaths.map( (path2) => RulesyncCommand.fromFile({ relativeFilePath: (0, import_node_path11.basename)(path2) }) ) )).filter((result) => result.status === "fulfilled").map((result) => result.value); logger.info(`Successfully loaded ${rulesyncCommands.length} rulesync commands`); return rulesyncCommands; } /** * Implementation of abstract method from FeatureProcessor * Load tool-specific command configurations and parse them into ToolCommand instances */ async loadToolFiles() { switch (this.toolTarget) { case "claudecode": return await this.loadClaudecodeCommands(); case "geminicli": return await this.loadGeminicliCommands(); case "roo": return await this.loadRooCommands(); case "copilot": return await this.loadCopilotCommands(); case "cursor": return await this.loadCursorCommands(); case "codexcli": return await this.loadCodexcliCommands(); default: throw new Error(`Unsupported tool target: ${this.toolTarget}`); } } async loadToolCommandDefault({ toolTarget, relativeDirPath, extension }) { const commandFilePaths = await findFilesByGlobs( (0, import_node_path11.join)(this.baseDir, relativeDirPath, `*.${extension}`) ); const toolCommands = (await Promise.allSettled( commandFilePaths.map((path2) => { switch (toolTarget) { case "claudecode": return ClaudecodeCommand.fromFile({ relativeFilePath: (0, import_node_path11.basename)(path2) }); case "geminicli": return GeminiCliCommand.fromFile({ relativeFilePath: (0, import_node_path11.basename)(path2) }); case "roo": return RooCommand.fromFile({ relativeFilePath: (0, import_node_path11.basename)(path2) }); case "copilot": return CopilotCommand.fromFile({ relativeFilePath: (0, import_node_path11.basename)(path2) }); case "cursor": return CursorCommand.fromFile({ relativeFilePath: (0, import_node_path11.basename)(path2) }); case "codexcli": return CodexCliCommand.fromFile({ relativeFilePath: (0, import_node_path11.basename)(path2) }); default: throw new Error(`Unsupported tool target: ${toolTarget}`); } }) )).filter((result) => result.status === "fulfilled").map((result) => result.value); logger.info(`Successfully loaded ${toolCommands.length} ${relativeDirPath} commands`); return toolCommands; } /** * Load Claude Code command configurations from .claude/commands/ directory */ async loadCopilotCommands() { return await this.loadToolCommandDefault({ toolTarget: "copilot", relativeDirPath: CopilotCommand.getSettablePaths().relativeDirPath, extension: "md" }); } /** * Load Claude Code command configurations from .claude/commands/ directory */ async loadClaudecodeCommands() { return await this.loadToolCommandDefault({ toolTarget: "claudecode", relativeDirPath: ClaudecodeCommand.getSettablePaths().relativeDirPath, extension: "md" }); } /** * Load Gemini CLI command configurations from .gemini/commands/ directory */ async loadCursorCommands() { return await this.loadToolCommandDefault({ toolTarget: "cursor", relativeDirPath: CursorCommand.getSettablePaths().relativeDirPath, extension: "md" }); } /** * Load Gemini CLI command configurations from .gemini/commands/ directory */ async loadGeminicliCommands() { return await this.loadToolCommandDefault({ toolTarget: "geminicli", relativeDirPath: GeminiCliCommand.getSettablePaths().relativeDirPath, extension: "md" }); } /** * Load Roo Code command configurations from .roo/commands/ directory */ async loadCodexcliCommands() { return await this.loadToolCommandDefault({ toolTarget: "codexcli", relativeDirPath: CodexCliCommand.getSettablePaths().relativeDirPath, extension: "md" }); } /** * Load Roo Code command configurations from .roo/commands/ directory */ async loadRooCommands() { return await this.loadToolCommandDefault({ toolTarget: "roo", relativeDirPath: RooCommand.getSettablePaths().relativeDirPath, extension: "md" }); } /** * Implementation of abstract method from FeatureProcessor * Return the tool targets that this processor supports */ static getToolTargets({ includeSimulated = false } = {}) { if (!includeSimulated) { return commandsProcessorToolTargets.filter( (target) => !commandsProcessorToolTargetsSimulated.includes(target) ); } return commandsProcessorToolTargets; } static getToolTargetsSimulated() { return commandsProcessorToolTargetsSimulated; } }; // src/config/config-resolver.ts var import_c12 = require("c12"); // src/config/config.ts var Config = class { baseDirs; targets; features; verbose; delete; experimentalSimulateCommands; experimentalSimulateSubagents; constructor({ baseDirs, targets, features, verbose, delete: isDelete, experimentalSimulateCommands, experimentalSimulateSubagents }) { this.baseDirs = baseDirs; this.targets = targets; this.features = features; this.verbose = verbose; this.delete = isDelete; this.experimentalSimulateCommands = experimentalSimulateCommands; this.experimentalSimulateSubagents = experimentalSimulateSubagents; } getBaseDirs() { return this.baseDirs; } getTargets() { if (this.targets.includes("*")) { return [...ALL_TOOL_TARGETS]; } return this.targets.filter((target) => target !== "*"); } getFeatures() { if (this.features.includes("*")) { return [...ALL_FEATURES]; } return this.features.filter((feature) => feature !== "*"); } getVerbose() { return this.verbose; } getDelete() { return this.delete; } getExperimentalSimulateCommands() { return this.experimentalSimulateCommands; } getExperimentalSimulateSubagents() { return this.experimentalSimulateSubagents; } }; // src/config/config-resolver.ts var defaults = { targets: ["agentsmd"], features: ["rules"], verbose: false, delete: false, baseDirs: ["."], configPath: "rulesync.jsonc", experimentalSimulateCommands: false, experimentalSimulateSubagents: false }; var ConfigResolver = class { static async resolve({ targets, features, verbose, delete: isDelete, baseDirs, configPath = defaults.configPath, experimentalSimulateCommands, experimentalSimulateSubagents }) { if (!fileExists(configPath)) { return new Config({ targets: targets ?? defaults.targets, features: features ?? defaults.features, verbose: verbose ?? defaults.verbose, delete: isDelete ?? defaults.delete, baseDirs: baseDirs ?? defaults.baseDirs, experimentalSimulateCommands: experimentalSimulateCommands ?? defaults.experimentalSimulateCommands, experimentalSimulateSubagents: experimentalSimulateSubagents ?? defaults.experimentalSimulateSubagents }); } const loadOptions = { name: "rulesync", cwd: process.cwd(), rcFile: false, // Disable rc file lookup configFile: "rulesync" // Will look for rulesync.jsonc, rulesync.ts, etc. }; if (configPath) { loadOptions.configFile = configPath; } const { config: configByFile } = await (0, import_c12.loadConfig)(loadOptions); const configParams = { targets: targets ?? configByFile.targets ?? defaults.targets, features: features ?? configByFile.features ?? defaults.features, verbose: verbose ?? configByFile.verbose ?? defaults.verbose, delete: isDelete ?? configByFile.delete ?? defaults.delete, baseDirs: baseDirs ?? configByFile.baseDirs ?? defaults.baseDirs, experimentalSimulateCommands: experimentalSimulateCommands ?? configByFile.experimentalSimulateCommands ?? defaults.experimentalSimulateCommands, experimentalSimulateSubagents: experimentalSimulateSubagents ?? configByFile.experimentalSimulateSubagents ?? defaults.experimentalSimulateSubagents }; return new Config(configParams); } }; // src/ignore/ignore-processor.ts var import_mini9 = require("zod/mini"); // src/ignore/amazonqcli-ignore.ts var import_node_path12 = require("path"); // src/types/tool-file.ts var ToolFile = class extends AiFile { }; // src/ignore/rulesync-ignore.ts var RulesyncIgnore = class _RulesyncIgnore extends RulesyncFile { validate() { return { success: true, error: null }; } static getSettablePaths() { return { relativeDirPath: ".", relativeFilePath: ".rulesyncignore" }; } static async fromFile() { const fileContent = await readFileContent(this.getSettablePaths().relativeFilePath); return new _RulesyncIgnore({ baseDir: ".", relativeDirPath: this.getSettablePaths().relativeDirPath, relativeFilePath: this.getSettablePaths().relativeFilePath, fileContent }); } }; // src/ignore/tool-ignore.ts var ToolIgnore = class extends ToolFile { patterns; constructor({ ...rest }) { super({ ...rest, validate: true // Skip validation during construction }); this.patterns = this.fileContent.split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")); if (rest.validate) { const result = this.validate(); if (!result.success) { throw result.error; } } } static getSettablePaths() { throw new Error("Please implement this method in the subclass."); } getPatterns() { return this.patterns; } validate() { return { success: true, error: null }; } static fromRulesyncIgnore(_params) { throw new Error("Please implement this method in the subclass."); } toRulesyncIgnoreDefault() { return new RulesyncIgnore({ baseDir: ".", relativeDirPath: ".", relativeFilePath: ".rulesyncignore", fileContent: this.fileContent }); } static async fromFile(_params) { throw new Error("Please implement this method in the subclass."); } }; // src/ignore/amazonqcli-ignore.ts var AmazonqcliIgnore = class _AmazonqcliIgnore extends ToolIgnore { static getSettablePaths() { return { relativeDirPath: ".", relativeFilePath: ".amazonqignore" }; } /** * Convert to RulesyncIgnore format */ toRulesyncIgnore() { return this.toRulesyncIgnoreDefault(); } /** * Create AmazonqcliIgnore from RulesyncIgnore * Supports conversion from unified rulesync format to Amazon Q CLI specific format */ static fromRulesyncIgnore({ baseDir = ".", rulesyncIgnore }) { const body = rulesyncIgnore.getFileContent(); return new _AmazonqcliIgnore({ baseDir, relativeDirPath: this.getSettablePaths().relativeDirPath, relativeFilePath: this.getSettablePaths().relativeFilePath, fileContent: body }); } /** * Create AmazonqcliIgnore from file path * Supports both proposed .q-ignore and .amazonqignore formats */ static async fromFile({ baseDir = ".", validate = true }) { const fileContent = await readFileContent( (0, import_node_path12.join)( baseDir, this.getSettablePaths().relativeDirPath, this.getSettablePaths().relativeFilePath ) ); return new _AmazonqcliIgnore({ baseDir, relativeDirPath: this.getSettablePaths().relativeDirPath, relativeFilePath: this.getSettablePaths().relativeFilePath, fileContent, validate }); } }; // src/ignore/augmentcode-ignore.ts var import_node_path13 = require("path"); var AugmentcodeIgnore = class _AugmentcodeIgnore extends ToolIgnore { static getSettablePaths() { return { relativeDirPath: ".", relativeFilePath: ".augmentignore" }; } /** * Convert to RulesyncIgnore format */ toRulesyncIgnore() { return this.toRulesyncIgnoreDefault(); } /** * Create AugmentcodeIgnore from RulesyncIgnore * Supports conversion from unified rulesync format to AugmentCode specific format */ static fromRulesyncIgnore({ baseDir = ".", rulesyncIgnore }) { return new _AugmentcodeIgnore({ baseDir, relativeDirPath: this.getSettablePaths().relativeDirPath, relativeFilePath: this.getSettablePaths().relativeFilePath, fileContent: rulesyncIgnore.getFileContent() }); } /** * Create AugmentcodeIgnore from file path * Reads and parses .augmentignore file */ static async fromFile({ baseDir = ".", validate = true }) { const fileContent = await readFileContent( (0, import_node_path13.join)( baseDir, this.getSettablePaths().relativeDirPath, this.getSettablePaths().relativeFilePath ) ); return new _AugmentcodeIgnore({ baseDir, relativeDirPath: this.getSettablePaths().relativeDirPath, relativeFilePath: this.getSettablePaths().relativeFilePath, fileContent, validate }); } }; // src/ignore/cline-ignore.ts var import_node_path14 = require("path"); var ClineIgnore = class _ClineIgnore extends ToolIgnore { static getSettablePaths() { return { relativeDirPath: ".", relativeFilePath: ".clineignore" }; } /** * Convert ClineIgnore to RulesyncIgnore format */ toRulesyncIgnore() { return this.toRulesyncIgnoreDefault(); } /** * Create ClineIgnore from RulesyncIgnore */ static fromRulesyncIgnore({ baseDir = ".", rulesyncIgnore }) { const body = rulesyncIgnore.getFileContent(); return new _ClineIgnore({ baseDir, relativeDirPath: this.getSettablePaths().relativeDirPath, relativeFilePath: this.getSettablePaths().relativeFilePath, fileContent: body }); } /** * Load ClineIgnore from .clineignore file */ static async fromFile({ baseDir = ".", validate = true }) { const fileContent = await readFileContent( (0, import_node_path14.join)( baseDir, this.getSettablePaths().relativeDirPath, this.getSettablePaths().relativeFilePath ) ); return new _ClineIgnore({ baseDir, relativeDirPath: this.getSettablePaths().relativeDirPath, relativeFilePath: this.getSettablePaths().relativeFilePath, f