rulesync 3.23.6 → 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 +802 -112
  3. package/dist/index.js +802 -112
  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:
@@ -4547,7 +4572,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
4547
4572
  fromFile
4548
4573
  }) {
4549
4574
  const paths = await findFilesByGlobs(join44(this.baseDir, relativeDirPath, "*.md"));
4550
- 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);
4551
4576
  logger.info(`Successfully loaded ${subagents.length} ${relativeDirPath} subagents`);
4552
4577
  return subagents;
4553
4578
  }
@@ -7069,6 +7094,614 @@ For example, if the user instructs \`Call planner subagent to plan the refactori
7069
7094
  }
7070
7095
  };
7071
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
+
7072
7705
  // src/cli/commands/generate.ts
7073
7706
  async function generateCommand(options) {
7074
7707
  const config = await ConfigResolver.resolve(options);
@@ -7084,7 +7717,8 @@ async function generateCommand(options) {
7084
7717
  const totalMcpOutputs = await generateMcp(config);
7085
7718
  const totalCommandOutputs = await generateCommands(config);
7086
7719
  const totalSubagentOutputs = await generateSubagents(config);
7087
- const totalGenerated = totalRulesOutputs + totalMcpOutputs + totalCommandOutputs + totalIgnoreOutputs + totalSubagentOutputs;
7720
+ const totalSkillOutputs = await generateSkills(config);
7721
+ const totalGenerated = totalRulesOutputs + totalMcpOutputs + totalCommandOutputs + totalIgnoreOutputs + totalSubagentOutputs + totalSkillOutputs;
7088
7722
  if (totalGenerated === 0) {
7089
7723
  const enabledFeatures = config.getFeatures().join(", ");
7090
7724
  logger.warn(`\u26A0\uFE0F No files generated for enabled features: ${enabledFeatures}`);
@@ -7097,6 +7731,7 @@ async function generateCommand(options) {
7097
7731
  if (totalMcpOutputs > 0) parts.push(`${totalMcpOutputs} MCP files`);
7098
7732
  if (totalCommandOutputs > 0) parts.push(`${totalCommandOutputs} commands`);
7099
7733
  if (totalSubagentOutputs > 0) parts.push(`${totalSubagentOutputs} subagents`);
7734
+ if (totalSkillOutputs > 0) parts.push(`${totalSkillOutputs} skills`);
7100
7735
  logger.success(`\u{1F389} All done! Generated ${totalGenerated} file(s) total (${parts.join(" + ")})`);
7101
7736
  }
7102
7737
  }
@@ -7271,11 +7906,39 @@ async function generateSubagents(config) {
7271
7906
  }
7272
7907
  return totalSubagentOutputs;
7273
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
+ }
7274
7937
 
7275
7938
  // src/cli/commands/gitignore.ts
7276
- import { join as join65 } from "path";
7939
+ import { join as join70 } from "path";
7277
7940
  var gitignoreCommand = async () => {
7278
- const gitignorePath = join65(process.cwd(), ".gitignore");
7941
+ const gitignorePath = join70(process.cwd(), ".gitignore");
7279
7942
  const rulesFilesToIgnore = [
7280
7943
  "# Generated by rulesync - AI tool configuration files",
7281
7944
  // AGENTS.md
@@ -7292,6 +7955,7 @@ var gitignoreCommand = async () => {
7292
7955
  "**/.claude/memories/",
7293
7956
  "**/.claude/commands/",
7294
7957
  "**/.claude/agents/",
7958
+ "**/.claude/skills/",
7295
7959
  "**/.claude/settings.local.json",
7296
7960
  "**/.mcp.json",
7297
7961
  // Cline
@@ -7385,6 +8049,7 @@ async function importCommand(options) {
7385
8049
  await importMcp(config, tool);
7386
8050
  await importCommands(config, tool);
7387
8051
  await importSubagents(config, tool);
8052
+ await importSkills(config, tool);
7388
8053
  }
7389
8054
  async function importRules(config, tool) {
7390
8055
  if (!config.getFeatures().includes("rules")) {
@@ -7514,9 +8179,34 @@ async function importSubagents(config, tool) {
7514
8179
  }
7515
8180
  return writtenCount;
7516
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
+ }
7517
8207
 
7518
8208
  // src/cli/commands/init.ts
7519
- import { join as join66 } from "path";
8209
+ import { join as join71 } from "path";
7520
8210
  async function initCommand() {
7521
8211
  logger.info("Initializing rulesync...");
7522
8212
  await ensureDir(RULESYNC_RELATIVE_DIR_PATH);
@@ -7679,14 +8369,14 @@ Attention, again, you are just the planner, so though you can read any files and
7679
8369
  await ensureDir(commandPaths.relativeDirPath);
7680
8370
  await ensureDir(subagentPaths.relativeDirPath);
7681
8371
  await ensureDir(ignorePaths.relativeDirPath);
7682
- const ruleFilepath = join66(rulePaths.recommended.relativeDirPath, sampleRuleFile.filename);
8372
+ const ruleFilepath = join71(rulePaths.recommended.relativeDirPath, sampleRuleFile.filename);
7683
8373
  if (!await fileExists(ruleFilepath)) {
7684
8374
  await writeFileContent(ruleFilepath, sampleRuleFile.content);
7685
8375
  logger.success(`Created ${ruleFilepath}`);
7686
8376
  } else {
7687
8377
  logger.info(`Skipped ${ruleFilepath} (already exists)`);
7688
8378
  }
7689
- const mcpFilepath = join66(
8379
+ const mcpFilepath = join71(
7690
8380
  mcpPaths.recommended.relativeDirPath,
7691
8381
  mcpPaths.recommended.relativeFilePath
7692
8382
  );
@@ -7696,21 +8386,21 @@ Attention, again, you are just the planner, so though you can read any files and
7696
8386
  } else {
7697
8387
  logger.info(`Skipped ${mcpFilepath} (already exists)`);
7698
8388
  }
7699
- const commandFilepath = join66(commandPaths.relativeDirPath, sampleCommandFile.filename);
8389
+ const commandFilepath = join71(commandPaths.relativeDirPath, sampleCommandFile.filename);
7700
8390
  if (!await fileExists(commandFilepath)) {
7701
8391
  await writeFileContent(commandFilepath, sampleCommandFile.content);
7702
8392
  logger.success(`Created ${commandFilepath}`);
7703
8393
  } else {
7704
8394
  logger.info(`Skipped ${commandFilepath} (already exists)`);
7705
8395
  }
7706
- const subagentFilepath = join66(subagentPaths.relativeDirPath, sampleSubagentFile.filename);
8396
+ const subagentFilepath = join71(subagentPaths.relativeDirPath, sampleSubagentFile.filename);
7707
8397
  if (!await fileExists(subagentFilepath)) {
7708
8398
  await writeFileContent(subagentFilepath, sampleSubagentFile.content);
7709
8399
  logger.success(`Created ${subagentFilepath}`);
7710
8400
  } else {
7711
8401
  logger.info(`Skipped ${subagentFilepath} (already exists)`);
7712
8402
  }
7713
- const ignoreFilepath = join66(ignorePaths.relativeDirPath, ignorePaths.relativeFilePath);
8403
+ const ignoreFilepath = join71(ignorePaths.relativeDirPath, ignorePaths.relativeFilePath);
7714
8404
  if (!await fileExists(ignoreFilepath)) {
7715
8405
  await writeFileContent(ignoreFilepath, sampleIgnoreFile.content);
7716
8406
  logger.success(`Created ${ignoreFilepath}`);
@@ -7723,12 +8413,12 @@ Attention, again, you are just the planner, so though you can read any files and
7723
8413
  import { FastMCP } from "fastmcp";
7724
8414
 
7725
8415
  // src/mcp/commands.ts
7726
- import { basename as basename18, join as join67 } from "path";
7727
- 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";
7728
8418
  var maxCommandSizeBytes = 1024 * 1024;
7729
8419
  var maxCommandsCount = 1e3;
7730
8420
  async function listCommands() {
7731
- const commandsDir = join67(process.cwd(), RULESYNC_COMMANDS_RELATIVE_DIR_PATH);
8421
+ const commandsDir = join72(process.cwd(), RULESYNC_COMMANDS_RELATIVE_DIR_PATH);
7732
8422
  try {
7733
8423
  const files = await listDirectoryFiles(commandsDir);
7734
8424
  const mdFiles = files.filter((file) => file.endsWith(".md"));
@@ -7740,7 +8430,7 @@ async function listCommands() {
7740
8430
  });
7741
8431
  const frontmatter = command.getFrontmatter();
7742
8432
  return {
7743
- relativePathFromCwd: join67(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, file),
8433
+ relativePathFromCwd: join72(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, file),
7744
8434
  frontmatter
7745
8435
  };
7746
8436
  } catch (error) {
@@ -7760,13 +8450,13 @@ async function getCommand({ relativePathFromCwd }) {
7760
8450
  relativePath: relativePathFromCwd,
7761
8451
  intendedRootDir: process.cwd()
7762
8452
  });
7763
- const filename = basename18(relativePathFromCwd);
8453
+ const filename = basename20(relativePathFromCwd);
7764
8454
  try {
7765
8455
  const command = await RulesyncCommand.fromFile({
7766
8456
  relativeFilePath: filename
7767
8457
  });
7768
8458
  return {
7769
- relativePathFromCwd: join67(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename),
8459
+ relativePathFromCwd: join72(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename),
7770
8460
  frontmatter: command.getFrontmatter(),
7771
8461
  body: command.getBody()
7772
8462
  };
@@ -7785,7 +8475,7 @@ async function putCommand({
7785
8475
  relativePath: relativePathFromCwd,
7786
8476
  intendedRootDir: process.cwd()
7787
8477
  });
7788
- const filename = basename18(relativePathFromCwd);
8478
+ const filename = basename20(relativePathFromCwd);
7789
8479
  const estimatedSize = JSON.stringify(frontmatter).length + body.length;
7790
8480
  if (estimatedSize > maxCommandSizeBytes) {
7791
8481
  throw new Error(
@@ -7795,7 +8485,7 @@ async function putCommand({
7795
8485
  try {
7796
8486
  const existingCommands = await listCommands();
7797
8487
  const isUpdate = existingCommands.some(
7798
- (command2) => command2.relativePathFromCwd === join67(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename)
8488
+ (command2) => command2.relativePathFromCwd === join72(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename)
7799
8489
  );
7800
8490
  if (!isUpdate && existingCommands.length >= maxCommandsCount) {
7801
8491
  throw new Error(`Maximum number of commands (${maxCommandsCount}) reached`);
@@ -7810,11 +8500,11 @@ async function putCommand({
7810
8500
  fileContent,
7811
8501
  validate: true
7812
8502
  });
7813
- const commandsDir = join67(process.cwd(), RULESYNC_COMMANDS_RELATIVE_DIR_PATH);
8503
+ const commandsDir = join72(process.cwd(), RULESYNC_COMMANDS_RELATIVE_DIR_PATH);
7814
8504
  await ensureDir(commandsDir);
7815
8505
  await writeFileContent(command.getFilePath(), command.getFileContent());
7816
8506
  return {
7817
- relativePathFromCwd: join67(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename),
8507
+ relativePathFromCwd: join72(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename),
7818
8508
  frontmatter: command.getFrontmatter(),
7819
8509
  body: command.getBody()
7820
8510
  };
@@ -7829,12 +8519,12 @@ async function deleteCommand({ relativePathFromCwd }) {
7829
8519
  relativePath: relativePathFromCwd,
7830
8520
  intendedRootDir: process.cwd()
7831
8521
  });
7832
- const filename = basename18(relativePathFromCwd);
7833
- 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);
7834
8524
  try {
7835
8525
  await removeFile(fullPath);
7836
8526
  return {
7837
- relativePathFromCwd: join67(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename)
8527
+ relativePathFromCwd: join72(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename)
7838
8528
  };
7839
8529
  } catch (error) {
7840
8530
  throw new Error(`Failed to delete command file ${relativePathFromCwd}: ${formatError(error)}`, {
@@ -7843,23 +8533,23 @@ async function deleteCommand({ relativePathFromCwd }) {
7843
8533
  }
7844
8534
  }
7845
8535
  var commandToolSchemas = {
7846
- listCommands: z25.object({}),
7847
- getCommand: z25.object({
7848
- relativePathFromCwd: z25.string()
8536
+ listCommands: z28.object({}),
8537
+ getCommand: z28.object({
8538
+ relativePathFromCwd: z28.string()
7849
8539
  }),
7850
- putCommand: z25.object({
7851
- relativePathFromCwd: z25.string(),
8540
+ putCommand: z28.object({
8541
+ relativePathFromCwd: z28.string(),
7852
8542
  frontmatter: RulesyncCommandFrontmatterSchema,
7853
- body: z25.string()
8543
+ body: z28.string()
7854
8544
  }),
7855
- deleteCommand: z25.object({
7856
- relativePathFromCwd: z25.string()
8545
+ deleteCommand: z28.object({
8546
+ relativePathFromCwd: z28.string()
7857
8547
  })
7858
8548
  };
7859
8549
  var commandTools = {
7860
8550
  listCommands: {
7861
8551
  name: "listCommands",
7862
- 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.`,
7863
8553
  parameters: commandToolSchemas.listCommands,
7864
8554
  execute: async () => {
7865
8555
  const commands = await listCommands();
@@ -7901,11 +8591,11 @@ var commandTools = {
7901
8591
  };
7902
8592
 
7903
8593
  // src/mcp/ignore.ts
7904
- import { join as join68 } from "path";
7905
- import { z as z26 } from "zod/mini";
8594
+ import { join as join73 } from "path";
8595
+ import { z as z29 } from "zod/mini";
7906
8596
  var maxIgnoreFileSizeBytes = 100 * 1024;
7907
8597
  async function getIgnoreFile() {
7908
- const ignoreFilePath = join68(process.cwd(), RULESYNC_IGNORE_RELATIVE_FILE_PATH);
8598
+ const ignoreFilePath = join73(process.cwd(), RULESYNC_IGNORE_RELATIVE_FILE_PATH);
7909
8599
  try {
7910
8600
  const content = await readFileContent(ignoreFilePath);
7911
8601
  return {
@@ -7919,7 +8609,7 @@ async function getIgnoreFile() {
7919
8609
  }
7920
8610
  }
7921
8611
  async function putIgnoreFile({ content }) {
7922
- const ignoreFilePath = join68(process.cwd(), RULESYNC_IGNORE_RELATIVE_FILE_PATH);
8612
+ const ignoreFilePath = join73(process.cwd(), RULESYNC_IGNORE_RELATIVE_FILE_PATH);
7923
8613
  const contentSizeBytes = Buffer.byteLength(content, "utf8");
7924
8614
  if (contentSizeBytes > maxIgnoreFileSizeBytes) {
7925
8615
  throw new Error(
@@ -7940,7 +8630,7 @@ async function putIgnoreFile({ content }) {
7940
8630
  }
7941
8631
  }
7942
8632
  async function deleteIgnoreFile() {
7943
- const ignoreFilePath = join68(process.cwd(), RULESYNC_IGNORE_RELATIVE_FILE_PATH);
8633
+ const ignoreFilePath = join73(process.cwd(), RULESYNC_IGNORE_RELATIVE_FILE_PATH);
7944
8634
  try {
7945
8635
  await removeFile(ignoreFilePath);
7946
8636
  return {
@@ -7953,11 +8643,11 @@ async function deleteIgnoreFile() {
7953
8643
  }
7954
8644
  }
7955
8645
  var ignoreToolSchemas = {
7956
- getIgnoreFile: z26.object({}),
7957
- putIgnoreFile: z26.object({
7958
- content: z26.string()
8646
+ getIgnoreFile: z29.object({}),
8647
+ putIgnoreFile: z29.object({
8648
+ content: z29.string()
7959
8649
  }),
7960
- deleteIgnoreFile: z26.object({})
8650
+ deleteIgnoreFile: z29.object({})
7961
8651
  };
7962
8652
  var ignoreTools = {
7963
8653
  getIgnoreFile: {
@@ -7990,8 +8680,8 @@ var ignoreTools = {
7990
8680
  };
7991
8681
 
7992
8682
  // src/mcp/mcp.ts
7993
- import { join as join69 } from "path";
7994
- import { z as z27 } from "zod/mini";
8683
+ import { join as join74 } from "path";
8684
+ import { z as z30 } from "zod/mini";
7995
8685
  var maxMcpSizeBytes = 1024 * 1024;
7996
8686
  async function getMcpFile() {
7997
8687
  const config = await ConfigResolver.resolve({});
@@ -8000,7 +8690,7 @@ async function getMcpFile() {
8000
8690
  validate: true,
8001
8691
  modularMcp: config.getModularMcp()
8002
8692
  });
8003
- const relativePathFromCwd = join69(
8693
+ const relativePathFromCwd = join74(
8004
8694
  rulesyncMcp.getRelativeDirPath(),
8005
8695
  rulesyncMcp.getRelativeFilePath()
8006
8696
  );
@@ -8033,7 +8723,7 @@ async function putMcpFile({ content }) {
8033
8723
  const paths = RulesyncMcp.getSettablePaths();
8034
8724
  const relativeDirPath = paths.recommended.relativeDirPath;
8035
8725
  const relativeFilePath = paths.recommended.relativeFilePath;
8036
- const fullPath = join69(baseDir, relativeDirPath, relativeFilePath);
8726
+ const fullPath = join74(baseDir, relativeDirPath, relativeFilePath);
8037
8727
  const rulesyncMcp = new RulesyncMcp({
8038
8728
  baseDir,
8039
8729
  relativeDirPath,
@@ -8042,9 +8732,9 @@ async function putMcpFile({ content }) {
8042
8732
  validate: true,
8043
8733
  modularMcp: config.getModularMcp()
8044
8734
  });
8045
- await ensureDir(join69(baseDir, relativeDirPath));
8735
+ await ensureDir(join74(baseDir, relativeDirPath));
8046
8736
  await writeFileContent(fullPath, content);
8047
- const relativePathFromCwd = join69(relativeDirPath, relativeFilePath);
8737
+ const relativePathFromCwd = join74(relativeDirPath, relativeFilePath);
8048
8738
  return {
8049
8739
  relativePathFromCwd,
8050
8740
  content: rulesyncMcp.getFileContent()
@@ -8059,15 +8749,15 @@ async function deleteMcpFile() {
8059
8749
  try {
8060
8750
  const baseDir = process.cwd();
8061
8751
  const paths = RulesyncMcp.getSettablePaths();
8062
- const recommendedPath = join69(
8752
+ const recommendedPath = join74(
8063
8753
  baseDir,
8064
8754
  paths.recommended.relativeDirPath,
8065
8755
  paths.recommended.relativeFilePath
8066
8756
  );
8067
- const legacyPath = join69(baseDir, paths.legacy.relativeDirPath, paths.legacy.relativeFilePath);
8757
+ const legacyPath = join74(baseDir, paths.legacy.relativeDirPath, paths.legacy.relativeFilePath);
8068
8758
  await removeFile(recommendedPath);
8069
8759
  await removeFile(legacyPath);
8070
- const relativePathFromCwd = join69(
8760
+ const relativePathFromCwd = join74(
8071
8761
  paths.recommended.relativeDirPath,
8072
8762
  paths.recommended.relativeFilePath
8073
8763
  );
@@ -8081,11 +8771,11 @@ async function deleteMcpFile() {
8081
8771
  }
8082
8772
  }
8083
8773
  var mcpToolSchemas = {
8084
- getMcpFile: z27.object({}),
8085
- putMcpFile: z27.object({
8086
- content: z27.string()
8774
+ getMcpFile: z30.object({}),
8775
+ putMcpFile: z30.object({
8776
+ content: z30.string()
8087
8777
  }),
8088
- deleteMcpFile: z27.object({})
8778
+ deleteMcpFile: z30.object({})
8089
8779
  };
8090
8780
  var mcpTools = {
8091
8781
  getMcpFile: {
@@ -8118,12 +8808,12 @@ var mcpTools = {
8118
8808
  };
8119
8809
 
8120
8810
  // src/mcp/rules.ts
8121
- import { basename as basename19, join as join70 } from "path";
8122
- 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";
8123
8813
  var maxRuleSizeBytes = 1024 * 1024;
8124
8814
  var maxRulesCount = 1e3;
8125
8815
  async function listRules() {
8126
- const rulesDir = join70(process.cwd(), RULESYNC_RULES_RELATIVE_DIR_PATH);
8816
+ const rulesDir = join75(process.cwd(), RULESYNC_RULES_RELATIVE_DIR_PATH);
8127
8817
  try {
8128
8818
  const files = await listDirectoryFiles(rulesDir);
8129
8819
  const mdFiles = files.filter((file) => file.endsWith(".md"));
@@ -8136,7 +8826,7 @@ async function listRules() {
8136
8826
  });
8137
8827
  const frontmatter = rule.getFrontmatter();
8138
8828
  return {
8139
- relativePathFromCwd: join70(RULESYNC_RULES_RELATIVE_DIR_PATH, file),
8829
+ relativePathFromCwd: join75(RULESYNC_RULES_RELATIVE_DIR_PATH, file),
8140
8830
  frontmatter
8141
8831
  };
8142
8832
  } catch (error) {
@@ -8156,14 +8846,14 @@ async function getRule({ relativePathFromCwd }) {
8156
8846
  relativePath: relativePathFromCwd,
8157
8847
  intendedRootDir: process.cwd()
8158
8848
  });
8159
- const filename = basename19(relativePathFromCwd);
8849
+ const filename = basename21(relativePathFromCwd);
8160
8850
  try {
8161
8851
  const rule = await RulesyncRule.fromFile({
8162
8852
  relativeFilePath: filename,
8163
8853
  validate: true
8164
8854
  });
8165
8855
  return {
8166
- relativePathFromCwd: join70(RULESYNC_RULES_RELATIVE_DIR_PATH, filename),
8856
+ relativePathFromCwd: join75(RULESYNC_RULES_RELATIVE_DIR_PATH, filename),
8167
8857
  frontmatter: rule.getFrontmatter(),
8168
8858
  body: rule.getBody()
8169
8859
  };
@@ -8182,7 +8872,7 @@ async function putRule({
8182
8872
  relativePath: relativePathFromCwd,
8183
8873
  intendedRootDir: process.cwd()
8184
8874
  });
8185
- const filename = basename19(relativePathFromCwd);
8875
+ const filename = basename21(relativePathFromCwd);
8186
8876
  const estimatedSize = JSON.stringify(frontmatter).length + body.length;
8187
8877
  if (estimatedSize > maxRuleSizeBytes) {
8188
8878
  throw new Error(
@@ -8192,7 +8882,7 @@ async function putRule({
8192
8882
  try {
8193
8883
  const existingRules = await listRules();
8194
8884
  const isUpdate = existingRules.some(
8195
- (rule2) => rule2.relativePathFromCwd === join70(RULESYNC_RULES_RELATIVE_DIR_PATH, filename)
8885
+ (rule2) => rule2.relativePathFromCwd === join75(RULESYNC_RULES_RELATIVE_DIR_PATH, filename)
8196
8886
  );
8197
8887
  if (!isUpdate && existingRules.length >= maxRulesCount) {
8198
8888
  throw new Error(`Maximum number of rules (${maxRulesCount}) reached`);
@@ -8205,11 +8895,11 @@ async function putRule({
8205
8895
  body,
8206
8896
  validate: true
8207
8897
  });
8208
- const rulesDir = join70(process.cwd(), RULESYNC_RULES_RELATIVE_DIR_PATH);
8898
+ const rulesDir = join75(process.cwd(), RULESYNC_RULES_RELATIVE_DIR_PATH);
8209
8899
  await ensureDir(rulesDir);
8210
8900
  await writeFileContent(rule.getFilePath(), rule.getFileContent());
8211
8901
  return {
8212
- relativePathFromCwd: join70(RULESYNC_RULES_RELATIVE_DIR_PATH, filename),
8902
+ relativePathFromCwd: join75(RULESYNC_RULES_RELATIVE_DIR_PATH, filename),
8213
8903
  frontmatter: rule.getFrontmatter(),
8214
8904
  body: rule.getBody()
8215
8905
  };
@@ -8224,12 +8914,12 @@ async function deleteRule({ relativePathFromCwd }) {
8224
8914
  relativePath: relativePathFromCwd,
8225
8915
  intendedRootDir: process.cwd()
8226
8916
  });
8227
- const filename = basename19(relativePathFromCwd);
8228
- 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);
8229
8919
  try {
8230
8920
  await removeFile(fullPath);
8231
8921
  return {
8232
- relativePathFromCwd: join70(RULESYNC_RULES_RELATIVE_DIR_PATH, filename)
8922
+ relativePathFromCwd: join75(RULESYNC_RULES_RELATIVE_DIR_PATH, filename)
8233
8923
  };
8234
8924
  } catch (error) {
8235
8925
  throw new Error(`Failed to delete rule file ${relativePathFromCwd}: ${formatError(error)}`, {
@@ -8238,23 +8928,23 @@ async function deleteRule({ relativePathFromCwd }) {
8238
8928
  }
8239
8929
  }
8240
8930
  var ruleToolSchemas = {
8241
- listRules: z28.object({}),
8242
- getRule: z28.object({
8243
- relativePathFromCwd: z28.string()
8931
+ listRules: z31.object({}),
8932
+ getRule: z31.object({
8933
+ relativePathFromCwd: z31.string()
8244
8934
  }),
8245
- putRule: z28.object({
8246
- relativePathFromCwd: z28.string(),
8935
+ putRule: z31.object({
8936
+ relativePathFromCwd: z31.string(),
8247
8937
  frontmatter: RulesyncRuleFrontmatterSchema,
8248
- body: z28.string()
8938
+ body: z31.string()
8249
8939
  }),
8250
- deleteRule: z28.object({
8251
- relativePathFromCwd: z28.string()
8940
+ deleteRule: z31.object({
8941
+ relativePathFromCwd: z31.string()
8252
8942
  })
8253
8943
  };
8254
8944
  var ruleTools = {
8255
8945
  listRules: {
8256
8946
  name: "listRules",
8257
- 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.`,
8258
8948
  parameters: ruleToolSchemas.listRules,
8259
8949
  execute: async () => {
8260
8950
  const rules = await listRules();
@@ -8296,12 +8986,12 @@ var ruleTools = {
8296
8986
  };
8297
8987
 
8298
8988
  // src/mcp/subagents.ts
8299
- import { basename as basename20, join as join71 } from "path";
8300
- 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";
8301
8991
  var maxSubagentSizeBytes = 1024 * 1024;
8302
8992
  var maxSubagentsCount = 1e3;
8303
8993
  async function listSubagents() {
8304
- const subagentsDir = join71(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
8994
+ const subagentsDir = join76(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
8305
8995
  try {
8306
8996
  const files = await listDirectoryFiles(subagentsDir);
8307
8997
  const mdFiles = files.filter((file) => file.endsWith(".md"));
@@ -8314,7 +9004,7 @@ async function listSubagents() {
8314
9004
  });
8315
9005
  const frontmatter = subagent.getFrontmatter();
8316
9006
  return {
8317
- relativePathFromCwd: join71(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, file),
9007
+ relativePathFromCwd: join76(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, file),
8318
9008
  frontmatter
8319
9009
  };
8320
9010
  } catch (error) {
@@ -8336,14 +9026,14 @@ async function getSubagent({ relativePathFromCwd }) {
8336
9026
  relativePath: relativePathFromCwd,
8337
9027
  intendedRootDir: process.cwd()
8338
9028
  });
8339
- const filename = basename20(relativePathFromCwd);
9029
+ const filename = basename22(relativePathFromCwd);
8340
9030
  try {
8341
9031
  const subagent = await RulesyncSubagent.fromFile({
8342
9032
  relativeFilePath: filename,
8343
9033
  validate: true
8344
9034
  });
8345
9035
  return {
8346
- relativePathFromCwd: join71(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
9036
+ relativePathFromCwd: join76(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
8347
9037
  frontmatter: subagent.getFrontmatter(),
8348
9038
  body: subagent.getBody()
8349
9039
  };
@@ -8362,7 +9052,7 @@ async function putSubagent({
8362
9052
  relativePath: relativePathFromCwd,
8363
9053
  intendedRootDir: process.cwd()
8364
9054
  });
8365
- const filename = basename20(relativePathFromCwd);
9055
+ const filename = basename22(relativePathFromCwd);
8366
9056
  const estimatedSize = JSON.stringify(frontmatter).length + body.length;
8367
9057
  if (estimatedSize > maxSubagentSizeBytes) {
8368
9058
  throw new Error(
@@ -8372,7 +9062,7 @@ async function putSubagent({
8372
9062
  try {
8373
9063
  const existingSubagents = await listSubagents();
8374
9064
  const isUpdate = existingSubagents.some(
8375
- (subagent2) => subagent2.relativePathFromCwd === join71(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
9065
+ (subagent2) => subagent2.relativePathFromCwd === join76(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
8376
9066
  );
8377
9067
  if (!isUpdate && existingSubagents.length >= maxSubagentsCount) {
8378
9068
  throw new Error(`Maximum number of subagents (${maxSubagentsCount}) reached`);
@@ -8385,11 +9075,11 @@ async function putSubagent({
8385
9075
  body,
8386
9076
  validate: true
8387
9077
  });
8388
- const subagentsDir = join71(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
9078
+ const subagentsDir = join76(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
8389
9079
  await ensureDir(subagentsDir);
8390
9080
  await writeFileContent(subagent.getFilePath(), subagent.getFileContent());
8391
9081
  return {
8392
- relativePathFromCwd: join71(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
9082
+ relativePathFromCwd: join76(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
8393
9083
  frontmatter: subagent.getFrontmatter(),
8394
9084
  body: subagent.getBody()
8395
9085
  };
@@ -8404,12 +9094,12 @@ async function deleteSubagent({ relativePathFromCwd }) {
8404
9094
  relativePath: relativePathFromCwd,
8405
9095
  intendedRootDir: process.cwd()
8406
9096
  });
8407
- const filename = basename20(relativePathFromCwd);
8408
- 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);
8409
9099
  try {
8410
9100
  await removeFile(fullPath);
8411
9101
  return {
8412
- relativePathFromCwd: join71(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
9102
+ relativePathFromCwd: join76(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
8413
9103
  };
8414
9104
  } catch (error) {
8415
9105
  throw new Error(
@@ -8421,23 +9111,23 @@ async function deleteSubagent({ relativePathFromCwd }) {
8421
9111
  }
8422
9112
  }
8423
9113
  var subagentToolSchemas = {
8424
- listSubagents: z29.object({}),
8425
- getSubagent: z29.object({
8426
- relativePathFromCwd: z29.string()
9114
+ listSubagents: z32.object({}),
9115
+ getSubagent: z32.object({
9116
+ relativePathFromCwd: z32.string()
8427
9117
  }),
8428
- putSubagent: z29.object({
8429
- relativePathFromCwd: z29.string(),
9118
+ putSubagent: z32.object({
9119
+ relativePathFromCwd: z32.string(),
8430
9120
  frontmatter: RulesyncSubagentFrontmatterSchema,
8431
- body: z29.string()
9121
+ body: z32.string()
8432
9122
  }),
8433
- deleteSubagent: z29.object({
8434
- relativePathFromCwd: z29.string()
9123
+ deleteSubagent: z32.object({
9124
+ relativePathFromCwd: z32.string()
8435
9125
  })
8436
9126
  };
8437
9127
  var subagentTools = {
8438
9128
  listSubagents: {
8439
9129
  name: "listSubagents",
8440
- 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.`,
8441
9131
  parameters: subagentToolSchemas.listSubagents,
8442
9132
  execute: async () => {
8443
9133
  const subagents = await listSubagents();
@@ -8511,7 +9201,7 @@ async function mcpCommand({ version }) {
8511
9201
  }
8512
9202
 
8513
9203
  // src/cli/index.ts
8514
- var getVersion = () => "3.23.6";
9204
+ var getVersion = () => "3.24.0";
8515
9205
  var main = async () => {
8516
9206
  const program = new Command();
8517
9207
  const version = getVersion();