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