rulesync 3.23.5 → 3.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +53 -23
  2. package/dist/index.cjs +815 -119
  3. package/dist/index.js +815 -119
  4. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ var ANNOUNCEMENT = "".trim();
8
8
 
9
9
  // src/types/features.ts
10
10
  import { z } from "zod/mini";
11
- var ALL_FEATURES = ["rules", "ignore", "mcp", "subagents", "commands"];
11
+ var ALL_FEATURES = ["rules", "ignore", "mcp", "subagents", "commands", "skills"];
12
12
  var ALL_FEATURES_WITH_WILDCARD = [...ALL_FEATURES, "*"];
13
13
  var FeatureSchema = z.enum(ALL_FEATURES);
14
14
  var FeaturesSchema = z.array(FeatureSchema);
@@ -137,6 +137,10 @@ async function readFileContent(filepath) {
137
137
  logger.debug(`Reading file: ${filepath}`);
138
138
  return readFile(filepath, "utf-8");
139
139
  }
140
+ async function readFileBuffer(filepath) {
141
+ logger.debug(`Reading file buffer: ${filepath}`);
142
+ return readFile(filepath);
143
+ }
140
144
  function addTrailingNewline(content) {
141
145
  if (!content) {
142
146
  return "\n";
@@ -164,11 +168,32 @@ async function listDirectoryFiles(dir) {
164
168
  }
165
169
  }
166
170
  async function findFilesByGlobs(globs, options = {}) {
171
+ const { type = "all" } = options;
167
172
  const items = globSync(globs, { withFileTypes: true });
168
- if (!options.fileOnly) {
169
- return items.map((item) => join(item.parentPath, item.name));
173
+ switch (type) {
174
+ case "file":
175
+ return items.filter((item) => item.isFile()).map((item) => join(item.parentPath, item.name));
176
+ case "dir":
177
+ return items.filter((item) => item.isDirectory()).map((item) => join(item.parentPath, item.name));
178
+ case "all":
179
+ return items.map((item) => join(item.parentPath, item.name));
180
+ default:
181
+ throw new Error(`Invalid type: ${type}`);
182
+ }
183
+ }
184
+ async function removeDirectory(dirPath) {
185
+ const dangerousPaths = [".", "/", "~", "src", "node_modules"];
186
+ if (dangerousPaths.includes(dirPath) || dirPath === "") {
187
+ logger.warn(`Skipping deletion of dangerous path: ${dirPath}`);
188
+ return;
189
+ }
190
+ try {
191
+ if (await fileExists(dirPath)) {
192
+ await rm(dirPath, { recursive: true, force: true });
193
+ }
194
+ } catch (error) {
195
+ logger.warn(`Failed to remove directory ${dirPath}:`, error);
170
196
  }
171
- return items.filter((item) => item.isFile()).map((item) => join(item.parentPath, item.name));
172
197
  }
173
198
  async function removeFile(filepath) {
174
199
  logger.debug(`Removing file: ${filepath}`);
@@ -1674,7 +1699,7 @@ var CommandsProcessor = class extends FeatureProcessor {
1674
1699
  );
1675
1700
  const rulesyncCommands = (await Promise.allSettled(
1676
1701
  rulesyncCommandPaths.map(
1677
- (path2) => RulesyncCommand.fromFile({ relativeFilePath: basename11(path2) })
1702
+ (path3) => RulesyncCommand.fromFile({ relativeFilePath: basename11(path3) })
1678
1703
  )
1679
1704
  )).filter((result) => result.status === "fulfilled").map((result) => result.value);
1680
1705
  logger.info(`Successfully loaded ${rulesyncCommands.length} rulesync commands`);
@@ -1716,45 +1741,45 @@ var CommandsProcessor = class extends FeatureProcessor {
1716
1741
  join12(this.baseDir, relativeDirPath, `*.${extension}`)
1717
1742
  );
1718
1743
  const toolCommands = (await Promise.allSettled(
1719
- commandFilePaths.map((path2) => {
1744
+ commandFilePaths.map((path3) => {
1720
1745
  switch (toolTarget) {
1721
1746
  case "agentsmd":
1722
1747
  return AgentsmdCommand.fromFile({
1723
1748
  baseDir: this.baseDir,
1724
- relativeFilePath: basename11(path2)
1749
+ relativeFilePath: basename11(path3)
1725
1750
  });
1726
1751
  case "claudecode":
1727
1752
  return ClaudecodeCommand.fromFile({
1728
1753
  baseDir: this.baseDir,
1729
- relativeFilePath: basename11(path2),
1754
+ relativeFilePath: basename11(path3),
1730
1755
  global: this.global
1731
1756
  });
1732
1757
  case "geminicli":
1733
1758
  return GeminiCliCommand.fromFile({
1734
1759
  baseDir: this.baseDir,
1735
- relativeFilePath: basename11(path2),
1760
+ relativeFilePath: basename11(path3),
1736
1761
  global: this.global
1737
1762
  });
1738
1763
  case "roo":
1739
1764
  return RooCommand.fromFile({
1740
1765
  baseDir: this.baseDir,
1741
- relativeFilePath: basename11(path2)
1766
+ relativeFilePath: basename11(path3)
1742
1767
  });
1743
1768
  case "copilot":
1744
1769
  return CopilotCommand.fromFile({
1745
1770
  baseDir: this.baseDir,
1746
- relativeFilePath: basename11(path2)
1771
+ relativeFilePath: basename11(path3)
1747
1772
  });
1748
1773
  case "cursor":
1749
1774
  return CursorCommand.fromFile({
1750
1775
  baseDir: this.baseDir,
1751
- relativeFilePath: basename11(path2),
1776
+ relativeFilePath: basename11(path3),
1752
1777
  global: this.global
1753
1778
  });
1754
1779
  case "codexcli":
1755
1780
  return CodexcliCommand.fromFile({
1756
1781
  baseDir: this.baseDir,
1757
- relativeFilePath: basename11(path2),
1782
+ relativeFilePath: basename11(path3),
1758
1783
  global: this.global
1759
1784
  });
1760
1785
  default:
@@ -2551,7 +2576,12 @@ var IgnoreProcessor = class extends FeatureProcessor {
2551
2576
  const toolIgnores = await this.loadToolIgnores();
2552
2577
  return toolIgnores;
2553
2578
  } catch (error) {
2554
- logger.error(`Failed to load tool files: ${formatError(error)}`);
2579
+ const errorMessage = `Failed to load tool files: ${formatError(error)}`;
2580
+ if (error instanceof Error && error.message.includes("no such file or directory")) {
2581
+ logger.debug(errorMessage);
2582
+ } else {
2583
+ logger.error(errorMessage);
2584
+ }
2555
2585
  return [];
2556
2586
  }
2557
2587
  }
@@ -3572,9 +3602,7 @@ var McpProcessor = class extends FeatureProcessor {
3572
3602
  try {
3573
3603
  return [await RulesyncMcp.fromFile({ modularMcp: this.modularMcp })];
3574
3604
  } catch (error) {
3575
- logger.error(
3576
- `Failed to load MCP files for tool target: ${this.toolTarget}: ${formatError(error)}`
3577
- );
3605
+ logger.error(`Failed to load a Rulesync MCP file: ${formatError(error)}`);
3578
3606
  return [];
3579
3607
  }
3580
3608
  }
@@ -3668,9 +3696,12 @@ var McpProcessor = class extends FeatureProcessor {
3668
3696
  logger.info(`Successfully loaded ${toolMcps.length} ${this.toolTarget} MCP files`);
3669
3697
  return toolMcps;
3670
3698
  } catch (error) {
3671
- logger.error(
3672
- `Failed to load MCP files for tool target: ${this.toolTarget}: ${formatError(error)}`
3673
- );
3699
+ const errorMessage = `Failed to load MCP files for tool target: ${this.toolTarget}: ${formatError(error)}`;
3700
+ if (error instanceof Error && error.message.includes("no such file or directory")) {
3701
+ logger.debug(errorMessage);
3702
+ } else {
3703
+ logger.error(errorMessage);
3704
+ }
3674
3705
  return [];
3675
3706
  }
3676
3707
  }
@@ -4541,7 +4572,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
4541
4572
  fromFile
4542
4573
  }) {
4543
4574
  const paths = await findFilesByGlobs(join44(this.baseDir, relativeDirPath, "*.md"));
4544
- const subagents = (await Promise.allSettled(paths.map((path2) => fromFile(basename14(path2))))).filter((r) => r.status === "fulfilled").map((r) => r.value);
4575
+ const subagents = (await Promise.allSettled(paths.map((path3) => fromFile(basename14(path3))))).filter((r) => r.status === "fulfilled").map((r) => r.value);
4545
4576
  logger.info(`Successfully loaded ${subagents.length} ${relativeDirPath} subagents`);
4546
4577
  return subagents;
4547
4578
  }
@@ -7063,6 +7094,614 @@ For example, if the user instructs \`Call planner subagent to plan the refactori
7063
7094
  }
7064
7095
  };
7065
7096
 
7097
+ // src/features/skills/skills-processor.ts
7098
+ import { basename as basename19, join as join69 } from "path";
7099
+ import { z as z27 } from "zod/mini";
7100
+
7101
+ // src/types/dir-feature-processor.ts
7102
+ import { join as join65 } from "path";
7103
+ var DirFeatureProcessor = class {
7104
+ baseDir;
7105
+ constructor({ baseDir = process.cwd() }) {
7106
+ this.baseDir = baseDir;
7107
+ }
7108
+ /**
7109
+ * Return tool targets that this feature supports.
7110
+ */
7111
+ static getToolTargets(_params = {}) {
7112
+ throw new Error("Not implemented");
7113
+ }
7114
+ /**
7115
+ * Once converted to rulesync/tool dirs, write them to the filesystem.
7116
+ * Returns the number of directories written.
7117
+ */
7118
+ async writeAiDirs(aiDirs) {
7119
+ for (const aiDir of aiDirs) {
7120
+ const dirPath = aiDir.getDirPath();
7121
+ await ensureDir(dirPath);
7122
+ const mainFile = aiDir.getMainFile();
7123
+ if (mainFile) {
7124
+ const mainFilePath = join65(dirPath, mainFile.name);
7125
+ const contentWithNewline = addTrailingNewline(mainFile.body);
7126
+ await writeFileContent(mainFilePath, contentWithNewline);
7127
+ }
7128
+ const otherFiles = aiDir.getOtherFiles();
7129
+ for (const file of otherFiles) {
7130
+ const filePath = join65(dirPath, file.relativeFilePathToDirPath);
7131
+ const contentWithNewline = addTrailingNewline(file.fileBuffer.toString("utf-8"));
7132
+ await writeFileContent(filePath, contentWithNewline);
7133
+ }
7134
+ }
7135
+ return aiDirs.length;
7136
+ }
7137
+ async removeAiDirs(aiDirs) {
7138
+ for (const aiDir of aiDirs) {
7139
+ await removeDirectory(aiDir.getDirPath());
7140
+ }
7141
+ }
7142
+ };
7143
+
7144
+ // src/features/skills/claudecode-skill.ts
7145
+ import { join as join68 } from "path";
7146
+ import { z as z26 } from "zod/mini";
7147
+
7148
+ // src/constants/general.ts
7149
+ var SKILL_FILE_NAME = "SKILL.md";
7150
+
7151
+ // src/features/skills/rulesync-skill.ts
7152
+ import { join as join67 } from "path";
7153
+ import { z as z25 } from "zod/mini";
7154
+
7155
+ // src/types/ai-dir.ts
7156
+ import path2, { basename as basename18, join as join66, relative as relative3, resolve as resolve4 } from "path";
7157
+ var AiDir = class {
7158
+ /**
7159
+ * @example "."
7160
+ */
7161
+ baseDir;
7162
+ /**
7163
+ * @example ".rulesync/skills"
7164
+ */
7165
+ relativeDirPath;
7166
+ /**
7167
+ * @example "my-skill"
7168
+ */
7169
+ dirName;
7170
+ /**
7171
+ * Optional main file with frontmatter support
7172
+ */
7173
+ mainFile;
7174
+ /**
7175
+ * Additional files in the directory
7176
+ */
7177
+ otherFiles;
7178
+ /**
7179
+ * @example false
7180
+ */
7181
+ global;
7182
+ constructor({
7183
+ baseDir = process.cwd(),
7184
+ relativeDirPath,
7185
+ dirName,
7186
+ mainFile,
7187
+ otherFiles = [],
7188
+ global = false
7189
+ }) {
7190
+ if (dirName.includes(path2.sep) || dirName.includes("/") || dirName.includes("\\")) {
7191
+ throw new Error(`Directory name cannot contain path separators: dirName="${dirName}"`);
7192
+ }
7193
+ this.baseDir = baseDir;
7194
+ this.relativeDirPath = relativeDirPath;
7195
+ this.dirName = dirName;
7196
+ this.mainFile = mainFile;
7197
+ this.otherFiles = otherFiles;
7198
+ this.global = global;
7199
+ }
7200
+ static async fromDir(_params) {
7201
+ throw new Error("Please implement this method in the subclass.");
7202
+ }
7203
+ getBaseDir() {
7204
+ return this.baseDir;
7205
+ }
7206
+ getRelativeDirPath() {
7207
+ return this.relativeDirPath;
7208
+ }
7209
+ getDirName() {
7210
+ return this.dirName;
7211
+ }
7212
+ getDirPath() {
7213
+ const fullPath = path2.join(this.baseDir, this.relativeDirPath, this.dirName);
7214
+ const resolvedFull = resolve4(fullPath);
7215
+ const resolvedBase = resolve4(this.baseDir);
7216
+ const rel = relative3(resolvedBase, resolvedFull);
7217
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) {
7218
+ throw new Error(
7219
+ `Path traversal detected: Final path escapes baseDir. baseDir="${this.baseDir}", relativeDirPath="${this.relativeDirPath}", dirName="${this.dirName}"`
7220
+ );
7221
+ }
7222
+ return fullPath;
7223
+ }
7224
+ getMainFile() {
7225
+ return this.mainFile;
7226
+ }
7227
+ getOtherFiles() {
7228
+ return this.otherFiles;
7229
+ }
7230
+ getRelativePathFromCwd() {
7231
+ return path2.join(this.relativeDirPath, this.dirName);
7232
+ }
7233
+ getGlobal() {
7234
+ return this.global;
7235
+ }
7236
+ setMainFile(name, body, frontmatter) {
7237
+ this.mainFile = { name, body, frontmatter };
7238
+ }
7239
+ /**
7240
+ * Recursively collects all files from a directory, excluding the specified main file.
7241
+ * This is a common utility for loading additional files alongside the main file.
7242
+ *
7243
+ * @param baseDir - The base directory path
7244
+ * @param relativeDirPath - The relative path to the directory containing the skill
7245
+ * @param dirName - The name of the directory
7246
+ * @param excludeFileName - The name of the file to exclude (typically the main file)
7247
+ * @returns Array of files with their relative paths and buffers
7248
+ */
7249
+ static async collectOtherFiles(baseDir, relativeDirPath, dirName, excludeFileName) {
7250
+ const dirPath = join66(baseDir, relativeDirPath, dirName);
7251
+ const glob = join66(dirPath, "**", "*");
7252
+ const filePaths = await findFilesByGlobs(glob, { type: "file" });
7253
+ const filteredPaths = filePaths.filter((filePath) => basename18(filePath) !== excludeFileName);
7254
+ const files = await Promise.all(
7255
+ filteredPaths.map(async (filePath) => {
7256
+ const fileBuffer = await readFileBuffer(filePath);
7257
+ return {
7258
+ relativeFilePathToDirPath: relative3(dirPath, filePath),
7259
+ fileBuffer
7260
+ };
7261
+ })
7262
+ );
7263
+ return files;
7264
+ }
7265
+ };
7266
+
7267
+ // src/features/skills/rulesync-skill.ts
7268
+ var RulesyncSkillFrontmatterSchema = z25.object({
7269
+ name: z25.string(),
7270
+ description: z25.string(),
7271
+ claudecode: z25.optional(
7272
+ z25.object({
7273
+ "allowed-tools": z25.optional(z25.array(z25.string()))
7274
+ })
7275
+ )
7276
+ });
7277
+ var RulesyncSkill = class _RulesyncSkill extends AiDir {
7278
+ constructor({
7279
+ baseDir = process.cwd(),
7280
+ relativeDirPath = RULESYNC_SKILLS_RELATIVE_DIR_PATH,
7281
+ dirName,
7282
+ frontmatter,
7283
+ body,
7284
+ otherFiles = [],
7285
+ validate = true,
7286
+ global = false
7287
+ }) {
7288
+ super({
7289
+ baseDir,
7290
+ relativeDirPath,
7291
+ dirName,
7292
+ mainFile: {
7293
+ name: SKILL_FILE_NAME,
7294
+ body,
7295
+ frontmatter: { ...frontmatter }
7296
+ },
7297
+ otherFiles,
7298
+ global
7299
+ });
7300
+ if (validate) {
7301
+ const result = this.validate();
7302
+ if (!result.success) {
7303
+ throw result.error;
7304
+ }
7305
+ }
7306
+ }
7307
+ static getSettablePaths() {
7308
+ return {
7309
+ relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH
7310
+ };
7311
+ }
7312
+ getFrontmatter() {
7313
+ if (!this.mainFile?.frontmatter) {
7314
+ throw new Error("Frontmatter is not defined");
7315
+ }
7316
+ const result = RulesyncSkillFrontmatterSchema.parse(this.mainFile.frontmatter);
7317
+ return result;
7318
+ }
7319
+ getBody() {
7320
+ return this.mainFile?.body ?? "";
7321
+ }
7322
+ validate() {
7323
+ const result = RulesyncSkillFrontmatterSchema.safeParse(this.mainFile?.frontmatter);
7324
+ if (!result.success) {
7325
+ return {
7326
+ success: false,
7327
+ error: new Error(
7328
+ `Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`
7329
+ )
7330
+ };
7331
+ }
7332
+ return { success: true, error: null };
7333
+ }
7334
+ static async fromDir({
7335
+ baseDir = process.cwd(),
7336
+ relativeDirPath = RULESYNC_SKILLS_RELATIVE_DIR_PATH,
7337
+ dirName,
7338
+ global = false
7339
+ }) {
7340
+ const skillDirPath = join67(baseDir, relativeDirPath, dirName);
7341
+ const skillFilePath = join67(skillDirPath, SKILL_FILE_NAME);
7342
+ if (!await fileExists(skillFilePath)) {
7343
+ throw new Error(`${SKILL_FILE_NAME} not found in ${skillDirPath}`);
7344
+ }
7345
+ const fileContent = await readFileContent(skillFilePath);
7346
+ const { frontmatter, body: content } = parseFrontmatter(fileContent);
7347
+ const result = RulesyncSkillFrontmatterSchema.safeParse(frontmatter);
7348
+ if (!result.success) {
7349
+ throw new Error(`Invalid frontmatter in ${skillFilePath}: ${formatError(result.error)}`);
7350
+ }
7351
+ const otherFiles = await this.collectOtherFiles(
7352
+ baseDir,
7353
+ relativeDirPath,
7354
+ dirName,
7355
+ SKILL_FILE_NAME
7356
+ );
7357
+ return new _RulesyncSkill({
7358
+ baseDir,
7359
+ relativeDirPath,
7360
+ dirName,
7361
+ frontmatter: result.data,
7362
+ body: content.trim(),
7363
+ otherFiles,
7364
+ validate: true,
7365
+ global
7366
+ });
7367
+ }
7368
+ };
7369
+
7370
+ // src/features/skills/tool-skill.ts
7371
+ var ToolSkill = class extends AiDir {
7372
+ /**
7373
+ * Get the settable paths for this tool's skill directories.
7374
+ *
7375
+ * @param options - Optional configuration including global mode
7376
+ * @returns Object containing the relative directory path
7377
+ */
7378
+ static getSettablePaths(_options) {
7379
+ throw new Error("Please implement this method in the subclass.");
7380
+ }
7381
+ /**
7382
+ * Load a skill from a tool-specific directory.
7383
+ *
7384
+ * This method should:
7385
+ * 1. Read the SKILL.md file content
7386
+ * 2. Parse tool-specific frontmatter format
7387
+ * 3. Validate the parsed data
7388
+ * 4. Collect other skill files in the directory
7389
+ * 5. Return a concrete ToolSkill instance
7390
+ *
7391
+ * @param params - Parameters including the skill directory name
7392
+ * @returns Promise resolving to a concrete ToolSkill instance
7393
+ */
7394
+ static async fromDir(_params) {
7395
+ throw new Error("Please implement this method in the subclass.");
7396
+ }
7397
+ /**
7398
+ * Convert a RulesyncSkill to the tool-specific skill format.
7399
+ *
7400
+ * This method should:
7401
+ * 1. Extract relevant data from the RulesyncSkill
7402
+ * 2. Transform frontmatter to tool-specific format
7403
+ * 3. Transform body content if needed
7404
+ * 4. Preserve other skill files
7405
+ * 5. Return a concrete ToolSkill instance
7406
+ *
7407
+ * @param params - Parameters including the RulesyncSkill to convert
7408
+ * @returns A concrete ToolSkill instance
7409
+ */
7410
+ static fromRulesyncSkill(_params) {
7411
+ throw new Error("Please implement this method in the subclass.");
7412
+ }
7413
+ /**
7414
+ * Check if this tool is targeted by a RulesyncSkill.
7415
+ * Since skills don't have targets field like commands/subagents,
7416
+ * the default behavior may vary by tool.
7417
+ *
7418
+ * @param rulesyncSkill - The RulesyncSkill to check
7419
+ * @returns True if this tool should use the skill
7420
+ */
7421
+ static isTargetedByRulesyncSkill(_rulesyncSkill) {
7422
+ throw new Error("Please implement this method in the subclass.");
7423
+ }
7424
+ };
7425
+
7426
+ // src/features/skills/claudecode-skill.ts
7427
+ var ClaudecodeSkillFrontmatterSchema = z26.object({
7428
+ name: z26.string(),
7429
+ description: z26.string(),
7430
+ "allowed-tools": z26.optional(z26.array(z26.string()))
7431
+ });
7432
+ var ClaudecodeSkill = class _ClaudecodeSkill extends ToolSkill {
7433
+ constructor({
7434
+ baseDir = process.cwd(),
7435
+ relativeDirPath = join68(".claude", "skills"),
7436
+ dirName,
7437
+ frontmatter,
7438
+ body,
7439
+ otherFiles = [],
7440
+ validate = true,
7441
+ global = false
7442
+ }) {
7443
+ super({
7444
+ baseDir,
7445
+ relativeDirPath,
7446
+ dirName,
7447
+ mainFile: {
7448
+ name: SKILL_FILE_NAME,
7449
+ body,
7450
+ frontmatter: { ...frontmatter }
7451
+ },
7452
+ otherFiles,
7453
+ global
7454
+ });
7455
+ if (validate) {
7456
+ const result = this.validate();
7457
+ if (!result.success) {
7458
+ throw result.error;
7459
+ }
7460
+ }
7461
+ }
7462
+ static getSettablePaths({
7463
+ global: _global = false
7464
+ } = {}) {
7465
+ return {
7466
+ relativeDirPath: join68(".claude", "skills")
7467
+ };
7468
+ }
7469
+ getFrontmatter() {
7470
+ if (!this.mainFile?.frontmatter) {
7471
+ throw new Error("Frontmatter is not defined");
7472
+ }
7473
+ const result = ClaudecodeSkillFrontmatterSchema.parse(this.mainFile.frontmatter);
7474
+ return result;
7475
+ }
7476
+ getBody() {
7477
+ return this.mainFile?.body ?? "";
7478
+ }
7479
+ validate() {
7480
+ if (this.mainFile === void 0) {
7481
+ return {
7482
+ success: false,
7483
+ error: new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
7484
+ };
7485
+ }
7486
+ const result = ClaudecodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
7487
+ if (!result.success) {
7488
+ return {
7489
+ success: false,
7490
+ error: new Error(
7491
+ `Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`
7492
+ )
7493
+ };
7494
+ }
7495
+ return { success: true, error: null };
7496
+ }
7497
+ toRulesyncSkill() {
7498
+ const frontmatter = this.getFrontmatter();
7499
+ const rulesyncFrontmatter = {
7500
+ name: frontmatter.name,
7501
+ description: frontmatter.description,
7502
+ ...frontmatter["allowed-tools"] && {
7503
+ claudecode: {
7504
+ "allowed-tools": frontmatter["allowed-tools"]
7505
+ }
7506
+ }
7507
+ };
7508
+ return new RulesyncSkill({
7509
+ baseDir: this.baseDir,
7510
+ relativeDirPath: this.relativeDirPath,
7511
+ dirName: this.getDirName(),
7512
+ frontmatter: rulesyncFrontmatter,
7513
+ body: this.getBody(),
7514
+ otherFiles: this.getOtherFiles(),
7515
+ validate: true,
7516
+ global: this.global
7517
+ });
7518
+ }
7519
+ static fromRulesyncSkill({
7520
+ rulesyncSkill,
7521
+ validate = true,
7522
+ global = false
7523
+ }) {
7524
+ const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
7525
+ const claudecodeFrontmatter = {
7526
+ name: rulesyncFrontmatter.name,
7527
+ description: rulesyncFrontmatter.description,
7528
+ "allowed-tools": rulesyncFrontmatter.claudecode?.["allowed-tools"]
7529
+ };
7530
+ const settablePaths = _ClaudecodeSkill.getSettablePaths({ global });
7531
+ return new _ClaudecodeSkill({
7532
+ baseDir: rulesyncSkill.getBaseDir(),
7533
+ relativeDirPath: settablePaths.relativeDirPath,
7534
+ dirName: rulesyncSkill.getDirName(),
7535
+ frontmatter: claudecodeFrontmatter,
7536
+ body: rulesyncSkill.getBody(),
7537
+ otherFiles: rulesyncSkill.getOtherFiles(),
7538
+ validate,
7539
+ global
7540
+ });
7541
+ }
7542
+ static isTargetedByRulesyncSkill(_rulesyncSkill) {
7543
+ return true;
7544
+ }
7545
+ static async fromDir({
7546
+ baseDir = process.cwd(),
7547
+ relativeDirPath,
7548
+ dirName,
7549
+ global = false
7550
+ }) {
7551
+ const settablePaths = this.getSettablePaths({ global });
7552
+ const actualRelativeDirPath = relativeDirPath ?? settablePaths.relativeDirPath;
7553
+ const skillDirPath = join68(baseDir, actualRelativeDirPath, dirName);
7554
+ const skillFilePath = join68(skillDirPath, SKILL_FILE_NAME);
7555
+ if (!await fileExists(skillFilePath)) {
7556
+ throw new Error(`${SKILL_FILE_NAME} not found in ${skillDirPath}`);
7557
+ }
7558
+ const fileContent = await readFileContent(skillFilePath);
7559
+ const { frontmatter, body: content } = parseFrontmatter(fileContent);
7560
+ const result = ClaudecodeSkillFrontmatterSchema.safeParse(frontmatter);
7561
+ if (!result.success) {
7562
+ throw new Error(`Invalid frontmatter in ${skillFilePath}: ${formatError(result.error)}`);
7563
+ }
7564
+ const otherFiles = await this.collectOtherFiles(
7565
+ baseDir,
7566
+ actualRelativeDirPath,
7567
+ dirName,
7568
+ SKILL_FILE_NAME
7569
+ );
7570
+ return new _ClaudecodeSkill({
7571
+ baseDir,
7572
+ relativeDirPath: actualRelativeDirPath,
7573
+ dirName,
7574
+ frontmatter: result.data,
7575
+ body: content.trim(),
7576
+ otherFiles,
7577
+ validate: true,
7578
+ global
7579
+ });
7580
+ }
7581
+ };
7582
+
7583
+ // src/features/skills/skills-processor.ts
7584
+ var skillsProcessorToolTargets = ["claudecode"];
7585
+ var skillsProcessorToolTargetsGlobal = ["claudecode"];
7586
+ var SkillsProcessorToolTargetSchema = z27.enum(skillsProcessorToolTargets);
7587
+ var SkillsProcessor = class extends DirFeatureProcessor {
7588
+ toolTarget;
7589
+ global;
7590
+ constructor({
7591
+ baseDir = process.cwd(),
7592
+ toolTarget,
7593
+ global = false
7594
+ }) {
7595
+ super({ baseDir });
7596
+ const result = SkillsProcessorToolTargetSchema.safeParse(toolTarget);
7597
+ if (!result.success) {
7598
+ throw new Error(
7599
+ `Invalid tool target for SkillsProcessor: ${toolTarget}. ${formatError(result.error)}`
7600
+ );
7601
+ }
7602
+ this.toolTarget = result.data;
7603
+ this.global = global;
7604
+ }
7605
+ async convertRulesyncDirsToToolDirs(rulesyncDirs) {
7606
+ const rulesyncSkills = rulesyncDirs.filter(
7607
+ (dir) => dir instanceof RulesyncSkill
7608
+ );
7609
+ const toolSkills = rulesyncSkills.map((rulesyncSkill) => {
7610
+ switch (this.toolTarget) {
7611
+ case "claudecode":
7612
+ if (!ClaudecodeSkill.isTargetedByRulesyncSkill(rulesyncSkill)) {
7613
+ return null;
7614
+ }
7615
+ return ClaudecodeSkill.fromRulesyncSkill({
7616
+ rulesyncSkill,
7617
+ global: this.global
7618
+ });
7619
+ default:
7620
+ throw new Error(`Unsupported tool target: ${this.toolTarget}`);
7621
+ }
7622
+ }).filter((skill) => skill !== null);
7623
+ return toolSkills;
7624
+ }
7625
+ async convertToolDirsToRulesyncDirs(toolDirs) {
7626
+ const toolSkills = toolDirs.filter((dir) => dir instanceof ToolSkill);
7627
+ const rulesyncSkills = toolSkills.map((toolSkill) => {
7628
+ return toolSkill.toRulesyncSkill();
7629
+ });
7630
+ return rulesyncSkills;
7631
+ }
7632
+ /**
7633
+ * Implementation of abstract method from DirFeatureProcessor
7634
+ * Load and parse rulesync skill directories from .rulesync/skills/ directory
7635
+ */
7636
+ async loadRulesyncDirs() {
7637
+ const paths = RulesyncSkill.getSettablePaths();
7638
+ const rulesyncSkillsDirPath = join69(this.baseDir, paths.relativeDirPath);
7639
+ const dirPaths = await findFilesByGlobs(join69(rulesyncSkillsDirPath, "*"), { type: "dir" });
7640
+ const dirNames = dirPaths.map((path3) => basename19(path3));
7641
+ const results = await Promise.allSettled(
7642
+ dirNames.map(
7643
+ (dirName) => RulesyncSkill.fromDir({ baseDir: this.baseDir, dirName, global: this.global })
7644
+ )
7645
+ );
7646
+ const rulesyncSkills = [];
7647
+ for (const result of results) {
7648
+ if (result.status === "fulfilled") {
7649
+ rulesyncSkills.push(result.value);
7650
+ }
7651
+ }
7652
+ logger.info(`Successfully loaded ${rulesyncSkills.length} rulesync skills`);
7653
+ return rulesyncSkills;
7654
+ }
7655
+ /**
7656
+ * Implementation of abstract method from DirFeatureProcessor
7657
+ * Load tool-specific skill configurations and parse them into ToolSkill instances
7658
+ */
7659
+ async loadToolDirs() {
7660
+ switch (this.toolTarget) {
7661
+ case "claudecode":
7662
+ return await this.loadClaudecodeSkills();
7663
+ default:
7664
+ throw new Error(`Unsupported tool target: ${this.toolTarget}`);
7665
+ }
7666
+ }
7667
+ async loadToolDirsToDelete() {
7668
+ return this.loadToolDirs();
7669
+ }
7670
+ /**
7671
+ * Load Claude Code skill configurations from .claude/skills/ directory
7672
+ */
7673
+ async loadClaudecodeSkills() {
7674
+ const paths = ClaudecodeSkill.getSettablePaths({ global: this.global });
7675
+ const skillsDirPath = join69(this.baseDir, paths.relativeDirPath);
7676
+ const dirPaths = await findFilesByGlobs(join69(skillsDirPath, "*"), { type: "dir" });
7677
+ const dirNames = dirPaths.map((path3) => basename19(path3));
7678
+ const toolSkills = (await Promise.allSettled(
7679
+ dirNames.map(
7680
+ (dirName) => ClaudecodeSkill.fromDir({
7681
+ baseDir: this.baseDir,
7682
+ dirName,
7683
+ global: this.global
7684
+ })
7685
+ )
7686
+ )).filter((result) => result.status === "fulfilled").map((result) => result.value);
7687
+ logger.info(`Successfully loaded ${toolSkills.length} ${paths.relativeDirPath} skills`);
7688
+ return toolSkills;
7689
+ }
7690
+ /**
7691
+ * Implementation of abstract method from DirFeatureProcessor
7692
+ * Return the tool targets that this processor supports
7693
+ */
7694
+ static getToolTargets(_params = {}) {
7695
+ return skillsProcessorToolTargets;
7696
+ }
7697
+ /**
7698
+ * Return the tool targets that this processor supports in global mode
7699
+ */
7700
+ static getToolTargetsGlobal() {
7701
+ return skillsProcessorToolTargetsGlobal;
7702
+ }
7703
+ };
7704
+
7066
7705
  // src/cli/commands/generate.ts
7067
7706
  async function generateCommand(options) {
7068
7707
  const config = await ConfigResolver.resolve(options);
@@ -7078,7 +7717,8 @@ async function generateCommand(options) {
7078
7717
  const totalMcpOutputs = await generateMcp(config);
7079
7718
  const totalCommandOutputs = await generateCommands(config);
7080
7719
  const totalSubagentOutputs = await generateSubagents(config);
7081
- const totalGenerated = totalRulesOutputs + totalMcpOutputs + totalCommandOutputs + totalIgnoreOutputs + totalSubagentOutputs;
7720
+ const totalSkillOutputs = await generateSkills(config);
7721
+ const totalGenerated = totalRulesOutputs + totalMcpOutputs + totalCommandOutputs + totalIgnoreOutputs + totalSubagentOutputs + totalSkillOutputs;
7082
7722
  if (totalGenerated === 0) {
7083
7723
  const enabledFeatures = config.getFeatures().join(", ");
7084
7724
  logger.warn(`\u26A0\uFE0F No files generated for enabled features: ${enabledFeatures}`);
@@ -7091,6 +7731,7 @@ async function generateCommand(options) {
7091
7731
  if (totalMcpOutputs > 0) parts.push(`${totalMcpOutputs} MCP files`);
7092
7732
  if (totalCommandOutputs > 0) parts.push(`${totalCommandOutputs} commands`);
7093
7733
  if (totalSubagentOutputs > 0) parts.push(`${totalSubagentOutputs} subagents`);
7734
+ if (totalSkillOutputs > 0) parts.push(`${totalSkillOutputs} skills`);
7094
7735
  logger.success(`\u{1F389} All done! Generated ${totalGenerated} file(s) total (${parts.join(" + ")})`);
7095
7736
  }
7096
7737
  }
@@ -7265,11 +7906,39 @@ async function generateSubagents(config) {
7265
7906
  }
7266
7907
  return totalSubagentOutputs;
7267
7908
  }
7909
+ async function generateSkills(config) {
7910
+ if (!config.getFeatures().includes("skills")) {
7911
+ logger.debug("Skipping skill generation (not in --features)");
7912
+ return 0;
7913
+ }
7914
+ let totalSkillOutputs = 0;
7915
+ logger.info("Generating skill files...");
7916
+ const toolTargets = config.getGlobal() ? intersection(config.getTargets(), SkillsProcessor.getToolTargetsGlobal()) : intersection(config.getTargets(), SkillsProcessor.getToolTargets());
7917
+ for (const baseDir of config.getBaseDirs()) {
7918
+ for (const toolTarget of toolTargets) {
7919
+ const processor = new SkillsProcessor({
7920
+ baseDir,
7921
+ toolTarget,
7922
+ global: config.getGlobal()
7923
+ });
7924
+ if (config.getDelete()) {
7925
+ const oldToolDirs = await processor.loadToolDirsToDelete();
7926
+ await processor.removeAiDirs(oldToolDirs);
7927
+ }
7928
+ const rulesyncDirs = await processor.loadRulesyncDirs();
7929
+ const toolDirs = await processor.convertRulesyncDirsToToolDirs(rulesyncDirs);
7930
+ const writtenCount = await processor.writeAiDirs(toolDirs);
7931
+ totalSkillOutputs += writtenCount;
7932
+ logger.success(`Generated ${writtenCount} ${toolTarget} skill(s) in ${baseDir}`);
7933
+ }
7934
+ }
7935
+ return totalSkillOutputs;
7936
+ }
7268
7937
 
7269
7938
  // src/cli/commands/gitignore.ts
7270
- import { join as join65 } from "path";
7939
+ import { join as join70 } from "path";
7271
7940
  var gitignoreCommand = async () => {
7272
- const gitignorePath = join65(process.cwd(), ".gitignore");
7941
+ const gitignorePath = join70(process.cwd(), ".gitignore");
7273
7942
  const rulesFilesToIgnore = [
7274
7943
  "# Generated by rulesync - AI tool configuration files",
7275
7944
  // AGENTS.md
@@ -7286,6 +7955,7 @@ var gitignoreCommand = async () => {
7286
7955
  "**/.claude/memories/",
7287
7956
  "**/.claude/commands/",
7288
7957
  "**/.claude/agents/",
7958
+ "**/.claude/skills/",
7289
7959
  "**/.claude/settings.local.json",
7290
7960
  "**/.mcp.json",
7291
7961
  // Cline
@@ -7379,6 +8049,7 @@ async function importCommand(options) {
7379
8049
  await importMcp(config, tool);
7380
8050
  await importCommands(config, tool);
7381
8051
  await importSubagents(config, tool);
8052
+ await importSkills(config, tool);
7382
8053
  }
7383
8054
  async function importRules(config, tool) {
7384
8055
  if (!config.getFeatures().includes("rules")) {
@@ -7508,9 +8179,34 @@ async function importSubagents(config, tool) {
7508
8179
  }
7509
8180
  return writtenCount;
7510
8181
  }
8182
+ async function importSkills(config, tool) {
8183
+ if (!config.getFeatures().includes("skills")) {
8184
+ return 0;
8185
+ }
8186
+ const global = config.getGlobal();
8187
+ const supportedTargets = SkillsProcessor.getToolTargets();
8188
+ if (!supportedTargets.includes(tool)) {
8189
+ return 0;
8190
+ }
8191
+ const skillsProcessor = new SkillsProcessor({
8192
+ baseDir: config.getBaseDirs()[0] ?? ".",
8193
+ toolTarget: tool,
8194
+ global
8195
+ });
8196
+ const toolDirs = await skillsProcessor.loadToolDirs();
8197
+ if (toolDirs.length === 0) {
8198
+ return 0;
8199
+ }
8200
+ const rulesyncDirs = await skillsProcessor.convertToolDirsToRulesyncDirs(toolDirs);
8201
+ const writtenCount = await skillsProcessor.writeAiDirs(rulesyncDirs);
8202
+ if (config.getVerbose() && writtenCount > 0) {
8203
+ logger.success(`Created ${writtenCount} skill directories`);
8204
+ }
8205
+ return writtenCount;
8206
+ }
7511
8207
 
7512
8208
  // src/cli/commands/init.ts
7513
- import { join as join66 } from "path";
8209
+ import { join as join71 } from "path";
7514
8210
  async function initCommand() {
7515
8211
  logger.info("Initializing rulesync...");
7516
8212
  await ensureDir(RULESYNC_RELATIVE_DIR_PATH);
@@ -7673,14 +8369,14 @@ Attention, again, you are just the planner, so though you can read any files and
7673
8369
  await ensureDir(commandPaths.relativeDirPath);
7674
8370
  await ensureDir(subagentPaths.relativeDirPath);
7675
8371
  await ensureDir(ignorePaths.relativeDirPath);
7676
- const ruleFilepath = join66(rulePaths.recommended.relativeDirPath, sampleRuleFile.filename);
8372
+ const ruleFilepath = join71(rulePaths.recommended.relativeDirPath, sampleRuleFile.filename);
7677
8373
  if (!await fileExists(ruleFilepath)) {
7678
8374
  await writeFileContent(ruleFilepath, sampleRuleFile.content);
7679
8375
  logger.success(`Created ${ruleFilepath}`);
7680
8376
  } else {
7681
8377
  logger.info(`Skipped ${ruleFilepath} (already exists)`);
7682
8378
  }
7683
- const mcpFilepath = join66(
8379
+ const mcpFilepath = join71(
7684
8380
  mcpPaths.recommended.relativeDirPath,
7685
8381
  mcpPaths.recommended.relativeFilePath
7686
8382
  );
@@ -7690,21 +8386,21 @@ Attention, again, you are just the planner, so though you can read any files and
7690
8386
  } else {
7691
8387
  logger.info(`Skipped ${mcpFilepath} (already exists)`);
7692
8388
  }
7693
- const commandFilepath = join66(commandPaths.relativeDirPath, sampleCommandFile.filename);
8389
+ const commandFilepath = join71(commandPaths.relativeDirPath, sampleCommandFile.filename);
7694
8390
  if (!await fileExists(commandFilepath)) {
7695
8391
  await writeFileContent(commandFilepath, sampleCommandFile.content);
7696
8392
  logger.success(`Created ${commandFilepath}`);
7697
8393
  } else {
7698
8394
  logger.info(`Skipped ${commandFilepath} (already exists)`);
7699
8395
  }
7700
- const subagentFilepath = join66(subagentPaths.relativeDirPath, sampleSubagentFile.filename);
8396
+ const subagentFilepath = join71(subagentPaths.relativeDirPath, sampleSubagentFile.filename);
7701
8397
  if (!await fileExists(subagentFilepath)) {
7702
8398
  await writeFileContent(subagentFilepath, sampleSubagentFile.content);
7703
8399
  logger.success(`Created ${subagentFilepath}`);
7704
8400
  } else {
7705
8401
  logger.info(`Skipped ${subagentFilepath} (already exists)`);
7706
8402
  }
7707
- const ignoreFilepath = join66(ignorePaths.relativeDirPath, ignorePaths.relativeFilePath);
8403
+ const ignoreFilepath = join71(ignorePaths.relativeDirPath, ignorePaths.relativeFilePath);
7708
8404
  if (!await fileExists(ignoreFilepath)) {
7709
8405
  await writeFileContent(ignoreFilepath, sampleIgnoreFile.content);
7710
8406
  logger.success(`Created ${ignoreFilepath}`);
@@ -7717,12 +8413,12 @@ Attention, again, you are just the planner, so though you can read any files and
7717
8413
  import { FastMCP } from "fastmcp";
7718
8414
 
7719
8415
  // src/mcp/commands.ts
7720
- import { basename as basename18, join as join67 } from "path";
7721
- import { z as z25 } from "zod/mini";
8416
+ import { basename as basename20, join as join72 } from "path";
8417
+ import { z as z28 } from "zod/mini";
7722
8418
  var maxCommandSizeBytes = 1024 * 1024;
7723
8419
  var maxCommandsCount = 1e3;
7724
8420
  async function listCommands() {
7725
- const commandsDir = join67(process.cwd(), RULESYNC_COMMANDS_RELATIVE_DIR_PATH);
8421
+ const commandsDir = join72(process.cwd(), RULESYNC_COMMANDS_RELATIVE_DIR_PATH);
7726
8422
  try {
7727
8423
  const files = await listDirectoryFiles(commandsDir);
7728
8424
  const mdFiles = files.filter((file) => file.endsWith(".md"));
@@ -7734,7 +8430,7 @@ async function listCommands() {
7734
8430
  });
7735
8431
  const frontmatter = command.getFrontmatter();
7736
8432
  return {
7737
- relativePathFromCwd: join67(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, file),
8433
+ relativePathFromCwd: join72(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, file),
7738
8434
  frontmatter
7739
8435
  };
7740
8436
  } catch (error) {
@@ -7754,13 +8450,13 @@ async function getCommand({ relativePathFromCwd }) {
7754
8450
  relativePath: relativePathFromCwd,
7755
8451
  intendedRootDir: process.cwd()
7756
8452
  });
7757
- const filename = basename18(relativePathFromCwd);
8453
+ const filename = basename20(relativePathFromCwd);
7758
8454
  try {
7759
8455
  const command = await RulesyncCommand.fromFile({
7760
8456
  relativeFilePath: filename
7761
8457
  });
7762
8458
  return {
7763
- relativePathFromCwd: join67(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename),
8459
+ relativePathFromCwd: join72(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename),
7764
8460
  frontmatter: command.getFrontmatter(),
7765
8461
  body: command.getBody()
7766
8462
  };
@@ -7779,7 +8475,7 @@ async function putCommand({
7779
8475
  relativePath: relativePathFromCwd,
7780
8476
  intendedRootDir: process.cwd()
7781
8477
  });
7782
- const filename = basename18(relativePathFromCwd);
8478
+ const filename = basename20(relativePathFromCwd);
7783
8479
  const estimatedSize = JSON.stringify(frontmatter).length + body.length;
7784
8480
  if (estimatedSize > maxCommandSizeBytes) {
7785
8481
  throw new Error(
@@ -7789,7 +8485,7 @@ async function putCommand({
7789
8485
  try {
7790
8486
  const existingCommands = await listCommands();
7791
8487
  const isUpdate = existingCommands.some(
7792
- (command2) => command2.relativePathFromCwd === join67(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename)
8488
+ (command2) => command2.relativePathFromCwd === join72(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename)
7793
8489
  );
7794
8490
  if (!isUpdate && existingCommands.length >= maxCommandsCount) {
7795
8491
  throw new Error(`Maximum number of commands (${maxCommandsCount}) reached`);
@@ -7804,11 +8500,11 @@ async function putCommand({
7804
8500
  fileContent,
7805
8501
  validate: true
7806
8502
  });
7807
- const commandsDir = join67(process.cwd(), RULESYNC_COMMANDS_RELATIVE_DIR_PATH);
8503
+ const commandsDir = join72(process.cwd(), RULESYNC_COMMANDS_RELATIVE_DIR_PATH);
7808
8504
  await ensureDir(commandsDir);
7809
8505
  await writeFileContent(command.getFilePath(), command.getFileContent());
7810
8506
  return {
7811
- relativePathFromCwd: join67(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename),
8507
+ relativePathFromCwd: join72(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename),
7812
8508
  frontmatter: command.getFrontmatter(),
7813
8509
  body: command.getBody()
7814
8510
  };
@@ -7823,12 +8519,12 @@ async function deleteCommand({ relativePathFromCwd }) {
7823
8519
  relativePath: relativePathFromCwd,
7824
8520
  intendedRootDir: process.cwd()
7825
8521
  });
7826
- const filename = basename18(relativePathFromCwd);
7827
- const fullPath = join67(process.cwd(), RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename);
8522
+ const filename = basename20(relativePathFromCwd);
8523
+ const fullPath = join72(process.cwd(), RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename);
7828
8524
  try {
7829
8525
  await removeFile(fullPath);
7830
8526
  return {
7831
- relativePathFromCwd: join67(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename)
8527
+ relativePathFromCwd: join72(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename)
7832
8528
  };
7833
8529
  } catch (error) {
7834
8530
  throw new Error(`Failed to delete command file ${relativePathFromCwd}: ${formatError(error)}`, {
@@ -7837,23 +8533,23 @@ async function deleteCommand({ relativePathFromCwd }) {
7837
8533
  }
7838
8534
  }
7839
8535
  var commandToolSchemas = {
7840
- listCommands: z25.object({}),
7841
- getCommand: z25.object({
7842
- relativePathFromCwd: z25.string()
8536
+ listCommands: z28.object({}),
8537
+ getCommand: z28.object({
8538
+ relativePathFromCwd: z28.string()
7843
8539
  }),
7844
- putCommand: z25.object({
7845
- relativePathFromCwd: z25.string(),
8540
+ putCommand: z28.object({
8541
+ relativePathFromCwd: z28.string(),
7846
8542
  frontmatter: RulesyncCommandFrontmatterSchema,
7847
- body: z25.string()
8543
+ body: z28.string()
7848
8544
  }),
7849
- deleteCommand: z25.object({
7850
- relativePathFromCwd: z25.string()
8545
+ deleteCommand: z28.object({
8546
+ relativePathFromCwd: z28.string()
7851
8547
  })
7852
8548
  };
7853
8549
  var commandTools = {
7854
8550
  listCommands: {
7855
8551
  name: "listCommands",
7856
- description: `List all commands from ${join67(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, "*.md")} with their frontmatter.`,
8552
+ description: `List all commands from ${join72(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, "*.md")} with their frontmatter.`,
7857
8553
  parameters: commandToolSchemas.listCommands,
7858
8554
  execute: async () => {
7859
8555
  const commands = await listCommands();
@@ -7895,11 +8591,11 @@ var commandTools = {
7895
8591
  };
7896
8592
 
7897
8593
  // src/mcp/ignore.ts
7898
- import { join as join68 } from "path";
7899
- import { z as z26 } from "zod/mini";
8594
+ import { join as join73 } from "path";
8595
+ import { z as z29 } from "zod/mini";
7900
8596
  var maxIgnoreFileSizeBytes = 100 * 1024;
7901
8597
  async function getIgnoreFile() {
7902
- const ignoreFilePath = join68(process.cwd(), RULESYNC_IGNORE_RELATIVE_FILE_PATH);
8598
+ const ignoreFilePath = join73(process.cwd(), RULESYNC_IGNORE_RELATIVE_FILE_PATH);
7903
8599
  try {
7904
8600
  const content = await readFileContent(ignoreFilePath);
7905
8601
  return {
@@ -7913,7 +8609,7 @@ async function getIgnoreFile() {
7913
8609
  }
7914
8610
  }
7915
8611
  async function putIgnoreFile({ content }) {
7916
- const ignoreFilePath = join68(process.cwd(), RULESYNC_IGNORE_RELATIVE_FILE_PATH);
8612
+ const ignoreFilePath = join73(process.cwd(), RULESYNC_IGNORE_RELATIVE_FILE_PATH);
7917
8613
  const contentSizeBytes = Buffer.byteLength(content, "utf8");
7918
8614
  if (contentSizeBytes > maxIgnoreFileSizeBytes) {
7919
8615
  throw new Error(
@@ -7934,7 +8630,7 @@ async function putIgnoreFile({ content }) {
7934
8630
  }
7935
8631
  }
7936
8632
  async function deleteIgnoreFile() {
7937
- const ignoreFilePath = join68(process.cwd(), RULESYNC_IGNORE_RELATIVE_FILE_PATH);
8633
+ const ignoreFilePath = join73(process.cwd(), RULESYNC_IGNORE_RELATIVE_FILE_PATH);
7938
8634
  try {
7939
8635
  await removeFile(ignoreFilePath);
7940
8636
  return {
@@ -7947,11 +8643,11 @@ async function deleteIgnoreFile() {
7947
8643
  }
7948
8644
  }
7949
8645
  var ignoreToolSchemas = {
7950
- getIgnoreFile: z26.object({}),
7951
- putIgnoreFile: z26.object({
7952
- content: z26.string()
8646
+ getIgnoreFile: z29.object({}),
8647
+ putIgnoreFile: z29.object({
8648
+ content: z29.string()
7953
8649
  }),
7954
- deleteIgnoreFile: z26.object({})
8650
+ deleteIgnoreFile: z29.object({})
7955
8651
  };
7956
8652
  var ignoreTools = {
7957
8653
  getIgnoreFile: {
@@ -7984,8 +8680,8 @@ var ignoreTools = {
7984
8680
  };
7985
8681
 
7986
8682
  // src/mcp/mcp.ts
7987
- import { join as join69 } from "path";
7988
- import { z as z27 } from "zod/mini";
8683
+ import { join as join74 } from "path";
8684
+ import { z as z30 } from "zod/mini";
7989
8685
  var maxMcpSizeBytes = 1024 * 1024;
7990
8686
  async function getMcpFile() {
7991
8687
  const config = await ConfigResolver.resolve({});
@@ -7994,7 +8690,7 @@ async function getMcpFile() {
7994
8690
  validate: true,
7995
8691
  modularMcp: config.getModularMcp()
7996
8692
  });
7997
- const relativePathFromCwd = join69(
8693
+ const relativePathFromCwd = join74(
7998
8694
  rulesyncMcp.getRelativeDirPath(),
7999
8695
  rulesyncMcp.getRelativeFilePath()
8000
8696
  );
@@ -8027,7 +8723,7 @@ async function putMcpFile({ content }) {
8027
8723
  const paths = RulesyncMcp.getSettablePaths();
8028
8724
  const relativeDirPath = paths.recommended.relativeDirPath;
8029
8725
  const relativeFilePath = paths.recommended.relativeFilePath;
8030
- const fullPath = join69(baseDir, relativeDirPath, relativeFilePath);
8726
+ const fullPath = join74(baseDir, relativeDirPath, relativeFilePath);
8031
8727
  const rulesyncMcp = new RulesyncMcp({
8032
8728
  baseDir,
8033
8729
  relativeDirPath,
@@ -8036,9 +8732,9 @@ async function putMcpFile({ content }) {
8036
8732
  validate: true,
8037
8733
  modularMcp: config.getModularMcp()
8038
8734
  });
8039
- await ensureDir(join69(baseDir, relativeDirPath));
8735
+ await ensureDir(join74(baseDir, relativeDirPath));
8040
8736
  await writeFileContent(fullPath, content);
8041
- const relativePathFromCwd = join69(relativeDirPath, relativeFilePath);
8737
+ const relativePathFromCwd = join74(relativeDirPath, relativeFilePath);
8042
8738
  return {
8043
8739
  relativePathFromCwd,
8044
8740
  content: rulesyncMcp.getFileContent()
@@ -8053,15 +8749,15 @@ async function deleteMcpFile() {
8053
8749
  try {
8054
8750
  const baseDir = process.cwd();
8055
8751
  const paths = RulesyncMcp.getSettablePaths();
8056
- const recommendedPath = join69(
8752
+ const recommendedPath = join74(
8057
8753
  baseDir,
8058
8754
  paths.recommended.relativeDirPath,
8059
8755
  paths.recommended.relativeFilePath
8060
8756
  );
8061
- const legacyPath = join69(baseDir, paths.legacy.relativeDirPath, paths.legacy.relativeFilePath);
8757
+ const legacyPath = join74(baseDir, paths.legacy.relativeDirPath, paths.legacy.relativeFilePath);
8062
8758
  await removeFile(recommendedPath);
8063
8759
  await removeFile(legacyPath);
8064
- const relativePathFromCwd = join69(
8760
+ const relativePathFromCwd = join74(
8065
8761
  paths.recommended.relativeDirPath,
8066
8762
  paths.recommended.relativeFilePath
8067
8763
  );
@@ -8075,11 +8771,11 @@ async function deleteMcpFile() {
8075
8771
  }
8076
8772
  }
8077
8773
  var mcpToolSchemas = {
8078
- getMcpFile: z27.object({}),
8079
- putMcpFile: z27.object({
8080
- content: z27.string()
8774
+ getMcpFile: z30.object({}),
8775
+ putMcpFile: z30.object({
8776
+ content: z30.string()
8081
8777
  }),
8082
- deleteMcpFile: z27.object({})
8778
+ deleteMcpFile: z30.object({})
8083
8779
  };
8084
8780
  var mcpTools = {
8085
8781
  getMcpFile: {
@@ -8112,12 +8808,12 @@ var mcpTools = {
8112
8808
  };
8113
8809
 
8114
8810
  // src/mcp/rules.ts
8115
- import { basename as basename19, join as join70 } from "path";
8116
- import { z as z28 } from "zod/mini";
8811
+ import { basename as basename21, join as join75 } from "path";
8812
+ import { z as z31 } from "zod/mini";
8117
8813
  var maxRuleSizeBytes = 1024 * 1024;
8118
8814
  var maxRulesCount = 1e3;
8119
8815
  async function listRules() {
8120
- const rulesDir = join70(process.cwd(), RULESYNC_RULES_RELATIVE_DIR_PATH);
8816
+ const rulesDir = join75(process.cwd(), RULESYNC_RULES_RELATIVE_DIR_PATH);
8121
8817
  try {
8122
8818
  const files = await listDirectoryFiles(rulesDir);
8123
8819
  const mdFiles = files.filter((file) => file.endsWith(".md"));
@@ -8130,7 +8826,7 @@ async function listRules() {
8130
8826
  });
8131
8827
  const frontmatter = rule.getFrontmatter();
8132
8828
  return {
8133
- relativePathFromCwd: join70(RULESYNC_RULES_RELATIVE_DIR_PATH, file),
8829
+ relativePathFromCwd: join75(RULESYNC_RULES_RELATIVE_DIR_PATH, file),
8134
8830
  frontmatter
8135
8831
  };
8136
8832
  } catch (error) {
@@ -8150,14 +8846,14 @@ async function getRule({ relativePathFromCwd }) {
8150
8846
  relativePath: relativePathFromCwd,
8151
8847
  intendedRootDir: process.cwd()
8152
8848
  });
8153
- const filename = basename19(relativePathFromCwd);
8849
+ const filename = basename21(relativePathFromCwd);
8154
8850
  try {
8155
8851
  const rule = await RulesyncRule.fromFile({
8156
8852
  relativeFilePath: filename,
8157
8853
  validate: true
8158
8854
  });
8159
8855
  return {
8160
- relativePathFromCwd: join70(RULESYNC_RULES_RELATIVE_DIR_PATH, filename),
8856
+ relativePathFromCwd: join75(RULESYNC_RULES_RELATIVE_DIR_PATH, filename),
8161
8857
  frontmatter: rule.getFrontmatter(),
8162
8858
  body: rule.getBody()
8163
8859
  };
@@ -8176,7 +8872,7 @@ async function putRule({
8176
8872
  relativePath: relativePathFromCwd,
8177
8873
  intendedRootDir: process.cwd()
8178
8874
  });
8179
- const filename = basename19(relativePathFromCwd);
8875
+ const filename = basename21(relativePathFromCwd);
8180
8876
  const estimatedSize = JSON.stringify(frontmatter).length + body.length;
8181
8877
  if (estimatedSize > maxRuleSizeBytes) {
8182
8878
  throw new Error(
@@ -8186,7 +8882,7 @@ async function putRule({
8186
8882
  try {
8187
8883
  const existingRules = await listRules();
8188
8884
  const isUpdate = existingRules.some(
8189
- (rule2) => rule2.relativePathFromCwd === join70(RULESYNC_RULES_RELATIVE_DIR_PATH, filename)
8885
+ (rule2) => rule2.relativePathFromCwd === join75(RULESYNC_RULES_RELATIVE_DIR_PATH, filename)
8190
8886
  );
8191
8887
  if (!isUpdate && existingRules.length >= maxRulesCount) {
8192
8888
  throw new Error(`Maximum number of rules (${maxRulesCount}) reached`);
@@ -8199,11 +8895,11 @@ async function putRule({
8199
8895
  body,
8200
8896
  validate: true
8201
8897
  });
8202
- const rulesDir = join70(process.cwd(), RULESYNC_RULES_RELATIVE_DIR_PATH);
8898
+ const rulesDir = join75(process.cwd(), RULESYNC_RULES_RELATIVE_DIR_PATH);
8203
8899
  await ensureDir(rulesDir);
8204
8900
  await writeFileContent(rule.getFilePath(), rule.getFileContent());
8205
8901
  return {
8206
- relativePathFromCwd: join70(RULESYNC_RULES_RELATIVE_DIR_PATH, filename),
8902
+ relativePathFromCwd: join75(RULESYNC_RULES_RELATIVE_DIR_PATH, filename),
8207
8903
  frontmatter: rule.getFrontmatter(),
8208
8904
  body: rule.getBody()
8209
8905
  };
@@ -8218,12 +8914,12 @@ async function deleteRule({ relativePathFromCwd }) {
8218
8914
  relativePath: relativePathFromCwd,
8219
8915
  intendedRootDir: process.cwd()
8220
8916
  });
8221
- const filename = basename19(relativePathFromCwd);
8222
- const fullPath = join70(process.cwd(), RULESYNC_RULES_RELATIVE_DIR_PATH, filename);
8917
+ const filename = basename21(relativePathFromCwd);
8918
+ const fullPath = join75(process.cwd(), RULESYNC_RULES_RELATIVE_DIR_PATH, filename);
8223
8919
  try {
8224
8920
  await removeFile(fullPath);
8225
8921
  return {
8226
- relativePathFromCwd: join70(RULESYNC_RULES_RELATIVE_DIR_PATH, filename)
8922
+ relativePathFromCwd: join75(RULESYNC_RULES_RELATIVE_DIR_PATH, filename)
8227
8923
  };
8228
8924
  } catch (error) {
8229
8925
  throw new Error(`Failed to delete rule file ${relativePathFromCwd}: ${formatError(error)}`, {
@@ -8232,23 +8928,23 @@ async function deleteRule({ relativePathFromCwd }) {
8232
8928
  }
8233
8929
  }
8234
8930
  var ruleToolSchemas = {
8235
- listRules: z28.object({}),
8236
- getRule: z28.object({
8237
- relativePathFromCwd: z28.string()
8931
+ listRules: z31.object({}),
8932
+ getRule: z31.object({
8933
+ relativePathFromCwd: z31.string()
8238
8934
  }),
8239
- putRule: z28.object({
8240
- relativePathFromCwd: z28.string(),
8935
+ putRule: z31.object({
8936
+ relativePathFromCwd: z31.string(),
8241
8937
  frontmatter: RulesyncRuleFrontmatterSchema,
8242
- body: z28.string()
8938
+ body: z31.string()
8243
8939
  }),
8244
- deleteRule: z28.object({
8245
- relativePathFromCwd: z28.string()
8940
+ deleteRule: z31.object({
8941
+ relativePathFromCwd: z31.string()
8246
8942
  })
8247
8943
  };
8248
8944
  var ruleTools = {
8249
8945
  listRules: {
8250
8946
  name: "listRules",
8251
- description: `List all rules from ${join70(RULESYNC_RULES_RELATIVE_DIR_PATH, "*.md")} with their frontmatter.`,
8947
+ description: `List all rules from ${join75(RULESYNC_RULES_RELATIVE_DIR_PATH, "*.md")} with their frontmatter.`,
8252
8948
  parameters: ruleToolSchemas.listRules,
8253
8949
  execute: async () => {
8254
8950
  const rules = await listRules();
@@ -8290,12 +8986,12 @@ var ruleTools = {
8290
8986
  };
8291
8987
 
8292
8988
  // src/mcp/subagents.ts
8293
- import { basename as basename20, join as join71 } from "path";
8294
- import { z as z29 } from "zod/mini";
8989
+ import { basename as basename22, join as join76 } from "path";
8990
+ import { z as z32 } from "zod/mini";
8295
8991
  var maxSubagentSizeBytes = 1024 * 1024;
8296
8992
  var maxSubagentsCount = 1e3;
8297
8993
  async function listSubagents() {
8298
- const subagentsDir = join71(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
8994
+ const subagentsDir = join76(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
8299
8995
  try {
8300
8996
  const files = await listDirectoryFiles(subagentsDir);
8301
8997
  const mdFiles = files.filter((file) => file.endsWith(".md"));
@@ -8308,7 +9004,7 @@ async function listSubagents() {
8308
9004
  });
8309
9005
  const frontmatter = subagent.getFrontmatter();
8310
9006
  return {
8311
- relativePathFromCwd: join71(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, file),
9007
+ relativePathFromCwd: join76(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, file),
8312
9008
  frontmatter
8313
9009
  };
8314
9010
  } catch (error) {
@@ -8330,14 +9026,14 @@ async function getSubagent({ relativePathFromCwd }) {
8330
9026
  relativePath: relativePathFromCwd,
8331
9027
  intendedRootDir: process.cwd()
8332
9028
  });
8333
- const filename = basename20(relativePathFromCwd);
9029
+ const filename = basename22(relativePathFromCwd);
8334
9030
  try {
8335
9031
  const subagent = await RulesyncSubagent.fromFile({
8336
9032
  relativeFilePath: filename,
8337
9033
  validate: true
8338
9034
  });
8339
9035
  return {
8340
- relativePathFromCwd: join71(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
9036
+ relativePathFromCwd: join76(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
8341
9037
  frontmatter: subagent.getFrontmatter(),
8342
9038
  body: subagent.getBody()
8343
9039
  };
@@ -8356,7 +9052,7 @@ async function putSubagent({
8356
9052
  relativePath: relativePathFromCwd,
8357
9053
  intendedRootDir: process.cwd()
8358
9054
  });
8359
- const filename = basename20(relativePathFromCwd);
9055
+ const filename = basename22(relativePathFromCwd);
8360
9056
  const estimatedSize = JSON.stringify(frontmatter).length + body.length;
8361
9057
  if (estimatedSize > maxSubagentSizeBytes) {
8362
9058
  throw new Error(
@@ -8366,7 +9062,7 @@ async function putSubagent({
8366
9062
  try {
8367
9063
  const existingSubagents = await listSubagents();
8368
9064
  const isUpdate = existingSubagents.some(
8369
- (subagent2) => subagent2.relativePathFromCwd === join71(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
9065
+ (subagent2) => subagent2.relativePathFromCwd === join76(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
8370
9066
  );
8371
9067
  if (!isUpdate && existingSubagents.length >= maxSubagentsCount) {
8372
9068
  throw new Error(`Maximum number of subagents (${maxSubagentsCount}) reached`);
@@ -8379,11 +9075,11 @@ async function putSubagent({
8379
9075
  body,
8380
9076
  validate: true
8381
9077
  });
8382
- const subagentsDir = join71(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
9078
+ const subagentsDir = join76(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
8383
9079
  await ensureDir(subagentsDir);
8384
9080
  await writeFileContent(subagent.getFilePath(), subagent.getFileContent());
8385
9081
  return {
8386
- relativePathFromCwd: join71(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
9082
+ relativePathFromCwd: join76(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
8387
9083
  frontmatter: subagent.getFrontmatter(),
8388
9084
  body: subagent.getBody()
8389
9085
  };
@@ -8398,12 +9094,12 @@ async function deleteSubagent({ relativePathFromCwd }) {
8398
9094
  relativePath: relativePathFromCwd,
8399
9095
  intendedRootDir: process.cwd()
8400
9096
  });
8401
- const filename = basename20(relativePathFromCwd);
8402
- const fullPath = join71(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename);
9097
+ const filename = basename22(relativePathFromCwd);
9098
+ const fullPath = join76(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename);
8403
9099
  try {
8404
9100
  await removeFile(fullPath);
8405
9101
  return {
8406
- relativePathFromCwd: join71(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
9102
+ relativePathFromCwd: join76(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
8407
9103
  };
8408
9104
  } catch (error) {
8409
9105
  throw new Error(
@@ -8415,23 +9111,23 @@ async function deleteSubagent({ relativePathFromCwd }) {
8415
9111
  }
8416
9112
  }
8417
9113
  var subagentToolSchemas = {
8418
- listSubagents: z29.object({}),
8419
- getSubagent: z29.object({
8420
- relativePathFromCwd: z29.string()
9114
+ listSubagents: z32.object({}),
9115
+ getSubagent: z32.object({
9116
+ relativePathFromCwd: z32.string()
8421
9117
  }),
8422
- putSubagent: z29.object({
8423
- relativePathFromCwd: z29.string(),
9118
+ putSubagent: z32.object({
9119
+ relativePathFromCwd: z32.string(),
8424
9120
  frontmatter: RulesyncSubagentFrontmatterSchema,
8425
- body: z29.string()
9121
+ body: z32.string()
8426
9122
  }),
8427
- deleteSubagent: z29.object({
8428
- relativePathFromCwd: z29.string()
9123
+ deleteSubagent: z32.object({
9124
+ relativePathFromCwd: z32.string()
8429
9125
  })
8430
9126
  };
8431
9127
  var subagentTools = {
8432
9128
  listSubagents: {
8433
9129
  name: "listSubagents",
8434
- description: `List all subagents from ${join71(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, "*.md")} with their frontmatter.`,
9130
+ description: `List all subagents from ${join76(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, "*.md")} with their frontmatter.`,
8435
9131
  parameters: subagentToolSchemas.listSubagents,
8436
9132
  execute: async () => {
8437
9133
  const subagents = await listSubagents();
@@ -8505,7 +9201,7 @@ async function mcpCommand({ version }) {
8505
9201
  }
8506
9202
 
8507
9203
  // src/cli/index.ts
8508
- var getVersion = () => "3.23.5";
9204
+ var getVersion = () => "3.24.0";
8509
9205
  var main = async () => {
8510
9206
  const program = new Command();
8511
9207
  const version = getVersion();