UNPKG

rulesync

Version:

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

1,605 lines (1,575 loc) 242 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_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/logger.ts var import_consola = require("consola"); // src/utils/vitest.ts var isEnvTest = process.env.NODE_ENV === "test"; function getVitestWorkerId() { const vitestWorkerId = process.env.VITEST_WORKER_ID; if (!vitestWorkerId) { throw new Error("VITEST_WORKER_ID is not set"); } return vitestWorkerId; } // src/utils/logger.ts 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/cli/commands/generate.ts var import_es_toolkit2 = require("es-toolkit"); // src/commands/commands-processor.ts var import_node_path12 = require("path"); var import_mini9 = require("zod/mini"); // src/utils/file.ts var import_node_fs = require("fs"); var import_promises = require("fs/promises"); var import_node_os = __toESM(require("os"), 1); var import_node_path = require("path"); async function ensureDir(dirPath) { try { await (0, import_promises.stat)(dirPath); } catch { await (0, import_promises.mkdir)(dirPath, { recursive: true }); } } async function readOrInitializeFileContent(filePath, initialContent = "") { if (await fileExists(filePath)) { return await readFileContent(filePath); } else { await ensureDir((0, import_node_path.dirname)(filePath)); await writeFileContent(filePath, initialContent); return initialContent; } } async function directoryExists(dirPath) { try { const stats = await (0, import_promises.stat)(dirPath); return stats.isDirectory(); } catch { return false; } } async function readFileContent(filepath) { logger.debug(`Reading file: ${filepath}`); return (0, import_promises.readFile)(filepath, "utf-8"); } function addTrailingNewline(content) { if (!content) { return "\n"; } return content.trimEnd() + "\n"; } 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); } } function getHomeDirectory() { if (isEnvTest) { return (0, import_node_path.join)("./tmp", "tests", "home", getVitestWorkerId()); } return import_node_os.default.homedir(); } function validateBaseDir(baseDir) { if (baseDir.trim() === "") { throw new Error("baseDir cannot be an empty string"); } if (baseDir.includes("..")) { throw new Error(`baseDir cannot contain directory traversal (..): ${baseDir}`); } if (baseDir.startsWith("/")) { throw new Error(`baseDir must be a relative path. Absolute path not allowed: ${baseDir}`); } if (/^[a-zA-Z]:[/\\]/.test(baseDir)) { throw new Error(`baseDir must be a relative path. Absolute path not allowed: ${baseDir}`); } const normalized = (0, import_node_path.resolve)(".", baseDir); const rel = (0, import_node_path.relative)(".", normalized); if (rel.startsWith("..")) { throw new Error(`baseDir cannot contain directory traversal (..): ${baseDir}`); } } // 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) { const contentWithNewline = addTrailingNewline(aiFile.getFileContent()); await writeFileContent(aiFile.getFilePath(), contentWithNewline); } return aiFiles.length; } async removeAiFiles(aiFiles) { for (const aiFile of aiFiles) { await removeFile(aiFile.getFilePath()); } } }; // src/utils/error.ts var import_zod = require("zod"); function isZodErrorLike(error) { return error !== null && typeof error === "object" && "issues" in error && Array.isArray(error.issues) && error.issues.every( (issue) => issue !== null && typeof issue === "object" && "path" in issue && Array.isArray(issue.path) && "message" in issue && typeof issue.message === "string" ); } function formatError(error) { if (error instanceof import_zod.ZodError || isZodErrorLike(error)) { return error.issues.map((issue) => { const path2 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : ""; return `${path2}${issue.message}`; }).join("; "); } if (error instanceof Error) { return `${error.name}: ${error.message}`; } return String(error); } // src/commands/agentsmd-command.ts var import_node_path4 = require("path"); // 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/simulated-command.ts var import_node_path3 = require("path"); var import_mini2 = 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; /** * @example true */ global; constructor({ baseDir = ".", relativeDirPath, relativeFilePath, fileContent, validate = true, global = false }) { this.baseDir = baseDir; this.relativeDirPath = relativeDirPath; this.relativeFilePath = relativeFilePath; this.fileContent = fileContent; this.global = global; 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() { const fullPath = import_node_path2.default.join(this.baseDir, this.relativeDirPath, this.relativeFilePath); const resolvedFull = (0, import_node_path2.resolve)(fullPath); const resolvedBase = (0, import_node_path2.resolve)(this.baseDir); const rel = (0, import_node_path2.relative)(resolvedBase, resolvedFull); if (rel.startsWith("..") || import_node_path2.default.isAbsolute(rel)) { throw new Error( `Path traversal detected: Final path escapes baseDir. baseDir="${this.baseDir}", relativeDirPath="${this.relativeDirPath}", relativeFilePath="${this.relativeFilePath}"` ); } return fullPath; } getFileContent() { return this.fileContent; } getRelativePathFromCwd() { return import_node_path2.default.join(this.relativeDirPath, this.relativeFilePath); } setFileContent(newFileContent) { this.fileContent = newFileContent; } }; // 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/simulated-command.ts var SimulatedCommandFrontmatterSchema = import_mini2.z.object({ description: import_mini2.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 new Error( `Invalid frontmatter in ${(0, import_node_path3.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(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: new Error( `Invalid frontmatter in ${(0, import_node_path3.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}` ) }; } } static async fromFileDefault({ baseDir = ".", relativeFilePath, validate = true }) { const filePath = (0, import_node_path3.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}: ${formatError(result.error)}`); } return { baseDir, relativeDirPath: _SimulatedCommand.getSettablePaths().relativeDirPath, relativeFilePath: (0, import_node_path3.basename)(relativeFilePath), frontmatter: result.data, body: content.trim(), validate }; } }; // src/commands/agentsmd-command.ts var AgentsmdCommand = class _AgentsmdCommand extends SimulatedCommand { static getSettablePaths() { return { relativeDirPath: ".agents/commands" }; } static fromRulesyncCommand({ baseDir = ".", rulesyncCommand, validate = true }) { return new _AgentsmdCommand( this.fromRulesyncCommandDefault({ baseDir, rulesyncCommand, validate }) ); } static async fromFile({ baseDir = ".", relativeFilePath, validate = true }) { const filePath = (0, import_node_path4.join)( baseDir, _AgentsmdCommand.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}: ${formatError(result.error)}`); } return new _AgentsmdCommand({ baseDir, relativeDirPath: _AgentsmdCommand.getSettablePaths().relativeDirPath, relativeFilePath: (0, import_node_path4.basename)(relativeFilePath), frontmatter: result.data, body: content.trim(), validate }); } static isTargetedByRulesyncCommand(rulesyncCommand) { return this.isTargetedByRulesyncCommandDefault({ rulesyncCommand, toolTarget: "agentsmd" }); } }; // src/commands/claudecode-command.ts var import_node_path6 = require("path"); var import_mini5 = require("zod/mini"); // src/commands/rulesync-command.ts var import_node_path5 = require("path"); var import_mini4 = require("zod/mini"); // 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_mini3 = 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_mini3.z.enum(ALL_TOOL_TARGETS); var ToolTargetsSchema = import_mini3.z.array(ToolTargetSchema); var RulesyncTargetsSchema = import_mini3.z.array(import_mini3.z.enum(ALL_TOOL_TARGETS_WITH_WILDCARD)); // src/commands/rulesync-command.ts var RulesyncCommandFrontmatterSchema = import_mini4.z.object({ targets: RulesyncTargetsSchema, description: import_mini4.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 new Error( `Invalid frontmatter in ${(0, import_node_path5.join)(rest.baseDir ?? ".", rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(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: new Error( `Invalid frontmatter in ${(0, import_node_path5.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}` ) }; } } static async fromFile({ relativeFilePath }) { const fileContent = await readFileContent( (0, import_node_path5.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}: ${formatError(result.error)}`); } const filename = (0, import_node_path5.basename)(relativeFilePath); return new _RulesyncCommand({ baseDir: ".", relativeDirPath: _RulesyncCommand.getSettablePaths().relativeDirPath, relativeFilePath: filename, frontmatter: result.data, body: content.trim(), fileContent }); } }; // src/commands/claudecode-command.ts var ClaudecodeCommandFrontmatterSchema = import_mini5.z.object({ description: import_mini5.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 new Error( `Invalid frontmatter in ${(0, import_node_path6.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}` ); } } super({ ...rest, fileContent: stringifyFrontmatter(body, frontmatter) }); this.frontmatter = frontmatter; this.body = body; } static getSettablePaths(_options = {}) { return { relativeDirPath: (0, import_node_path6.join)(".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: ".", // RulesyncCommand baseDir is always the project root directory frontmatter: rulesyncFrontmatter, body: this.body, relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath, relativeFilePath: this.relativeFilePath, fileContent, validate: true }); } static fromRulesyncCommand({ baseDir = ".", rulesyncCommand, validate = true, global = false }) { const rulesyncFrontmatter = rulesyncCommand.getFrontmatter(); const claudecodeFrontmatter = { description: rulesyncFrontmatter.description }; const body = rulesyncCommand.getBody(); const paths = this.getSettablePaths({ global }); return new _ClaudecodeCommand({ baseDir, frontmatter: claudecodeFrontmatter, body, relativeDirPath: paths.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: new Error( `Invalid frontmatter in ${(0, import_node_path6.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}` ) }; } } static isTargetedByRulesyncCommand(rulesyncCommand) { return this.isTargetedByRulesyncCommandDefault({ rulesyncCommand, toolTarget: "claudecode" }); } static async fromFile({ baseDir = ".", relativeFilePath, validate = true, global = false }) { const paths = this.getSettablePaths({ global }); const filePath = (0, import_node_path6.join)(baseDir, paths.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}: ${formatError(result.error)}`); } return new _ClaudecodeCommand({ baseDir, relativeDirPath: paths.relativeDirPath, relativeFilePath: (0, import_node_path6.basename)(relativeFilePath), frontmatter: result.data, body: content.trim(), validate }); } }; // src/commands/codexcli-command.ts var import_node_path7 = require("path"); var CodexcliCommand = class _CodexcliCommand extends ToolCommand { static getSettablePaths({ global } = {}) { if (!global) { throw new Error("CodexcliCommand only supports global mode. Please pass { global: true }."); } return { relativeDirPath: (0, import_node_path7.join)(".codex", "prompts") }; } toRulesyncCommand() { const rulesyncFrontmatter = { targets: ["*"], description: "" }; return new RulesyncCommand({ baseDir: ".", // RulesyncCommand baseDir is always the project root directory frontmatter: rulesyncFrontmatter, body: this.getFileContent(), relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath, relativeFilePath: this.relativeFilePath, fileContent: this.getFileContent(), validate: true }); } static fromRulesyncCommand({ baseDir = ".", rulesyncCommand, validate = true, global = false }) { const paths = this.getSettablePaths({ global }); return new _CodexcliCommand({ baseDir, fileContent: rulesyncCommand.getBody(), relativeDirPath: paths.relativeDirPath, relativeFilePath: rulesyncCommand.getRelativeFilePath(), validate }); } validate() { return { success: true, error: null }; } getBody() { return this.getFileContent(); } static isTargetedByRulesyncCommand(rulesyncCommand) { return this.isTargetedByRulesyncCommandDefault({ rulesyncCommand, toolTarget: "codexcli" }); } static async fromFile({ baseDir = ".", relativeFilePath, validate = true, global = false }) { const paths = this.getSettablePaths({ global }); const filePath = (0, import_node_path7.join)(baseDir, paths.relativeDirPath, relativeFilePath); const fileContent = await readFileContent(filePath); const { body: content } = parseFrontmatter(fileContent); return new _CodexcliCommand({ baseDir, relativeDirPath: paths.relativeDirPath, relativeFilePath: (0, import_node_path7.basename)(relativeFilePath), fileContent: content.trim(), validate }); } }; // src/commands/copilot-command.ts var import_node_path8 = require("path"); var import_mini6 = require("zod/mini"); var CopilotCommandFrontmatterSchema = import_mini6.z.object({ mode: import_mini6.z.literal("agent"), description: import_mini6.z.string() }); var CopilotCommand = class _CopilotCommand extends ToolCommand { frontmatter; body; constructor({ frontmatter, body, ...rest }) { if (rest.validate) { const result = CopilotCommandFrontmatterSchema.safeParse(frontmatter); if (!result.success) { throw new Error( `Invalid frontmatter in ${(0, import_node_path8.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}` ); } } super({ ...rest, fileContent: stringifyFrontmatter(body, frontmatter) }); this.frontmatter = frontmatter; this.body = body; } static getSettablePaths() { return { relativeDirPath: (0, import_node_path8.join)(".github", "prompts") }; } getBody() { return this.body; } getFrontmatter() { return this.frontmatter; } toRulesyncCommand() { const rulesyncFrontmatter = { targets: ["*"], description: this.frontmatter.description }; const originalFilePath = this.relativeFilePath; const relativeFilePath = originalFilePath.replace(/\.prompt\.md$/, ".md"); return new RulesyncCommand({ baseDir: ".", frontmatter: rulesyncFrontmatter, body: this.body, relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath, relativeFilePath, fileContent: this.getFileContent(), validate: true }); } validate() { if (!this.frontmatter) { return { success: true, error: null }; } const result = CopilotCommandFrontmatterSchema.safeParse(this.frontmatter); if (result.success) { return { success: true, error: null }; } else { return { success: false, error: new Error( `Invalid frontmatter in ${(0, import_node_path8.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}` ) }; } } static fromRulesyncCommand({ baseDir = ".", rulesyncCommand, validate = true }) { const paths = this.getSettablePaths(); const rulesyncFrontmatter = rulesyncCommand.getFrontmatter(); const copilotFrontmatter = { mode: "agent", description: rulesyncFrontmatter.description }; const body = rulesyncCommand.getBody(); const originalFilePath = rulesyncCommand.getRelativeFilePath(); const relativeFilePath = originalFilePath.replace(/\.md$/, ".prompt.md"); return new _CopilotCommand({ baseDir, frontmatter: copilotFrontmatter, body, relativeDirPath: paths.relativeDirPath, relativeFilePath, validate }); } static async fromFile({ baseDir = ".", relativeFilePath, validate = true }) { const paths = this.getSettablePaths(); const filePath = (0, import_node_path8.join)(baseDir, paths.relativeDirPath, relativeFilePath); const fileContent = await readFileContent(filePath); const { frontmatter, body: content } = parseFrontmatter(fileContent); const result = CopilotCommandFrontmatterSchema.safeParse(frontmatter); if (!result.success) { throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`); } return new _CopilotCommand({ baseDir, relativeDirPath: paths.relativeDirPath, relativeFilePath: (0, import_node_path8.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_path9 = require("path"); var CursorCommand = class _CursorCommand extends ToolCommand { static getSettablePaths(_options = {}) { return { relativeDirPath: (0, import_node_path9.join)(".cursor", "commands") }; } toRulesyncCommand() { const rulesyncFrontmatter = { targets: ["*"], description: "" }; return new RulesyncCommand({ baseDir: ".", // RulesyncCommand baseDir is always the project root directory frontmatter: rulesyncFrontmatter, body: this.getFileContent(), relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath, relativeFilePath: this.relativeFilePath, fileContent: this.getFileContent(), validate: true }); } static fromRulesyncCommand({ baseDir = ".", rulesyncCommand, validate = true, global = false }) { const paths = this.getSettablePaths({ global }); return new _CursorCommand({ baseDir, fileContent: rulesyncCommand.getBody(), relativeDirPath: paths.relativeDirPath, relativeFilePath: rulesyncCommand.getRelativeFilePath(), validate }); } validate() { return { success: true, error: null }; } getBody() { return this.getFileContent(); } static isTargetedByRulesyncCommand(rulesyncCommand) { return this.isTargetedByRulesyncCommandDefault({ rulesyncCommand, toolTarget: "cursor" }); } static async fromFile({ baseDir = ".", relativeFilePath, validate = true, global = false }) { const paths = this.getSettablePaths({ global }); const filePath = (0, import_node_path9.join)(baseDir, paths.relativeDirPath, relativeFilePath); const fileContent = await readFileContent(filePath); const { body: content } = parseFrontmatter(fileContent); return new _CursorCommand({ baseDir, relativeDirPath: paths.relativeDirPath, relativeFilePath: (0, import_node_path9.basename)(relativeFilePath), fileContent: content.trim(), validate }); } }; // src/commands/geminicli-command.ts var import_node_path10 = require("path"); var import_smol_toml = require("smol-toml"); var import_mini7 = require("zod/mini"); var GeminiCliCommandFrontmatterSchema = import_mini7.z.object({ description: import_mini7.z.optional(import_mini7.z.string()), prompt: import_mini7.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(_options = {}) { return { relativeDirPath: (0, import_node_path10.join)(".gemini", "commands") }; } parseTomlContent(content) { try { const parsed = (0, import_smol_toml.parse)(content); const result = GeminiCliCommandFrontmatterSchema.safeParse(parsed); if (!result.success) { throw new Error( `Invalid frontmatter in Gemini CLI command file: ${formatError(result.error)}` ); } return { description: result.data.description || "", prompt: result.data.prompt }; } catch (error) { throw new Error(`Failed to parse TOML command file: ${error}`, { cause: 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: ".", // RulesyncCommand baseDir is always the project root directory frontmatter: rulesyncFrontmatter, body: this.body, relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath, relativeFilePath: this.relativeFilePath, fileContent, validate: true }); } static fromRulesyncCommand({ baseDir = ".", rulesyncCommand, validate = true, global = false }) { const rulesyncFrontmatter = rulesyncCommand.getFrontmatter(); const geminiFrontmatter = { description: rulesyncFrontmatter.description, prompt: rulesyncCommand.getBody() }; const tomlContent = `description = "${geminiFrontmatter.description}" prompt = """ ${geminiFrontmatter.prompt} """`; const paths = this.getSettablePaths({ global }); return new _GeminiCliCommand({ baseDir, relativeDirPath: paths.relativeDirPath, relativeFilePath: rulesyncCommand.getRelativeFilePath().replace(".md", ".toml"), fileContent: tomlContent, validate }); } static async fromFile({ baseDir = ".", relativeFilePath, validate = true, global = false }) { const paths = this.getSettablePaths({ global }); const filePath = (0, import_node_path10.join)(baseDir, paths.relativeDirPath, relativeFilePath); const fileContent = await readFileContent(filePath); return new _GeminiCliCommand({ baseDir, relativeDirPath: paths.relativeDirPath, relativeFilePath: (0, import_node_path10.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_path11 = require("path"); var import_mini8 = require("zod/mini"); var RooCommandFrontmatterSchema = import_mini8.z.object({ description: import_mini8.z.string(), "argument-hint": (0, import_mini8.optional)(import_mini8.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 new Error( `Invalid frontmatter in ${(0, import_node_path11.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(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: ".", // RulesyncCommand baseDir is always the project root directory 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: new Error( `Invalid frontmatter in ${(0, import_node_path11.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}` ) }; } } static isTargetedByRulesyncCommand(rulesyncCommand) { return this.isTargetedByRulesyncCommandDefault({ rulesyncCommand, toolTarget: "roo" }); } static async fromFile({ baseDir = ".", relativeFilePath, validate = true }) { const filePath = (0, import_node_path11.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}: ${formatError(result.error)}`); } return new _RooCommand({ baseDir, relativeDirPath: _RooCommand.getSettablePaths().relativeDirPath, relativeFilePath: (0, import_node_path11.basename)(relativeFilePath), frontmatter: result.data, body: content.trim(), fileContent, validate }); } }; // src/commands/commands-processor.ts var commandsProcessorToolTargets = [ "agentsmd", "claudecode", "geminicli", "roo", "copilot", "cursor" ]; var CommandsProcessorToolTargetSchema = import_mini9.z.enum( // codexcli is not in the list of tool targets but we add it here because it is a valid tool target for global mode generation commandsProcessorToolTargets.concat("codexcli") ); var commandsProcessorToolTargetsSimulated = ["agentsmd"]; var commandsProcessorToolTargetsGlobal = [ "claudecode", "cursor", "geminicli", "codexcli" ]; var CommandsProcessor = class extends FeatureProcessor { toolTarget; global; constructor({ baseDir = process.cwd(), toolTarget, global = false }) { super({ baseDir }); const result = CommandsProcessorToolTargetSchema.safeParse(toolTarget); if (!result.success) { throw new Error( `Invalid tool target for CommandsProcessor: ${toolTarget}. ${formatError(result.error)}` ); } this.toolTarget = result.data; this.global = global; } async convertRulesyncFilesToToolFiles(rulesyncFiles) { const rulesyncCommands = rulesyncFiles.filter( (file) => file instanceof RulesyncCommand ); const toolCommands = rulesyncCommands.map((rulesyncCommand) => { switch (this.toolTarget) { case "agentsmd": if (!AgentsmdCommand.isTargetedByRulesyncCommand(rulesyncCommand)) { return null; } return AgentsmdCommand.fromRulesyncCommand({ baseDir: this.baseDir, rulesyncCommand }); case "claudecode": if (!ClaudecodeCommand.isTargetedByRulesyncCommand(rulesyncCommand)) { return null; } return ClaudecodeCommand.fromRulesyncCommand({ baseDir: this.baseDir, rulesyncCommand, global: this.global }); case "geminicli": if (!GeminiCliCommand.isTargetedByRulesyncCommand(rulesyncCommand)) { return null; } return GeminiCliCommand.fromRulesyncCommand({ baseDir: this.baseDir, rulesyncCommand, global: this.global }); 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, global: this.global }); case "codexcli": if (!CodexcliCommand.isTargetedByRulesyncCommand(rulesyncCommand)) { return null; } return CodexcliCommand.fromRulesyncCommand({ baseDir: this.baseDir, rulesyncCommand, global: this.global }); 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_path12.join)(RulesyncCommand.getSettablePaths().relativeDirPath, "*.md") ); const rulesyncCommands = (await Promise.allSettled( rulesyncCommandPaths.map( (path2) => RulesyncCommand.fromFile({ relativeFilePath: (0, import_node_path12.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 "agentsmd": return await this.loadAgentsmdCommands(); 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 loadToolFilesToDelete() { return this.loadToolFiles(); } async loadToolCommandDefault({ toolTarget, relativeDirPath, extension }) { const commandFilePaths = await findFilesByGlobs( (0, import_node_path12.join)(this.baseDir, relativeDirPath, `*.${extension}`) ); const toolCommands = (await Promise.allSettled( commandFilePaths.map((path2) => { switch (toolTarget) { case "agentsmd": return AgentsmdCommand.fromFile({ baseDir: this.baseDir, relativeFilePath: (0, import_node_path12.basename)(path2) }); case "claudecode": return ClaudecodeCommand.fromFile({ baseDir: this.baseDir, relativeFilePath: (0, import_node_path12.basename)(path2), global: this.global }); case "geminicli": return GeminiCliCommand.fromFile({ baseDir: this.baseDir, relativeFilePath: (0, import_node_path12.basename)(path2), global: this.global }); case "roo": return RooCommand.fromFile({ baseDir: this.baseDir, relativeFilePath: (0, import_node_path12.basename)(path2) }); case "copilot": return CopilotCommand.fromFile({ baseDir: this.baseDir, relativeFilePath: (0, import_node_path12.basename)(path2) }); case "cursor": return CursorCommand.fromFile({ baseDir: this.baseDir, relativeFilePath: (0, import_node_path12.basename)(path2), global: this.global }); case "codexcli": return CodexcliCommand.fromFile({ baseDir: this.baseDir, relativeFilePath: (0, import_node_path12.basename)(path2), global: this.global }); 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 Agents.md command configurations from .agents/commands/ directory */ async loadAgentsmdCommands() { return await this.loadToolCommandDefault({ toolTarget: "agentsmd", relativeDirPath: AgentsmdCommand.getSettablePaths().relativeDirPath, extension: "md" }); } /** * Load Copilot command configurations from .github/prompts/ directory */ async loadCopilotCommands() { return await this.loadToolCommandDefault({ toolTarget: "copilot", relativeDirPath: CopilotCommand.getSettablePaths().relativeDirPath, extension: "prompt.md" }); } /** * Load Claude Code command configurations from .claude/commands/ directory */ async loadClaudecodeCommands() { const paths = ClaudecodeCommand.getSettablePaths({ global: this.global }); return await this.loadToolCommandDefault({ toolTarget: "claudecode", relativeDirPath: paths.relativeDirPath, extension: "md" }); } /** * Load Cursor command configurations from .cursor/commands/ directory */ async loadCursorCommands() { const paths = CursorCommand.getSettablePaths({ global: this.global }); return await this.loadToolCommandDefault({ toolTarget: "cursor", relativeDirPath: paths.relativeDirPath, extension