rulesync 0.57.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 +171 -18
  2. package/README.md +67 -727
  3. package/dist/index.cjs +447 -212
  4. package/dist/index.js +447 -212
  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.optional(z5.boolean()),
231
- targets: z5.optional(RulesyncTargetsSchema),
232
- description: z5.optional(z5.string()),
233
- globs: z5.optional(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,7 +2435,7 @@ ${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
2440
  const validatedData = RuleFrontmatterSchema.parse(parsed.data);
2297
2441
  const frontmatter = {
@@ -2310,7 +2454,7 @@ async function parseRuleFile(filepath) {
2310
2454
  },
2311
2455
  ...validatedData.tags !== void 0 && { tags: validatedData.tags }
2312
2456
  };
2313
- const filename = basename(filepath, ".md");
2457
+ const filename = basename2(filepath, ".md");
2314
2458
  return {
2315
2459
  frontmatter,
2316
2460
  content: parsed.content,
@@ -2600,12 +2744,12 @@ async function generateCommand(options = {}) {
2600
2744
  for (const tool of targetTools) {
2601
2745
  switch (tool) {
2602
2746
  case "augmentcode":
2603
- deleteTasks.push(removeDirectory(join11(".augment", "rules")));
2604
- deleteTasks.push(removeDirectory(join11(".augment", "ignore")));
2747
+ deleteTasks.push(removeDirectory(join14(".augment", "rules")));
2748
+ deleteTasks.push(removeDirectory(join14(".augment", "ignore")));
2605
2749
  break;
2606
2750
  case "augmentcode-legacy":
2607
2751
  deleteTasks.push(removeClaudeGeneratedFiles());
2608
- deleteTasks.push(removeDirectory(join11(".augment", "ignore")));
2752
+ deleteTasks.push(removeDirectory(join14(".augment", "ignore")));
2609
2753
  break;
2610
2754
  case "copilot":
2611
2755
  deleteTasks.push(removeDirectory(config.outputPaths.copilot));
@@ -2618,12 +2762,14 @@ async function generateCommand(options = {}) {
2618
2762
  break;
2619
2763
  case "claudecode":
2620
2764
  deleteTasks.push(removeClaudeGeneratedFiles());
2765
+ deleteTasks.push(removeDirectory(join14(".claude", "commands")));
2621
2766
  break;
2622
2767
  case "roo":
2623
2768
  deleteTasks.push(removeDirectory(config.outputPaths.roo));
2624
2769
  break;
2625
2770
  case "geminicli":
2626
2771
  deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
2772
+ deleteTasks.push(removeDirectory(join14(".gemini", "commands")));
2627
2773
  break;
2628
2774
  case "kiro":
2629
2775
  deleteTasks.push(removeDirectory(config.outputPaths.kiro));
@@ -2688,11 +2834,37 @@ Generating configurations for base directory: ${baseDir}`);
2688
2834
  }
2689
2835
  }
2690
2836
  }
2691
- 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;
2692
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`);
2693
2865
  console.log(
2694
2866
  `
2695
- \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(" + ")})`
2696
2868
  );
2697
2869
  }
2698
2870
  } catch (error) {
@@ -2703,9 +2875,9 @@ Generating configurations for base directory: ${baseDir}`);
2703
2875
 
2704
2876
  // src/cli/commands/gitignore.ts
2705
2877
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2706
- import { join as join12 } from "path";
2878
+ import { join as join15 } from "path";
2707
2879
  var gitignoreCommand = async () => {
2708
- const gitignorePath = join12(process.cwd(), ".gitignore");
2880
+ const gitignorePath = join15(process.cwd(), ".gitignore");
2709
2881
  const rulesFilesToIgnore = [
2710
2882
  "# Generated by rulesync - AI tool configuration files",
2711
2883
  "**/.github/copilot-instructions.md",
@@ -2716,6 +2888,7 @@ var gitignoreCommand = async () => {
2716
2888
  "**/.clineignore",
2717
2889
  "**/CLAUDE.md",
2718
2890
  "**/.claude/memories/",
2891
+ "**/.claude/commands/",
2719
2892
  "**/codex.md",
2720
2893
  "**/.codexignore",
2721
2894
  "**/.roo/rules/",
@@ -2723,6 +2896,7 @@ var gitignoreCommand = async () => {
2723
2896
  "**/.copilotignore",
2724
2897
  "**/GEMINI.md",
2725
2898
  "**/.gemini/memories/",
2899
+ "**/.gemini/commands/",
2726
2900
  "**/.aiexclude",
2727
2901
  "**/.aiignore",
2728
2902
  "**/.augmentignore",
@@ -2769,12 +2943,12 @@ ${linesToAdd.join("\n")}
2769
2943
  };
2770
2944
 
2771
2945
  // src/core/importer.ts
2772
- import { join as join19 } from "path";
2773
- import matter6 from "gray-matter";
2946
+ import { join as join22 } from "path";
2947
+ import matter7 from "gray-matter";
2774
2948
 
2775
2949
  // src/parsers/augmentcode.ts
2776
- import { basename as basename2, join as join13 } from "path";
2777
- import matter2 from "gray-matter";
2950
+ import { basename as basename3, join as join16 } from "path";
2951
+ import matter3 from "gray-matter";
2778
2952
 
2779
2953
  // src/utils/parser-helpers.ts
2780
2954
  function createParseResult() {
@@ -2822,7 +2996,7 @@ async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
2822
2996
  async function parseUnifiedAugmentcode(baseDir, config) {
2823
2997
  const result = createParseResult();
2824
2998
  if (config.rulesDir) {
2825
- const rulesDir = join13(baseDir, config.rulesDir);
2999
+ const rulesDir = join16(baseDir, config.rulesDir);
2826
3000
  if (await fileExists(rulesDir)) {
2827
3001
  const rulesResult = await parseAugmentRules(rulesDir, config);
2828
3002
  addRules(result, rulesResult.rules);
@@ -2835,7 +3009,7 @@ async function parseUnifiedAugmentcode(baseDir, config) {
2835
3009
  }
2836
3010
  }
2837
3011
  if (config.legacyFilePath) {
2838
- const legacyPath = join13(baseDir, config.legacyFilePath);
3012
+ const legacyPath = join16(baseDir, config.legacyFilePath);
2839
3013
  if (await fileExists(legacyPath)) {
2840
3014
  const legacyResult = await parseAugmentGuidelines(legacyPath, config);
2841
3015
  if (legacyResult.rule) {
@@ -2859,16 +3033,16 @@ async function parseAugmentRules(rulesDir, config) {
2859
3033
  const files = await readdir2(rulesDir);
2860
3034
  for (const file of files) {
2861
3035
  if (file.endsWith(".md") || file.endsWith(".mdc")) {
2862
- const filePath = join13(rulesDir, file);
3036
+ const filePath = join16(rulesDir, file);
2863
3037
  try {
2864
3038
  const rawContent = await readFileContent(filePath);
2865
- const parsed = matter2(rawContent);
3039
+ const parsed = matter3(rawContent);
2866
3040
  const frontmatterData = parsed.data;
2867
3041
  const ruleType = frontmatterData.type || "manual";
2868
3042
  const description = frontmatterData.description || "";
2869
3043
  const tags = Array.isArray(frontmatterData.tags) ? frontmatterData.tags : void 0;
2870
3044
  const isRoot = ruleType === "always";
2871
- const filename = basename2(file, file.endsWith(".mdc") ? ".mdc" : ".md");
3045
+ const filename = basename3(file, file.endsWith(".mdc") ? ".mdc" : ".md");
2872
3046
  const frontmatter = {
2873
3047
  root: isRoot,
2874
3048
  targets: [config.targetName],
@@ -2926,8 +3100,8 @@ async function parseAugmentGuidelines(guidelinesPath, config) {
2926
3100
  }
2927
3101
 
2928
3102
  // src/parsers/shared-helpers.ts
2929
- import { basename as basename3, join as join14 } from "path";
2930
- import matter3 from "gray-matter";
3103
+ import { basename as basename4, join as join17 } from "path";
3104
+ import matter4 from "gray-matter";
2931
3105
  async function parseConfigurationFiles(baseDir = process.cwd(), config) {
2932
3106
  const errors = [];
2933
3107
  const rules = [];
@@ -2940,7 +3114,7 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
2940
3114
  let content;
2941
3115
  let frontmatter;
2942
3116
  if (mainFile.useFrontmatter) {
2943
- const parsed = matter3(rawContent);
3117
+ const parsed = matter4(rawContent);
2944
3118
  content = parsed.content.trim();
2945
3119
  const parsedFrontmatter = parsed.data;
2946
3120
  frontmatter = {
@@ -2982,14 +3156,14 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
2982
3156
  const files = await readdir2(dirPath);
2983
3157
  for (const file of files) {
2984
3158
  if (file.endsWith(dirConfig.filePattern)) {
2985
- const filePath = join14(dirPath, file);
3159
+ const filePath = join17(dirPath, file);
2986
3160
  const fileResult = await safeAsyncOperation(async () => {
2987
3161
  const rawContent = await readFileContent(filePath);
2988
3162
  let content;
2989
3163
  let frontmatter;
2990
3164
  const filename = file.replace(new RegExp(`\\${dirConfig.filePattern}$`), "");
2991
3165
  if (dirConfig.filePattern === ".instructions.md") {
2992
- const parsed = matter3(rawContent);
3166
+ const parsed = matter4(rawContent);
2993
3167
  content = parsed.content.trim();
2994
3168
  const parsedFrontmatter = parsed.data;
2995
3169
  frontmatter = {
@@ -3055,6 +3229,13 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
3055
3229
  const memoryRules = await parseMemoryFiles(memoryDir, config);
3056
3230
  rules.push(...memoryRules);
3057
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
+ }
3058
3239
  const settingsPath = resolvePath(config.settingsPath, baseDir);
3059
3240
  if (await fileExists(settingsPath)) {
3060
3241
  const settingsResult = await parseSettingsFile(settingsPath, config.tool);
@@ -3120,10 +3301,10 @@ async function parseMemoryFiles(memoryDir, config) {
3120
3301
  const files = await readdir2(memoryDir);
3121
3302
  for (const file of files) {
3122
3303
  if (file.endsWith(".md")) {
3123
- const filePath = join14(memoryDir, file);
3304
+ const filePath = join17(memoryDir, file);
3124
3305
  const content = await readFileContent(filePath);
3125
3306
  if (content.trim()) {
3126
- const filename = basename3(file, ".md");
3307
+ const filename = basename4(file, ".md");
3127
3308
  const frontmatter = {
3128
3309
  root: false,
3129
3310
  targets: [config.tool],
@@ -3143,6 +3324,54 @@ async function parseMemoryFiles(memoryDir, config) {
3143
3324
  }
3144
3325
  return rules;
3145
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
+ }
3146
3375
  async function parseSettingsFile(settingsPath, tool) {
3147
3376
  const errors = [];
3148
3377
  let ignorePatterns;
@@ -3190,7 +3419,8 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
3190
3419
  settingsPath: ".claude/settings.json",
3191
3420
  mainDescription: "Main Claude Code configuration",
3192
3421
  memoryDescription: "Memory file",
3193
- filenamePrefix: "claude"
3422
+ filenamePrefix: "claude",
3423
+ commandsDirPath: ".claude/commands"
3194
3424
  });
3195
3425
  }
3196
3426
 
@@ -3215,7 +3445,7 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
3215
3445
  }
3216
3446
 
3217
3447
  // src/parsers/codexcli.ts
3218
- import { join as join15 } from "path";
3448
+ import { join as join18 } from "path";
3219
3449
 
3220
3450
  // src/parsers/copilot.ts
3221
3451
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
@@ -3238,10 +3468,10 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
3238
3468
  }
3239
3469
 
3240
3470
  // src/parsers/cursor.ts
3241
- import { basename as basename4, join as join16 } from "path";
3242
- import matter4 from "gray-matter";
3471
+ import { basename as basename5, join as join19 } from "path";
3472
+ import matter5 from "gray-matter";
3243
3473
  import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
3244
- import { z as z6 } from "zod/mini";
3474
+ import { z as z7 } from "zod/mini";
3245
3475
  var customMatterOptions = {
3246
3476
  engines: {
3247
3477
  yaml: {
@@ -3269,7 +3499,7 @@ var customMatterOptions = {
3269
3499
  }
3270
3500
  };
3271
3501
  function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
3272
- const FrontmatterSchema = z6.record(z6.string(), z6.unknown());
3502
+ const FrontmatterSchema = z7.record(z7.string(), z7.unknown());
3273
3503
  const parseResult = FrontmatterSchema.safeParse(cursorFrontmatter);
3274
3504
  if (!parseResult.success) {
3275
3505
  return {
@@ -3363,11 +3593,11 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3363
3593
  const rules = [];
3364
3594
  let ignorePatterns;
3365
3595
  let mcpServers;
3366
- const cursorFilePath = join16(baseDir, ".cursorrules");
3596
+ const cursorFilePath = join19(baseDir, ".cursorrules");
3367
3597
  if (await fileExists(cursorFilePath)) {
3368
3598
  try {
3369
3599
  const rawContent = await readFileContent(cursorFilePath);
3370
- const parsed = matter4(rawContent, customMatterOptions);
3600
+ const parsed = matter5(rawContent, customMatterOptions);
3371
3601
  const content = parsed.content.trim();
3372
3602
  if (content) {
3373
3603
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, "cursorrules");
@@ -3384,20 +3614,20 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3384
3614
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
3385
3615
  }
3386
3616
  }
3387
- const cursorRulesDir = join16(baseDir, ".cursor", "rules");
3617
+ const cursorRulesDir = join19(baseDir, ".cursor", "rules");
3388
3618
  if (await fileExists(cursorRulesDir)) {
3389
3619
  try {
3390
3620
  const { readdir: readdir2 } = await import("fs/promises");
3391
3621
  const files = await readdir2(cursorRulesDir);
3392
3622
  for (const file of files) {
3393
3623
  if (file.endsWith(".mdc")) {
3394
- const filePath = join16(cursorRulesDir, file);
3624
+ const filePath = join19(cursorRulesDir, file);
3395
3625
  try {
3396
3626
  const rawContent = await readFileContent(filePath);
3397
- const parsed = matter4(rawContent, customMatterOptions);
3627
+ const parsed = matter5(rawContent, customMatterOptions);
3398
3628
  const content = parsed.content.trim();
3399
3629
  if (content) {
3400
- const filename = basename4(file, ".mdc");
3630
+ const filename = basename5(file, ".mdc");
3401
3631
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, filename);
3402
3632
  rules.push({
3403
3633
  frontmatter,
@@ -3420,7 +3650,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3420
3650
  if (rules.length === 0) {
3421
3651
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
3422
3652
  }
3423
- const cursorIgnorePath = join16(baseDir, ".cursorignore");
3653
+ const cursorIgnorePath = join19(baseDir, ".cursorignore");
3424
3654
  if (await fileExists(cursorIgnorePath)) {
3425
3655
  try {
3426
3656
  const content = await readFileContent(cursorIgnorePath);
@@ -3433,7 +3663,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3433
3663
  errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
3434
3664
  }
3435
3665
  }
3436
- const cursorMcpPath = join16(baseDir, ".cursor", "mcp.json");
3666
+ const cursorMcpPath = join19(baseDir, ".cursor", "mcp.json");
3437
3667
  if (await fileExists(cursorMcpPath)) {
3438
3668
  try {
3439
3669
  const content = await readFileContent(cursorMcpPath);
@@ -3477,16 +3707,17 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
3477
3707
  additionalIgnoreFile: {
3478
3708
  path: ".aiexclude",
3479
3709
  parser: parseAiexclude
3480
- }
3710
+ },
3711
+ commandsDirPath: ".gemini/commands"
3481
3712
  });
3482
3713
  }
3483
3714
 
3484
3715
  // src/parsers/junie.ts
3485
- import { join as join17 } from "path";
3716
+ import { join as join20 } from "path";
3486
3717
  async function parseJunieConfiguration(baseDir = process.cwd()) {
3487
3718
  const errors = [];
3488
3719
  const rules = [];
3489
- const guidelinesPath = join17(baseDir, ".junie", "guidelines.md");
3720
+ const guidelinesPath = join20(baseDir, ".junie", "guidelines.md");
3490
3721
  if (!await fileExists(guidelinesPath)) {
3491
3722
  errors.push(".junie/guidelines.md file not found");
3492
3723
  return { rules, errors };
@@ -3539,8 +3770,8 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
3539
3770
 
3540
3771
  // src/parsers/windsurf.ts
3541
3772
  import { readFile as readFile2 } from "fs/promises";
3542
- import { join as join18 } from "path";
3543
- import matter5 from "gray-matter";
3773
+ import { join as join21 } from "path";
3774
+ import matter6 from "gray-matter";
3544
3775
 
3545
3776
  // src/core/importer.ts
3546
3777
  async function importConfiguration(options) {
@@ -3626,7 +3857,7 @@ async function importConfiguration(options) {
3626
3857
  if (rules.length === 0 && !ignorePatterns && !mcpServers) {
3627
3858
  return { success: false, rulesCreated: 0, errors };
3628
3859
  }
3629
- const rulesDirPath = join19(baseDir, rulesDir);
3860
+ const rulesDirPath = join22(baseDir, rulesDir);
3630
3861
  try {
3631
3862
  const { mkdir: mkdir3 } = await import("fs/promises");
3632
3863
  await mkdir3(rulesDirPath, { recursive: true });
@@ -3639,8 +3870,13 @@ async function importConfiguration(options) {
3639
3870
  for (const rule of rules) {
3640
3871
  try {
3641
3872
  const baseFilename = rule.filename;
3642
- const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
3643
- 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`);
3644
3880
  const content = generateRuleFileContent(rule);
3645
3881
  await writeFileContent(filePath, content);
3646
3882
  rulesCreated++;
@@ -3655,7 +3891,7 @@ async function importConfiguration(options) {
3655
3891
  let ignoreFileCreated = false;
3656
3892
  if (ignorePatterns && ignorePatterns.length > 0) {
3657
3893
  try {
3658
- const rulesyncignorePath = join19(baseDir, ".rulesyncignore");
3894
+ const rulesyncignorePath = join22(baseDir, ".rulesyncignore");
3659
3895
  const ignoreContent = `${ignorePatterns.join("\n")}
3660
3896
  `;
3661
3897
  await writeFileContent(rulesyncignorePath, ignoreContent);
@@ -3671,7 +3907,7 @@ async function importConfiguration(options) {
3671
3907
  let mcpFileCreated = false;
3672
3908
  if (mcpServers && Object.keys(mcpServers).length > 0) {
3673
3909
  try {
3674
- const mcpPath = join19(baseDir, rulesDir, ".mcp.json");
3910
+ const mcpPath = join22(baseDir, rulesDir, ".mcp.json");
3675
3911
  const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
3676
3912
  `;
3677
3913
  await writeFileContent(mcpPath, mcpContent);
@@ -3693,17 +3929,16 @@ async function importConfiguration(options) {
3693
3929
  };
3694
3930
  }
3695
3931
  function generateRuleFileContent(rule) {
3696
- const frontmatter = matter6.stringify("", rule.frontmatter);
3697
- return frontmatter + rule.content;
3698
- }
3699
- async function generateUniqueFilename(rulesDir, baseFilename) {
3700
- let filename = baseFilename;
3701
- let counter = 1;
3702
- while (await fileExists(join19(rulesDir, `${filename}.md`))) {
3703
- filename = `${baseFilename}-${counter}`;
3704
- 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;
3705
3939
  }
3706
- return filename;
3940
+ const frontmatter = matter7.stringify("", rule.frontmatter);
3941
+ return frontmatter + rule.content;
3707
3942
  }
3708
3943
 
3709
3944
  // src/cli/commands/import.ts
@@ -3766,7 +4001,7 @@ async function importCommand(options = {}) {
3766
4001
  }
3767
4002
 
3768
4003
  // src/cli/commands/init.ts
3769
- import { join as join20 } from "path";
4004
+ import { join as join23 } from "path";
3770
4005
  async function initCommand() {
3771
4006
  const aiRulesDir = ".rulesync";
3772
4007
  console.log("Initializing rulesync...");
@@ -3813,7 +4048,7 @@ globs: ["**/*"]
3813
4048
  - Follow single responsibility principle
3814
4049
  `
3815
4050
  };
3816
- const filepath = join20(aiRulesDir, sampleFile.filename);
4051
+ const filepath = join23(aiRulesDir, sampleFile.filename);
3817
4052
  if (!await fileExists(filepath)) {
3818
4053
  await writeFileContent(filepath, sampleFile.content);
3819
4054
  console.log(`Created ${filepath}`);
@@ -3957,7 +4192,7 @@ async function watchCommand() {
3957
4192
 
3958
4193
  // src/cli/index.ts
3959
4194
  var program = new Command();
3960
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.57.0");
4195
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.58.0");
3961
4196
  program.command("init").description("Initialize rulesync in current directory").action(initCommand);
3962
4197
  program.command("add <filename>").description("Add a new rule file").action(addCommand);
3963
4198
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);