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
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 __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