rulesync 0.56.0 → 0.58.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 (5) hide show
  1. package/README.ja.md +173 -20
  2. package/README.md +67 -699
  3. package/dist/index.cjs +464 -213
  4. package/dist/index.js +464 -213
  5. package/package.json +19 -19
package/dist/index.js CHANGED
@@ -129,128 +129,137 @@ var ClaudeSettingsSchema = z.looseObject({
129
129
  )
130
130
  });
131
131
 
132
- // src/types/config.ts
132
+ // src/types/commands.ts
133
133
  import { z as z2 } from "zod/mini";
134
- var ConfigSchema = z2.object({
135
- aiRulesDir: z2.string(),
136
- outputPaths: z2.record(ToolTargetSchema, z2.string()),
137
- watchEnabled: z2.boolean(),
138
- defaultTargets: ToolTargetsSchema
134
+ var CommandFrontmatterSchema = z2.object({
135
+ description: z2.optional(z2.string())
139
136
  });
140
137
 
141
- // src/types/config-options.ts
138
+ // src/types/config.ts
142
139
  import { z as z3 } from "zod/mini";
143
- var OutputPathsSchema = z3.object({
144
- augmentcode: z3.optional(z3.string()),
145
- "augmentcode-legacy": z3.optional(z3.string()),
146
- copilot: z3.optional(z3.string()),
147
- cursor: z3.optional(z3.string()),
148
- cline: z3.optional(z3.string()),
149
- claudecode: z3.optional(z3.string()),
150
- codexcli: z3.optional(z3.string()),
151
- roo: z3.optional(z3.string()),
152
- geminicli: z3.optional(z3.string()),
153
- kiro: z3.optional(z3.string()),
154
- junie: z3.optional(z3.string()),
155
- windsurf: z3.optional(z3.string())
140
+ var ConfigSchema = z3.object({
141
+ aiRulesDir: z3.string(),
142
+ outputPaths: z3.record(ToolTargetSchema, z3.string()),
143
+ watchEnabled: z3.boolean(),
144
+ defaultTargets: ToolTargetsSchema,
145
+ claudecodeCommands: z3.optional(z3.string()),
146
+ geminicliCommands: z3.optional(z3.string())
147
+ });
148
+
149
+ // src/types/config-options.ts
150
+ import { z as z4 } from "zod/mini";
151
+ var OutputPathsSchema = z4.object({
152
+ augmentcode: z4.optional(z4.string()),
153
+ "augmentcode-legacy": z4.optional(z4.string()),
154
+ copilot: z4.optional(z4.string()),
155
+ cursor: z4.optional(z4.string()),
156
+ cline: z4.optional(z4.string()),
157
+ claudecode: z4.optional(z4.string()),
158
+ codexcli: z4.optional(z4.string()),
159
+ roo: z4.optional(z4.string()),
160
+ geminicli: z4.optional(z4.string()),
161
+ kiro: z4.optional(z4.string()),
162
+ junie: z4.optional(z4.string()),
163
+ windsurf: z4.optional(z4.string())
156
164
  });
157
- var ConfigOptionsSchema = z3.object({
158
- aiRulesDir: z3.optional(z3.string()),
159
- outputPaths: z3.optional(OutputPathsSchema),
160
- watchEnabled: z3.optional(z3.boolean()),
161
- defaultTargets: z3.optional(ToolTargetsSchema),
162
- targets: z3.optional(z3.array(ToolTargetSchema)),
163
- exclude: z3.optional(z3.array(ToolTargetSchema)),
164
- verbose: z3.optional(z3.boolean()),
165
- delete: z3.optional(z3.boolean()),
166
- baseDir: z3.optional(z3.union([z3.string(), z3.array(z3.string())])),
167
- watch: z3.optional(
168
- z3.object({
169
- enabled: z3.optional(z3.boolean()),
170
- interval: z3.optional(z3.number()),
171
- ignore: z3.optional(z3.array(z3.string()))
165
+ var ConfigOptionsSchema = z4.object({
166
+ aiRulesDir: z4.optional(z4.string()),
167
+ outputPaths: z4.optional(OutputPathsSchema),
168
+ watchEnabled: z4.optional(z4.boolean()),
169
+ defaultTargets: z4.optional(ToolTargetsSchema),
170
+ targets: z4.optional(z4.array(ToolTargetSchema)),
171
+ exclude: z4.optional(z4.array(ToolTargetSchema)),
172
+ verbose: z4.optional(z4.boolean()),
173
+ delete: z4.optional(z4.boolean()),
174
+ baseDir: z4.optional(z4.union([z4.string(), z4.array(z4.string())])),
175
+ watch: z4.optional(
176
+ z4.object({
177
+ enabled: z4.optional(z4.boolean()),
178
+ interval: z4.optional(z4.number()),
179
+ ignore: z4.optional(z4.array(z4.string()))
172
180
  })
173
181
  )
174
182
  });
175
- var MergedConfigSchema = z3.object({
176
- aiRulesDir: z3.string(),
177
- outputPaths: z3.record(ToolTargetSchema, z3.string()),
178
- watchEnabled: z3.boolean(),
183
+ var MergedConfigSchema = z4.object({
184
+ aiRulesDir: z4.string(),
185
+ outputPaths: z4.record(ToolTargetSchema, z4.string()),
186
+ watchEnabled: z4.boolean(),
179
187
  defaultTargets: ToolTargetsSchema,
180
- targets: z3.optional(z3.array(ToolTargetSchema)),
181
- exclude: z3.optional(z3.array(ToolTargetSchema)),
182
- verbose: z3.optional(z3.boolean()),
183
- delete: z3.optional(z3.boolean()),
184
- baseDir: z3.optional(z3.union([z3.string(), z3.array(z3.string())])),
185
- configPath: z3.optional(z3.string()),
186
- watch: z3.optional(
187
- z3.object({
188
- enabled: z3.optional(z3.boolean()),
189
- interval: z3.optional(z3.number()),
190
- ignore: z3.optional(z3.array(z3.string()))
188
+ targets: z4.optional(z4.array(ToolTargetSchema)),
189
+ exclude: z4.optional(z4.array(ToolTargetSchema)),
190
+ verbose: z4.optional(z4.boolean()),
191
+ delete: z4.optional(z4.boolean()),
192
+ baseDir: z4.optional(z4.union([z4.string(), z4.array(z4.string())])),
193
+ configPath: z4.optional(z4.string()),
194
+ watch: z4.optional(
195
+ z4.object({
196
+ enabled: z4.optional(z4.boolean()),
197
+ interval: z4.optional(z4.number()),
198
+ ignore: z4.optional(z4.array(z4.string()))
191
199
  })
192
200
  )
193
201
  });
194
202
 
195
203
  // src/types/mcp.ts
196
- import { z as z4 } from "zod/mini";
197
- var McpTransportTypeSchema = z4.enum(["stdio", "sse", "http"]);
198
- var McpServerBaseSchema = z4.object({
199
- command: z4.optional(z4.string()),
200
- args: z4.optional(z4.array(z4.string())),
201
- url: z4.optional(z4.string()),
202
- httpUrl: z4.optional(z4.string()),
203
- env: z4.optional(z4.record(z4.string(), z4.string())),
204
- disabled: z4.optional(z4.boolean()),
205
- networkTimeout: z4.optional(z4.number()),
206
- timeout: z4.optional(z4.number()),
207
- trust: z4.optional(z4.boolean()),
208
- cwd: z4.optional(z4.string()),
209
- transport: z4.optional(McpTransportTypeSchema),
210
- type: z4.optional(z4.enum(["sse", "streamable-http"])),
211
- alwaysAllow: z4.optional(z4.array(z4.string())),
212
- tools: z4.optional(z4.array(z4.string())),
213
- kiroAutoApprove: z4.optional(z4.array(z4.string())),
214
- kiroAutoBlock: z4.optional(z4.array(z4.string())),
215
- headers: z4.optional(z4.record(z4.string(), z4.string()))
204
+ import { z as z5 } from "zod/mini";
205
+ var McpTransportTypeSchema = z5.enum(["stdio", "sse", "http"]);
206
+ var McpServerBaseSchema = z5.object({
207
+ command: z5.optional(z5.string()),
208
+ args: z5.optional(z5.array(z5.string())),
209
+ url: z5.optional(z5.string()),
210
+ httpUrl: z5.optional(z5.string()),
211
+ env: z5.optional(z5.record(z5.string(), z5.string())),
212
+ disabled: z5.optional(z5.boolean()),
213
+ networkTimeout: z5.optional(z5.number()),
214
+ timeout: z5.optional(z5.number()),
215
+ trust: z5.optional(z5.boolean()),
216
+ cwd: z5.optional(z5.string()),
217
+ transport: z5.optional(McpTransportTypeSchema),
218
+ type: z5.optional(z5.enum(["sse", "streamable-http"])),
219
+ alwaysAllow: z5.optional(z5.array(z5.string())),
220
+ tools: z5.optional(z5.array(z5.string())),
221
+ kiroAutoApprove: z5.optional(z5.array(z5.string())),
222
+ kiroAutoBlock: z5.optional(z5.array(z5.string())),
223
+ headers: z5.optional(z5.record(z5.string(), z5.string()))
216
224
  });
217
- var RulesyncMcpServerSchema = z4.extend(McpServerBaseSchema, {
218
- targets: z4.optional(RulesyncTargetsSchema)
225
+ var RulesyncMcpServerSchema = z5.extend(McpServerBaseSchema, {
226
+ targets: z5.optional(RulesyncTargetsSchema)
219
227
  });
220
- var McpConfigSchema = z4.object({
221
- mcpServers: z4.record(z4.string(), McpServerBaseSchema)
228
+ var McpConfigSchema = z5.object({
229
+ mcpServers: z5.record(z5.string(), McpServerBaseSchema)
222
230
  });
223
- var RulesyncMcpConfigSchema = z4.object({
224
- mcpServers: z4.record(z4.string(), RulesyncMcpServerSchema)
231
+ var RulesyncMcpConfigSchema = z5.object({
232
+ mcpServers: z5.record(z5.string(), RulesyncMcpServerSchema)
225
233
  });
226
234
 
227
235
  // src/types/rules.ts
228
- import { z as z5 } from "zod/mini";
229
- var RuleFrontmatterSchema = z5.object({
230
- root: z5.boolean(),
231
- targets: RulesyncTargetsSchema,
232
- description: z5.string(),
233
- globs: z5.array(z5.string()),
234
- cursorRuleType: z5.optional(z5.enum(["always", "manual", "specificFiles", "intelligently"])),
235
- windsurfActivationMode: z5.optional(z5.enum(["always", "manual", "model-decision", "glob"])),
236
- windsurfOutputFormat: z5.optional(z5.enum(["single-file", "directory"])),
237
- tags: z5.optional(z5.array(z5.string()))
236
+ import { z as z6 } from "zod/mini";
237
+ var RuleFrontmatterSchema = z6.object({
238
+ root: z6.optional(z6.boolean()),
239
+ targets: z6.optional(RulesyncTargetsSchema),
240
+ description: z6.optional(z6.string()),
241
+ globs: z6.optional(z6.array(z6.string())),
242
+ cursorRuleType: z6.optional(z6.enum(["always", "manual", "specificFiles", "intelligently"])),
243
+ windsurfActivationMode: z6.optional(z6.enum(["always", "manual", "model-decision", "glob"])),
244
+ windsurfOutputFormat: z6.optional(z6.enum(["single-file", "directory"])),
245
+ tags: z6.optional(z6.array(z6.string()))
238
246
  });
239
- var ParsedRuleSchema = z5.object({
247
+ var ParsedRuleSchema = z6.object({
240
248
  frontmatter: RuleFrontmatterSchema,
241
- content: z5.string(),
242
- filename: z5.string(),
243
- filepath: z5.string()
249
+ content: z6.string(),
250
+ filename: z6.string(),
251
+ filepath: z6.string(),
252
+ type: z6.optional(z6.enum(["rule", "command"]))
244
253
  });
245
- var GeneratedOutputSchema = z5.object({
254
+ var GeneratedOutputSchema = z6.object({
246
255
  tool: ToolTargetSchema,
247
- filepath: z5.string(),
248
- content: z5.string()
256
+ filepath: z6.string(),
257
+ content: z6.string()
249
258
  });
250
- var GenerateOptionsSchema = z5.object({
251
- targetTools: z5.optional(ToolTargetsSchema),
252
- outputDir: z5.optional(z5.string()),
253
- watch: z5.optional(z5.boolean())
259
+ var GenerateOptionsSchema = z6.object({
260
+ targetTools: z6.optional(ToolTargetsSchema),
261
+ outputDir: z6.optional(z6.string()),
262
+ watch: z6.optional(z6.boolean())
254
263
  });
255
264
 
256
265
  // src/utils/config-loader.ts
@@ -542,28 +551,6 @@ async function removeClaudeGeneratedFiles() {
542
551
  }
543
552
  }
544
553
 
545
- // src/utils/rules.ts
546
- function isToolSpecificRule(rule, targetTool) {
547
- const filename = rule.filename;
548
- const toolPatterns = {
549
- "augmentcode-legacy": /^specification-augmentcode-legacy-/i,
550
- augmentcode: /^specification-augmentcode-/i,
551
- copilot: /^specification-copilot-/i,
552
- cursor: /^specification-cursor-/i,
553
- cline: /^specification-cline-/i,
554
- claudecode: /^specification-claudecode-/i,
555
- roo: /^specification-roo-/i,
556
- geminicli: /^specification-geminicli-/i,
557
- kiro: /^specification-kiro-/i
558
- };
559
- for (const [tool, pattern] of Object.entries(toolPatterns)) {
560
- if (pattern.test(filename)) {
561
- return tool === targetTool;
562
- }
563
- }
564
- return true;
565
- }
566
-
567
554
  // src/cli/commands/config.ts
568
555
  async function configCommand(options = {}) {
569
556
  if (options.init) {
@@ -775,10 +762,165 @@ export default config;
775
762
  }
776
763
 
777
764
  // src/cli/commands/generate.ts
778
- import { join as join11 } from "path";
765
+ import { join as join14 } from "path";
779
766
 
780
- // src/generators/ignore/shared-factory.ts
767
+ // src/core/command-generator.ts
768
+ import { join as join5 } from "path";
769
+
770
+ // src/generators/commands/claudecode.ts
781
771
  import { join as join3 } from "path";
772
+ var ClaudeCodeCommandGenerator = class {
773
+ generate(command, outputDir) {
774
+ const filepath = this.getOutputPath(command.filename, outputDir);
775
+ const frontmatter = ["---"];
776
+ if (command.frontmatter.description) {
777
+ frontmatter.push(`description: ${command.frontmatter.description}`);
778
+ }
779
+ frontmatter.push("---");
780
+ const content = `${frontmatter.join("\n")}
781
+
782
+ ${command.content.trim()}
783
+ `;
784
+ return {
785
+ tool: "claudecode",
786
+ filepath,
787
+ content
788
+ };
789
+ }
790
+ getOutputPath(filename, baseDir) {
791
+ const flattenedName = filename.replace(/\//g, "-");
792
+ return join3(baseDir, ".claude", "commands", `${flattenedName}.md`);
793
+ }
794
+ };
795
+
796
+ // src/generators/commands/geminicli.ts
797
+ import { join as join4 } from "path";
798
+ var GeminiCliCommandGenerator = class {
799
+ generate(command, outputDir) {
800
+ const filepath = this.getOutputPath(command.filename, outputDir);
801
+ const convertedContent = this.convertSyntax(command.content);
802
+ const tomlLines = [];
803
+ if (command.frontmatter.description) {
804
+ tomlLines.push(`description = "${this.escapeTomlString(command.frontmatter.description)}"`);
805
+ tomlLines.push("");
806
+ }
807
+ tomlLines.push(`prompt = """${convertedContent}"""`);
808
+ const content = tomlLines.join("\n") + "\n";
809
+ return {
810
+ tool: "geminicli",
811
+ filepath,
812
+ content
813
+ };
814
+ }
815
+ getOutputPath(filename, baseDir) {
816
+ const tomlFilename = filename.replace(/\.md$/, ".toml");
817
+ const filenameWithExt = tomlFilename.endsWith(".toml") ? tomlFilename : `${tomlFilename}.toml`;
818
+ return join4(baseDir, ".gemini", "commands", filenameWithExt);
819
+ }
820
+ convertSyntax(content) {
821
+ let converted = content;
822
+ converted = converted.replace(/\$ARGUMENTS/g, "{{args}}");
823
+ converted = converted.replace(/!`([^`]+)`/g, "!{$1}");
824
+ const atSyntaxMatches = converted.match(/@[^\s]+/g);
825
+ if (atSyntaxMatches) {
826
+ console.warn(
827
+ `\u26A0\uFE0F Warning: @ syntax found (${atSyntaxMatches.join(", ")}). Gemini CLI does not support file content injection. Consider using shell commands or remove these references.`
828
+ );
829
+ }
830
+ return converted.trim();
831
+ }
832
+ escapeTomlString(str) {
833
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
834
+ }
835
+ };
836
+
837
+ // src/generators/commands/index.ts
838
+ var commandGenerators = {
839
+ claudecode: new ClaudeCodeCommandGenerator(),
840
+ geminicli: new GeminiCliCommandGenerator()
841
+ };
842
+ function getCommandGenerator(tool) {
843
+ return commandGenerators[tool];
844
+ }
845
+
846
+ // src/core/command-parser.ts
847
+ import { basename } from "path";
848
+ import matter from "gray-matter";
849
+ async function parseCommandsFromDirectory(commandsDir) {
850
+ const commandFiles = await findFiles(commandsDir, ".md");
851
+ const commands = [];
852
+ const errors = [];
853
+ for (const filepath of commandFiles) {
854
+ try {
855
+ const command = await parseCommandFile(filepath);
856
+ commands.push(command);
857
+ } catch (error) {
858
+ const errorMessage = error instanceof Error ? error.message : String(error);
859
+ errors.push(`Failed to parse command file ${filepath}: ${errorMessage}`);
860
+ }
861
+ }
862
+ if (errors.length > 0) {
863
+ console.warn(`\u26A0\uFE0F Command parsing errors:
864
+ ${errors.join("\n")}`);
865
+ }
866
+ return commands;
867
+ }
868
+ async function parseCommandFile(filepath) {
869
+ const content = await readFileContent(filepath);
870
+ const parsed = matter(content);
871
+ try {
872
+ const validatedData = CommandFrontmatterSchema.parse(parsed.data);
873
+ const filename = basename(filepath, ".md");
874
+ return {
875
+ frontmatter: {
876
+ description: validatedData.description
877
+ },
878
+ content: parsed.content,
879
+ filename,
880
+ filepath
881
+ };
882
+ } catch (error) {
883
+ throw new Error(
884
+ `Invalid frontmatter in ${filepath}: ${error instanceof Error ? error.message : String(error)}`
885
+ );
886
+ }
887
+ }
888
+
889
+ // src/core/command-generator.ts
890
+ async function generateCommands(projectRoot, baseDir, targets) {
891
+ const commandsDir = join5(projectRoot, ".rulesync", "commands");
892
+ if (!await fileExists(commandsDir)) {
893
+ return [];
894
+ }
895
+ const commands = await parseCommandsFromDirectory(commandsDir);
896
+ if (commands.length === 0) {
897
+ return [];
898
+ }
899
+ const outputs = [];
900
+ const outputDir = baseDir || projectRoot;
901
+ const supportedTargets = targets.filter((target) => ["claudecode", "geminicli"].includes(target));
902
+ for (const target of supportedTargets) {
903
+ const generator = getCommandGenerator(target);
904
+ if (!generator) {
905
+ continue;
906
+ }
907
+ for (const command of commands) {
908
+ try {
909
+ const output = generator.generate(command, outputDir);
910
+ outputs.push(output);
911
+ } catch (error) {
912
+ const errorMessage = error instanceof Error ? error.message : String(error);
913
+ console.error(
914
+ `\u274C Failed to generate ${target} command for ${command.filename}: ${errorMessage}`
915
+ );
916
+ }
917
+ }
918
+ }
919
+ return outputs;
920
+ }
921
+
922
+ // src/generators/ignore/shared-factory.ts
923
+ import { join as join6 } from "path";
782
924
 
783
925
  // src/generators/ignore/shared-helpers.ts
784
926
  function extractIgnorePatternsFromRules(rules) {
@@ -896,7 +1038,7 @@ function generateIgnoreFile(rules, config, ignoreConfig, baseDir) {
896
1038
  const outputs = [];
897
1039
  const content = generateIgnoreContent(rules, ignoreConfig);
898
1040
  const outputPath = baseDir || process.cwd();
899
- const filepath = join3(outputPath, ignoreConfig.filename);
1041
+ const filepath = join6(outputPath, ignoreConfig.filename);
900
1042
  outputs.push({
901
1043
  tool: ignoreConfig.tool,
902
1044
  filepath,
@@ -1484,20 +1626,20 @@ function generateWindsurfIgnore(rules, config, baseDir) {
1484
1626
  }
1485
1627
 
1486
1628
  // src/generators/rules/augmentcode.ts
1487
- import { join as join6 } from "path";
1629
+ import { join as join9 } from "path";
1488
1630
 
1489
1631
  // src/generators/rules/shared-helpers.ts
1490
- import { join as join5 } from "path";
1632
+ import { join as join8 } from "path";
1491
1633
 
1492
1634
  // src/utils/ignore.ts
1493
- import { join as join4 } from "path";
1635
+ import { join as join7 } from "path";
1494
1636
  import micromatch from "micromatch";
1495
1637
  var cachedIgnorePatterns = null;
1496
1638
  async function loadIgnorePatterns(baseDir = process.cwd()) {
1497
1639
  if (cachedIgnorePatterns) {
1498
1640
  return cachedIgnorePatterns;
1499
1641
  }
1500
- const ignorePath = join4(baseDir, ".rulesyncignore");
1642
+ const ignorePath = join7(baseDir, ".rulesyncignore");
1501
1643
  if (!await fileExists(ignorePath)) {
1502
1644
  cachedIgnorePatterns = { patterns: [] };
1503
1645
  return cachedIgnorePatterns;
@@ -1551,7 +1693,7 @@ function addOutput(outputs, tool, config, baseDir, relativePath, content) {
1551
1693
  const outputDir = resolveOutputDir(config, tool, baseDir);
1552
1694
  outputs.push({
1553
1695
  tool,
1554
- filepath: join5(outputDir, relativePath),
1696
+ filepath: join8(outputDir, relativePath),
1555
1697
  content
1556
1698
  });
1557
1699
  }
@@ -1560,7 +1702,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
1560
1702
  for (const rule of rules) {
1561
1703
  const content = generatorConfig.generateContent(rule);
1562
1704
  const outputDir = resolveOutputDir(config, generatorConfig.tool, baseDir);
1563
- const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join5(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
1705
+ const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join8(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
1564
1706
  outputs.push({
1565
1707
  tool: generatorConfig.tool,
1566
1708
  filepath,
@@ -1568,7 +1710,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
1568
1710
  });
1569
1711
  }
1570
1712
  const ignorePatterns = await loadIgnorePatterns(baseDir);
1571
- if (ignorePatterns.patterns.length > 0) {
1713
+ if (ignorePatterns.patterns.length > 0 && generatorConfig.ignoreFileName) {
1572
1714
  const ignorePath = resolvePath(generatorConfig.ignoreFileName, baseDir);
1573
1715
  const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, generatorConfig.tool);
1574
1716
  outputs.push({
@@ -1588,7 +1730,7 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
1588
1730
  for (const rule of detailRules) {
1589
1731
  const content = generatorConfig.generateDetailContent(rule);
1590
1732
  const filepath = resolvePath(
1591
- join5(generatorConfig.detailSubDir, `${rule.filename}.md`),
1733
+ join8(generatorConfig.detailSubDir, `${rule.filename}.md`),
1592
1734
  baseDir
1593
1735
  );
1594
1736
  outputs.push({
@@ -1609,13 +1751,15 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
1609
1751
  }
1610
1752
  const ignorePatterns = await loadIgnorePatterns(baseDir);
1611
1753
  if (ignorePatterns.patterns.length > 0) {
1612
- const ignorePath = resolvePath(generatorConfig.ignoreFileName, baseDir);
1613
- const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, generatorConfig.tool);
1614
- outputs.push({
1615
- tool: generatorConfig.tool,
1616
- filepath: ignorePath,
1617
- content: ignoreContent
1618
- });
1754
+ if (generatorConfig.ignoreFileName) {
1755
+ const ignorePath = resolvePath(generatorConfig.ignoreFileName, baseDir);
1756
+ const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, generatorConfig.tool);
1757
+ outputs.push({
1758
+ tool: generatorConfig.tool,
1759
+ filepath: ignorePath,
1760
+ content: ignoreContent
1761
+ });
1762
+ }
1619
1763
  if (generatorConfig.updateAdditionalConfig) {
1620
1764
  const additionalOutputs = await generatorConfig.updateAdditionalConfig(
1621
1765
  ignorePatterns.patterns,
@@ -1649,7 +1793,7 @@ async function generateAugmentcodeConfig(rules, config, baseDir) {
1649
1793
  "augmentcode",
1650
1794
  config,
1651
1795
  baseDir,
1652
- join6(".augment", "rules", `${rule.filename}.md`),
1796
+ join9(".augment", "rules", `${rule.filename}.md`),
1653
1797
  generateRuleFile(rule)
1654
1798
  );
1655
1799
  });
@@ -1702,19 +1846,19 @@ function generateLegacyGuidelinesFile(allRules) {
1702
1846
  }
1703
1847
 
1704
1848
  // src/generators/rules/claudecode.ts
1705
- import { join as join7 } from "path";
1849
+ import { join as join10 } from "path";
1706
1850
  async function generateClaudecodeConfig(rules, config, baseDir) {
1707
1851
  const generatorConfig = {
1708
1852
  tool: "claudecode",
1709
1853
  fileExtension: ".md",
1710
- ignoreFileName: ".aiignore",
1854
+ // ignoreFileName omitted - Claude Code uses settings.json permissions.deny instead of ignore files
1711
1855
  generateContent: generateMemoryFile,
1712
1856
  generateRootContent: generateClaudeMarkdown,
1713
1857
  rootFilePath: "CLAUDE.md",
1714
1858
  generateDetailContent: generateMemoryFile,
1715
1859
  detailSubDir: ".claude/memories",
1716
1860
  updateAdditionalConfig: async (ignorePatterns, baseDir2) => {
1717
- const settingsPath = resolvePath(join7(".claude", "settings.json"), baseDir2);
1861
+ const settingsPath = resolvePath(join10(".claude", "settings.json"), baseDir2);
1718
1862
  await updateClaudeSettings(settingsPath, ignorePatterns);
1719
1863
  return [];
1720
1864
  }
@@ -1778,7 +1922,7 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
1778
1922
  }
1779
1923
 
1780
1924
  // src/generators/rules/generator-registry.ts
1781
- import { join as join8 } from "path";
1925
+ import { join as join11 } from "path";
1782
1926
  function determineCursorRuleType(frontmatter) {
1783
1927
  if (frontmatter.cursorRuleType) {
1784
1928
  return frontmatter.cursorRuleType;
@@ -1858,7 +2002,7 @@ var GENERATOR_REGISTRY = {
1858
2002
  },
1859
2003
  pathResolver: (rule, outputDir) => {
1860
2004
  const baseFilename = rule.filename.replace(/\.md$/, "");
1861
- return join8(outputDir, `${baseFilename}.instructions.md`);
2005
+ return join11(outputDir, `${baseFilename}.instructions.md`);
1862
2006
  }
1863
2007
  },
1864
2008
  cursor: {
@@ -1898,7 +2042,7 @@ var GENERATOR_REGISTRY = {
1898
2042
  return lines.join("\n");
1899
2043
  },
1900
2044
  pathResolver: (rule, outputDir) => {
1901
- return join8(outputDir, `${rule.filename}.mdc`);
2045
+ return join11(outputDir, `${rule.filename}.mdc`);
1902
2046
  }
1903
2047
  },
1904
2048
  codexcli: {
@@ -1934,10 +2078,10 @@ var GENERATOR_REGISTRY = {
1934
2078
  pathResolver: (rule, outputDir) => {
1935
2079
  const outputFormat = rule.frontmatter.windsurfOutputFormat || "directory";
1936
2080
  if (outputFormat === "single-file") {
1937
- return join8(outputDir, ".windsurf-rules");
2081
+ return join11(outputDir, ".windsurf-rules");
1938
2082
  } else {
1939
- const rulesDir = join8(outputDir, ".windsurf", "rules");
1940
- return join8(rulesDir, `${rule.filename}.md`);
2083
+ const rulesDir = join11(outputDir, ".windsurf", "rules");
2084
+ return join11(rulesDir, `${rule.filename}.md`);
1941
2085
  }
1942
2086
  }
1943
2087
  },
@@ -2201,7 +2345,7 @@ function filterRulesForTool(rules, tool, config) {
2201
2345
  if (!targets.includes(tool)) {
2202
2346
  return false;
2203
2347
  }
2204
- return isToolSpecificRule(rule, tool);
2348
+ return true;
2205
2349
  });
2206
2350
  }
2207
2351
  async function generateForTool(tool, rules, config, baseDir) {
@@ -2256,8 +2400,8 @@ async function generateForTool(tool, rules, config, baseDir) {
2256
2400
  }
2257
2401
 
2258
2402
  // src/core/parser.ts
2259
- import { basename } from "path";
2260
- import matter from "gray-matter";
2403
+ import { basename as basename2 } from "path";
2404
+ import matter2 from "gray-matter";
2261
2405
  async function parseRulesFromDirectory(aiRulesDir) {
2262
2406
  const ignorePatterns = await loadIgnorePatterns();
2263
2407
  const allRuleFiles = await findFiles(aiRulesDir, ".md");
@@ -2291,10 +2435,26 @@ ${errors.join("\n")}`);
2291
2435
  }
2292
2436
  async function parseRuleFile(filepath) {
2293
2437
  const content = await readFileContent(filepath);
2294
- const parsed = matter(content);
2438
+ const parsed = matter2(content);
2295
2439
  try {
2296
- const frontmatter = RuleFrontmatterSchema.parse(parsed.data);
2297
- const filename = basename(filepath, ".md");
2440
+ const validatedData = RuleFrontmatterSchema.parse(parsed.data);
2441
+ const frontmatter = {
2442
+ root: validatedData.root ?? false,
2443
+ targets: validatedData.targets ?? ["*"],
2444
+ description: validatedData.description ?? "",
2445
+ globs: validatedData.globs ?? [],
2446
+ ...validatedData.cursorRuleType !== void 0 && {
2447
+ cursorRuleType: validatedData.cursorRuleType
2448
+ },
2449
+ ...validatedData.windsurfActivationMode !== void 0 && {
2450
+ windsurfActivationMode: validatedData.windsurfActivationMode
2451
+ },
2452
+ ...validatedData.windsurfOutputFormat !== void 0 && {
2453
+ windsurfOutputFormat: validatedData.windsurfOutputFormat
2454
+ },
2455
+ ...validatedData.tags !== void 0 && { tags: validatedData.tags }
2456
+ };
2457
+ const filename = basename2(filepath, ".md");
2298
2458
  return {
2299
2459
  frontmatter,
2300
2460
  content: parsed.content,
@@ -2584,12 +2744,12 @@ async function generateCommand(options = {}) {
2584
2744
  for (const tool of targetTools) {
2585
2745
  switch (tool) {
2586
2746
  case "augmentcode":
2587
- deleteTasks.push(removeDirectory(join11(".augment", "rules")));
2588
- deleteTasks.push(removeDirectory(join11(".augment", "ignore")));
2747
+ deleteTasks.push(removeDirectory(join14(".augment", "rules")));
2748
+ deleteTasks.push(removeDirectory(join14(".augment", "ignore")));
2589
2749
  break;
2590
2750
  case "augmentcode-legacy":
2591
2751
  deleteTasks.push(removeClaudeGeneratedFiles());
2592
- deleteTasks.push(removeDirectory(join11(".augment", "ignore")));
2752
+ deleteTasks.push(removeDirectory(join14(".augment", "ignore")));
2593
2753
  break;
2594
2754
  case "copilot":
2595
2755
  deleteTasks.push(removeDirectory(config.outputPaths.copilot));
@@ -2602,12 +2762,14 @@ async function generateCommand(options = {}) {
2602
2762
  break;
2603
2763
  case "claudecode":
2604
2764
  deleteTasks.push(removeClaudeGeneratedFiles());
2765
+ deleteTasks.push(removeDirectory(join14(".claude", "commands")));
2605
2766
  break;
2606
2767
  case "roo":
2607
2768
  deleteTasks.push(removeDirectory(config.outputPaths.roo));
2608
2769
  break;
2609
2770
  case "geminicli":
2610
2771
  deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
2772
+ deleteTasks.push(removeDirectory(join14(".gemini", "commands")));
2611
2773
  break;
2612
2774
  case "kiro":
2613
2775
  deleteTasks.push(removeDirectory(config.outputPaths.kiro));
@@ -2672,11 +2834,37 @@ Generating configurations for base directory: ${baseDir}`);
2672
2834
  }
2673
2835
  }
2674
2836
  }
2675
- const totalGenerated = totalOutputs + totalMcpOutputs;
2837
+ if (config.verbose) {
2838
+ console.log("\nGenerating command files...");
2839
+ }
2840
+ let totalCommandOutputs = 0;
2841
+ for (const baseDir of baseDirs) {
2842
+ const commandResults = await generateCommands(
2843
+ process.cwd(),
2844
+ baseDir === process.cwd() ? void 0 : baseDir,
2845
+ config.defaultTargets
2846
+ );
2847
+ if (commandResults.length === 0) {
2848
+ if (config.verbose) {
2849
+ console.log(`No commands found for ${baseDir}`);
2850
+ }
2851
+ continue;
2852
+ }
2853
+ for (const result of commandResults) {
2854
+ await writeFileContent(result.filepath, result.content);
2855
+ console.log(`\u2705 Generated ${result.tool} command: ${result.filepath}`);
2856
+ totalCommandOutputs++;
2857
+ }
2858
+ }
2859
+ const totalGenerated = totalOutputs + totalMcpOutputs + totalCommandOutputs;
2676
2860
  if (totalGenerated > 0) {
2861
+ const parts = [];
2862
+ if (totalOutputs > 0) parts.push(`${totalOutputs} configurations`);
2863
+ if (totalMcpOutputs > 0) parts.push(`${totalMcpOutputs} MCP configurations`);
2864
+ if (totalCommandOutputs > 0) parts.push(`${totalCommandOutputs} commands`);
2677
2865
  console.log(
2678
2866
  `
2679
- \u{1F389} All done! Generated ${totalGenerated} file(s) total (${totalOutputs} configurations + ${totalMcpOutputs} MCP configurations)`
2867
+ \u{1F389} All done! Generated ${totalGenerated} file(s) total (${parts.join(" + ")})`
2680
2868
  );
2681
2869
  }
2682
2870
  } catch (error) {
@@ -2687,9 +2875,9 @@ Generating configurations for base directory: ${baseDir}`);
2687
2875
 
2688
2876
  // src/cli/commands/gitignore.ts
2689
2877
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2690
- import { join as join12 } from "path";
2878
+ import { join as join15 } from "path";
2691
2879
  var gitignoreCommand = async () => {
2692
- const gitignorePath = join12(process.cwd(), ".gitignore");
2880
+ const gitignorePath = join15(process.cwd(), ".gitignore");
2693
2881
  const rulesFilesToIgnore = [
2694
2882
  "# Generated by rulesync - AI tool configuration files",
2695
2883
  "**/.github/copilot-instructions.md",
@@ -2700,6 +2888,7 @@ var gitignoreCommand = async () => {
2700
2888
  "**/.clineignore",
2701
2889
  "**/CLAUDE.md",
2702
2890
  "**/.claude/memories/",
2891
+ "**/.claude/commands/",
2703
2892
  "**/codex.md",
2704
2893
  "**/.codexignore",
2705
2894
  "**/.roo/rules/",
@@ -2707,6 +2896,7 @@ var gitignoreCommand = async () => {
2707
2896
  "**/.copilotignore",
2708
2897
  "**/GEMINI.md",
2709
2898
  "**/.gemini/memories/",
2899
+ "**/.gemini/commands/",
2710
2900
  "**/.aiexclude",
2711
2901
  "**/.aiignore",
2712
2902
  "**/.augmentignore",
@@ -2753,12 +2943,12 @@ ${linesToAdd.join("\n")}
2753
2943
  };
2754
2944
 
2755
2945
  // src/core/importer.ts
2756
- import { join as join19 } from "path";
2757
- import matter6 from "gray-matter";
2946
+ import { join as join22 } from "path";
2947
+ import matter7 from "gray-matter";
2758
2948
 
2759
2949
  // src/parsers/augmentcode.ts
2760
- import { basename as basename2, join as join13 } from "path";
2761
- import matter2 from "gray-matter";
2950
+ import { basename as basename3, join as join16 } from "path";
2951
+ import matter3 from "gray-matter";
2762
2952
 
2763
2953
  // src/utils/parser-helpers.ts
2764
2954
  function createParseResult() {
@@ -2806,7 +2996,7 @@ async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
2806
2996
  async function parseUnifiedAugmentcode(baseDir, config) {
2807
2997
  const result = createParseResult();
2808
2998
  if (config.rulesDir) {
2809
- const rulesDir = join13(baseDir, config.rulesDir);
2999
+ const rulesDir = join16(baseDir, config.rulesDir);
2810
3000
  if (await fileExists(rulesDir)) {
2811
3001
  const rulesResult = await parseAugmentRules(rulesDir, config);
2812
3002
  addRules(result, rulesResult.rules);
@@ -2819,7 +3009,7 @@ async function parseUnifiedAugmentcode(baseDir, config) {
2819
3009
  }
2820
3010
  }
2821
3011
  if (config.legacyFilePath) {
2822
- const legacyPath = join13(baseDir, config.legacyFilePath);
3012
+ const legacyPath = join16(baseDir, config.legacyFilePath);
2823
3013
  if (await fileExists(legacyPath)) {
2824
3014
  const legacyResult = await parseAugmentGuidelines(legacyPath, config);
2825
3015
  if (legacyResult.rule) {
@@ -2843,16 +3033,16 @@ async function parseAugmentRules(rulesDir, config) {
2843
3033
  const files = await readdir2(rulesDir);
2844
3034
  for (const file of files) {
2845
3035
  if (file.endsWith(".md") || file.endsWith(".mdc")) {
2846
- const filePath = join13(rulesDir, file);
3036
+ const filePath = join16(rulesDir, file);
2847
3037
  try {
2848
3038
  const rawContent = await readFileContent(filePath);
2849
- const parsed = matter2(rawContent);
3039
+ const parsed = matter3(rawContent);
2850
3040
  const frontmatterData = parsed.data;
2851
3041
  const ruleType = frontmatterData.type || "manual";
2852
3042
  const description = frontmatterData.description || "";
2853
3043
  const tags = Array.isArray(frontmatterData.tags) ? frontmatterData.tags : void 0;
2854
3044
  const isRoot = ruleType === "always";
2855
- const filename = basename2(file, file.endsWith(".mdc") ? ".mdc" : ".md");
3045
+ const filename = basename3(file, file.endsWith(".mdc") ? ".mdc" : ".md");
2856
3046
  const frontmatter = {
2857
3047
  root: isRoot,
2858
3048
  targets: [config.targetName],
@@ -2910,8 +3100,8 @@ async function parseAugmentGuidelines(guidelinesPath, config) {
2910
3100
  }
2911
3101
 
2912
3102
  // src/parsers/shared-helpers.ts
2913
- import { basename as basename3, join as join14 } from "path";
2914
- import matter3 from "gray-matter";
3103
+ import { basename as basename4, join as join17 } from "path";
3104
+ import matter4 from "gray-matter";
2915
3105
  async function parseConfigurationFiles(baseDir = process.cwd(), config) {
2916
3106
  const errors = [];
2917
3107
  const rules = [];
@@ -2924,7 +3114,7 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
2924
3114
  let content;
2925
3115
  let frontmatter;
2926
3116
  if (mainFile.useFrontmatter) {
2927
- const parsed = matter3(rawContent);
3117
+ const parsed = matter4(rawContent);
2928
3118
  content = parsed.content.trim();
2929
3119
  const parsedFrontmatter = parsed.data;
2930
3120
  frontmatter = {
@@ -2966,14 +3156,14 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
2966
3156
  const files = await readdir2(dirPath);
2967
3157
  for (const file of files) {
2968
3158
  if (file.endsWith(dirConfig.filePattern)) {
2969
- const filePath = join14(dirPath, file);
3159
+ const filePath = join17(dirPath, file);
2970
3160
  const fileResult = await safeAsyncOperation(async () => {
2971
3161
  const rawContent = await readFileContent(filePath);
2972
3162
  let content;
2973
3163
  let frontmatter;
2974
3164
  const filename = file.replace(new RegExp(`\\${dirConfig.filePattern}$`), "");
2975
3165
  if (dirConfig.filePattern === ".instructions.md") {
2976
- const parsed = matter3(rawContent);
3166
+ const parsed = matter4(rawContent);
2977
3167
  content = parsed.content.trim();
2978
3168
  const parsedFrontmatter = parsed.data;
2979
3169
  frontmatter = {
@@ -3039,6 +3229,13 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
3039
3229
  const memoryRules = await parseMemoryFiles(memoryDir, config);
3040
3230
  rules.push(...memoryRules);
3041
3231
  }
3232
+ if (config.commandsDirPath) {
3233
+ const commandsDir = resolvePath(config.commandsDirPath, baseDir);
3234
+ if (await fileExists(commandsDir)) {
3235
+ const commandsRules = await parseCommandsFiles(commandsDir, config);
3236
+ rules.push(...commandsRules);
3237
+ }
3238
+ }
3042
3239
  const settingsPath = resolvePath(config.settingsPath, baseDir);
3043
3240
  if (await fileExists(settingsPath)) {
3044
3241
  const settingsResult = await parseSettingsFile(settingsPath, config.tool);
@@ -3104,10 +3301,10 @@ async function parseMemoryFiles(memoryDir, config) {
3104
3301
  const files = await readdir2(memoryDir);
3105
3302
  for (const file of files) {
3106
3303
  if (file.endsWith(".md")) {
3107
- const filePath = join14(memoryDir, file);
3304
+ const filePath = join17(memoryDir, file);
3108
3305
  const content = await readFileContent(filePath);
3109
3306
  if (content.trim()) {
3110
- const filename = basename3(file, ".md");
3307
+ const filename = basename4(file, ".md");
3111
3308
  const frontmatter = {
3112
3309
  root: false,
3113
3310
  targets: [config.tool],
@@ -3127,6 +3324,54 @@ async function parseMemoryFiles(memoryDir, config) {
3127
3324
  }
3128
3325
  return rules;
3129
3326
  }
3327
+ async function parseCommandsFiles(commandsDir, config) {
3328
+ const rules = [];
3329
+ try {
3330
+ const { readdir: readdir2 } = await import("fs/promises");
3331
+ const files = await readdir2(commandsDir);
3332
+ for (const file of files) {
3333
+ if (file.endsWith(".md")) {
3334
+ const filePath = join17(commandsDir, file);
3335
+ const content = await readFileContent(filePath);
3336
+ if (content.trim()) {
3337
+ const filename = basename4(file, ".md");
3338
+ let frontmatter;
3339
+ let ruleContent;
3340
+ try {
3341
+ const parsed = matter4(content);
3342
+ ruleContent = parsed.content.trim();
3343
+ const parsedFrontmatter = parsed.data;
3344
+ frontmatter = {
3345
+ root: false,
3346
+ targets: [config.tool],
3347
+ description: parsedFrontmatter.description || `Command: ${filename}`,
3348
+ globs: ["**/*"]
3349
+ };
3350
+ } catch {
3351
+ ruleContent = content.trim();
3352
+ frontmatter = {
3353
+ root: false,
3354
+ targets: [config.tool],
3355
+ description: `Command: ${filename}`,
3356
+ globs: ["**/*"]
3357
+ };
3358
+ }
3359
+ if (ruleContent) {
3360
+ rules.push({
3361
+ frontmatter,
3362
+ content: ruleContent,
3363
+ filename,
3364
+ filepath: filePath,
3365
+ type: "command"
3366
+ });
3367
+ }
3368
+ }
3369
+ }
3370
+ }
3371
+ } catch {
3372
+ }
3373
+ return rules;
3374
+ }
3130
3375
  async function parseSettingsFile(settingsPath, tool) {
3131
3376
  const errors = [];
3132
3377
  let ignorePatterns;
@@ -3174,7 +3419,8 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
3174
3419
  settingsPath: ".claude/settings.json",
3175
3420
  mainDescription: "Main Claude Code configuration",
3176
3421
  memoryDescription: "Memory file",
3177
- filenamePrefix: "claude"
3422
+ filenamePrefix: "claude",
3423
+ commandsDirPath: ".claude/commands"
3178
3424
  });
3179
3425
  }
3180
3426
 
@@ -3199,7 +3445,7 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
3199
3445
  }
3200
3446
 
3201
3447
  // src/parsers/codexcli.ts
3202
- import { join as join15 } from "path";
3448
+ import { join as join18 } from "path";
3203
3449
 
3204
3450
  // src/parsers/copilot.ts
3205
3451
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
@@ -3222,10 +3468,10 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
3222
3468
  }
3223
3469
 
3224
3470
  // src/parsers/cursor.ts
3225
- import { basename as basename4, join as join16 } from "path";
3226
- import matter4 from "gray-matter";
3471
+ import { basename as basename5, join as join19 } from "path";
3472
+ import matter5 from "gray-matter";
3227
3473
  import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
3228
- import { z as z6 } from "zod/mini";
3474
+ import { z as z7 } from "zod/mini";
3229
3475
  var customMatterOptions = {
3230
3476
  engines: {
3231
3477
  yaml: {
@@ -3253,7 +3499,7 @@ var customMatterOptions = {
3253
3499
  }
3254
3500
  };
3255
3501
  function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
3256
- const FrontmatterSchema = z6.record(z6.string(), z6.unknown());
3502
+ const FrontmatterSchema = z7.record(z7.string(), z7.unknown());
3257
3503
  const parseResult = FrontmatterSchema.safeParse(cursorFrontmatter);
3258
3504
  if (!parseResult.success) {
3259
3505
  return {
@@ -3347,11 +3593,11 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3347
3593
  const rules = [];
3348
3594
  let ignorePatterns;
3349
3595
  let mcpServers;
3350
- const cursorFilePath = join16(baseDir, ".cursorrules");
3596
+ const cursorFilePath = join19(baseDir, ".cursorrules");
3351
3597
  if (await fileExists(cursorFilePath)) {
3352
3598
  try {
3353
3599
  const rawContent = await readFileContent(cursorFilePath);
3354
- const parsed = matter4(rawContent, customMatterOptions);
3600
+ const parsed = matter5(rawContent, customMatterOptions);
3355
3601
  const content = parsed.content.trim();
3356
3602
  if (content) {
3357
3603
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, "cursorrules");
@@ -3368,20 +3614,20 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3368
3614
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
3369
3615
  }
3370
3616
  }
3371
- const cursorRulesDir = join16(baseDir, ".cursor", "rules");
3617
+ const cursorRulesDir = join19(baseDir, ".cursor", "rules");
3372
3618
  if (await fileExists(cursorRulesDir)) {
3373
3619
  try {
3374
3620
  const { readdir: readdir2 } = await import("fs/promises");
3375
3621
  const files = await readdir2(cursorRulesDir);
3376
3622
  for (const file of files) {
3377
3623
  if (file.endsWith(".mdc")) {
3378
- const filePath = join16(cursorRulesDir, file);
3624
+ const filePath = join19(cursorRulesDir, file);
3379
3625
  try {
3380
3626
  const rawContent = await readFileContent(filePath);
3381
- const parsed = matter4(rawContent, customMatterOptions);
3627
+ const parsed = matter5(rawContent, customMatterOptions);
3382
3628
  const content = parsed.content.trim();
3383
3629
  if (content) {
3384
- const filename = basename4(file, ".mdc");
3630
+ const filename = basename5(file, ".mdc");
3385
3631
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, filename);
3386
3632
  rules.push({
3387
3633
  frontmatter,
@@ -3404,7 +3650,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3404
3650
  if (rules.length === 0) {
3405
3651
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
3406
3652
  }
3407
- const cursorIgnorePath = join16(baseDir, ".cursorignore");
3653
+ const cursorIgnorePath = join19(baseDir, ".cursorignore");
3408
3654
  if (await fileExists(cursorIgnorePath)) {
3409
3655
  try {
3410
3656
  const content = await readFileContent(cursorIgnorePath);
@@ -3417,7 +3663,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3417
3663
  errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
3418
3664
  }
3419
3665
  }
3420
- const cursorMcpPath = join16(baseDir, ".cursor", "mcp.json");
3666
+ const cursorMcpPath = join19(baseDir, ".cursor", "mcp.json");
3421
3667
  if (await fileExists(cursorMcpPath)) {
3422
3668
  try {
3423
3669
  const content = await readFileContent(cursorMcpPath);
@@ -3461,16 +3707,17 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
3461
3707
  additionalIgnoreFile: {
3462
3708
  path: ".aiexclude",
3463
3709
  parser: parseAiexclude
3464
- }
3710
+ },
3711
+ commandsDirPath: ".gemini/commands"
3465
3712
  });
3466
3713
  }
3467
3714
 
3468
3715
  // src/parsers/junie.ts
3469
- import { join as join17 } from "path";
3716
+ import { join as join20 } from "path";
3470
3717
  async function parseJunieConfiguration(baseDir = process.cwd()) {
3471
3718
  const errors = [];
3472
3719
  const rules = [];
3473
- const guidelinesPath = join17(baseDir, ".junie", "guidelines.md");
3720
+ const guidelinesPath = join20(baseDir, ".junie", "guidelines.md");
3474
3721
  if (!await fileExists(guidelinesPath)) {
3475
3722
  errors.push(".junie/guidelines.md file not found");
3476
3723
  return { rules, errors };
@@ -3523,8 +3770,8 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
3523
3770
 
3524
3771
  // src/parsers/windsurf.ts
3525
3772
  import { readFile as readFile2 } from "fs/promises";
3526
- import { join as join18 } from "path";
3527
- import matter5 from "gray-matter";
3773
+ import { join as join21 } from "path";
3774
+ import matter6 from "gray-matter";
3528
3775
 
3529
3776
  // src/core/importer.ts
3530
3777
  async function importConfiguration(options) {
@@ -3610,7 +3857,7 @@ async function importConfiguration(options) {
3610
3857
  if (rules.length === 0 && !ignorePatterns && !mcpServers) {
3611
3858
  return { success: false, rulesCreated: 0, errors };
3612
3859
  }
3613
- const rulesDirPath = join19(baseDir, rulesDir);
3860
+ const rulesDirPath = join22(baseDir, rulesDir);
3614
3861
  try {
3615
3862
  const { mkdir: mkdir3 } = await import("fs/promises");
3616
3863
  await mkdir3(rulesDirPath, { recursive: true });
@@ -3623,8 +3870,13 @@ async function importConfiguration(options) {
3623
3870
  for (const rule of rules) {
3624
3871
  try {
3625
3872
  const baseFilename = rule.filename;
3626
- const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
3627
- const filePath = join19(rulesDirPath, `${filename}.md`);
3873
+ let targetDir = rulesDirPath;
3874
+ if (rule.type === "command") {
3875
+ targetDir = join22(rulesDirPath, "commands");
3876
+ const { mkdir: mkdir3 } = await import("fs/promises");
3877
+ await mkdir3(targetDir, { recursive: true });
3878
+ }
3879
+ const filePath = join22(targetDir, `${baseFilename}.md`);
3628
3880
  const content = generateRuleFileContent(rule);
3629
3881
  await writeFileContent(filePath, content);
3630
3882
  rulesCreated++;
@@ -3639,7 +3891,7 @@ async function importConfiguration(options) {
3639
3891
  let ignoreFileCreated = false;
3640
3892
  if (ignorePatterns && ignorePatterns.length > 0) {
3641
3893
  try {
3642
- const rulesyncignorePath = join19(baseDir, ".rulesyncignore");
3894
+ const rulesyncignorePath = join22(baseDir, ".rulesyncignore");
3643
3895
  const ignoreContent = `${ignorePatterns.join("\n")}
3644
3896
  `;
3645
3897
  await writeFileContent(rulesyncignorePath, ignoreContent);
@@ -3655,7 +3907,7 @@ async function importConfiguration(options) {
3655
3907
  let mcpFileCreated = false;
3656
3908
  if (mcpServers && Object.keys(mcpServers).length > 0) {
3657
3909
  try {
3658
- const mcpPath = join19(baseDir, rulesDir, ".mcp.json");
3910
+ const mcpPath = join22(baseDir, rulesDir, ".mcp.json");
3659
3911
  const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
3660
3912
  `;
3661
3913
  await writeFileContent(mcpPath, mcpContent);
@@ -3677,17 +3929,16 @@ async function importConfiguration(options) {
3677
3929
  };
3678
3930
  }
3679
3931
  function generateRuleFileContent(rule) {
3680
- const frontmatter = matter6.stringify("", rule.frontmatter);
3681
- return frontmatter + rule.content;
3682
- }
3683
- async function generateUniqueFilename(rulesDir, baseFilename) {
3684
- let filename = baseFilename;
3685
- let counter = 1;
3686
- while (await fileExists(join19(rulesDir, `${filename}.md`))) {
3687
- filename = `${baseFilename}-${counter}`;
3688
- counter++;
3932
+ if (rule.type === "command") {
3933
+ const simplifiedFrontmatter = {
3934
+ description: rule.frontmatter.description,
3935
+ targets: rule.frontmatter.targets
3936
+ };
3937
+ const frontmatter2 = matter7.stringify("", simplifiedFrontmatter);
3938
+ return frontmatter2 + rule.content;
3689
3939
  }
3690
- return filename;
3940
+ const frontmatter = matter7.stringify("", rule.frontmatter);
3941
+ return frontmatter + rule.content;
3691
3942
  }
3692
3943
 
3693
3944
  // src/cli/commands/import.ts
@@ -3750,7 +4001,7 @@ async function importCommand(options = {}) {
3750
4001
  }
3751
4002
 
3752
4003
  // src/cli/commands/init.ts
3753
- import { join as join20 } from "path";
4004
+ import { join as join23 } from "path";
3754
4005
  async function initCommand() {
3755
4006
  const aiRulesDir = ".rulesync";
3756
4007
  console.log("Initializing rulesync...");
@@ -3797,7 +4048,7 @@ globs: ["**/*"]
3797
4048
  - Follow single responsibility principle
3798
4049
  `
3799
4050
  };
3800
- const filepath = join20(aiRulesDir, sampleFile.filename);
4051
+ const filepath = join23(aiRulesDir, sampleFile.filename);
3801
4052
  if (!await fileExists(filepath)) {
3802
4053
  await writeFileContent(filepath, sampleFile.content);
3803
4054
  console.log(`Created ${filepath}`);
@@ -3941,7 +4192,7 @@ async function watchCommand() {
3941
4192
 
3942
4193
  // src/cli/index.ts
3943
4194
  var program = new Command();
3944
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.56.0");
4195
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.58.0");
3945
4196
  program.command("init").description("Initialize rulesync in current directory").action(initCommand);
3946
4197
  program.command("add <filename>").description("Add a new rule file").action(addCommand);
3947
4198
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);