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.cjs CHANGED
@@ -771,132 +771,141 @@ var ClaudeSettingsSchema = import_mini2.z.looseObject({
771
771
  )
772
772
  });
773
773
 
774
- // src/types/config.ts
774
+ // src/types/commands.ts
775
775
  var import_mini3 = require("zod/mini");
776
+ var CommandFrontmatterSchema = import_mini3.z.object({
777
+ description: import_mini3.z.optional(import_mini3.z.string())
778
+ });
779
+
780
+ // src/types/config.ts
781
+ var import_mini4 = require("zod/mini");
776
782
  init_tool_targets();
777
- var ConfigSchema = import_mini3.z.object({
778
- aiRulesDir: import_mini3.z.string(),
779
- outputPaths: import_mini3.z.record(ToolTargetSchema, import_mini3.z.string()),
780
- watchEnabled: import_mini3.z.boolean(),
781
- defaultTargets: ToolTargetsSchema
783
+ var ConfigSchema = import_mini4.z.object({
784
+ aiRulesDir: import_mini4.z.string(),
785
+ outputPaths: import_mini4.z.record(ToolTargetSchema, import_mini4.z.string()),
786
+ watchEnabled: import_mini4.z.boolean(),
787
+ defaultTargets: ToolTargetsSchema,
788
+ claudecodeCommands: import_mini4.z.optional(import_mini4.z.string()),
789
+ geminicliCommands: import_mini4.z.optional(import_mini4.z.string())
782
790
  });
783
791
 
784
792
  // src/types/config-options.ts
785
- var import_mini4 = require("zod/mini");
793
+ var import_mini5 = require("zod/mini");
786
794
  init_tool_targets();
787
- var OutputPathsSchema = import_mini4.z.object({
788
- augmentcode: import_mini4.z.optional(import_mini4.z.string()),
789
- "augmentcode-legacy": import_mini4.z.optional(import_mini4.z.string()),
790
- copilot: import_mini4.z.optional(import_mini4.z.string()),
791
- cursor: import_mini4.z.optional(import_mini4.z.string()),
792
- cline: import_mini4.z.optional(import_mini4.z.string()),
793
- claudecode: import_mini4.z.optional(import_mini4.z.string()),
794
- codexcli: import_mini4.z.optional(import_mini4.z.string()),
795
- roo: import_mini4.z.optional(import_mini4.z.string()),
796
- geminicli: import_mini4.z.optional(import_mini4.z.string()),
797
- kiro: import_mini4.z.optional(import_mini4.z.string()),
798
- junie: import_mini4.z.optional(import_mini4.z.string()),
799
- windsurf: import_mini4.z.optional(import_mini4.z.string())
795
+ var OutputPathsSchema = import_mini5.z.object({
796
+ augmentcode: import_mini5.z.optional(import_mini5.z.string()),
797
+ "augmentcode-legacy": import_mini5.z.optional(import_mini5.z.string()),
798
+ copilot: import_mini5.z.optional(import_mini5.z.string()),
799
+ cursor: import_mini5.z.optional(import_mini5.z.string()),
800
+ cline: import_mini5.z.optional(import_mini5.z.string()),
801
+ claudecode: import_mini5.z.optional(import_mini5.z.string()),
802
+ codexcli: import_mini5.z.optional(import_mini5.z.string()),
803
+ roo: import_mini5.z.optional(import_mini5.z.string()),
804
+ geminicli: import_mini5.z.optional(import_mini5.z.string()),
805
+ kiro: import_mini5.z.optional(import_mini5.z.string()),
806
+ junie: import_mini5.z.optional(import_mini5.z.string()),
807
+ windsurf: import_mini5.z.optional(import_mini5.z.string())
800
808
  });
801
- var ConfigOptionsSchema = import_mini4.z.object({
802
- aiRulesDir: import_mini4.z.optional(import_mini4.z.string()),
803
- outputPaths: import_mini4.z.optional(OutputPathsSchema),
804
- watchEnabled: import_mini4.z.optional(import_mini4.z.boolean()),
805
- defaultTargets: import_mini4.z.optional(ToolTargetsSchema),
806
- targets: import_mini4.z.optional(import_mini4.z.array(ToolTargetSchema)),
807
- exclude: import_mini4.z.optional(import_mini4.z.array(ToolTargetSchema)),
808
- verbose: import_mini4.z.optional(import_mini4.z.boolean()),
809
- delete: import_mini4.z.optional(import_mini4.z.boolean()),
810
- baseDir: import_mini4.z.optional(import_mini4.z.union([import_mini4.z.string(), import_mini4.z.array(import_mini4.z.string())])),
811
- watch: import_mini4.z.optional(
812
- import_mini4.z.object({
813
- enabled: import_mini4.z.optional(import_mini4.z.boolean()),
814
- interval: import_mini4.z.optional(import_mini4.z.number()),
815
- ignore: import_mini4.z.optional(import_mini4.z.array(import_mini4.z.string()))
809
+ var ConfigOptionsSchema = import_mini5.z.object({
810
+ aiRulesDir: import_mini5.z.optional(import_mini5.z.string()),
811
+ outputPaths: import_mini5.z.optional(OutputPathsSchema),
812
+ watchEnabled: import_mini5.z.optional(import_mini5.z.boolean()),
813
+ defaultTargets: import_mini5.z.optional(ToolTargetsSchema),
814
+ targets: import_mini5.z.optional(import_mini5.z.array(ToolTargetSchema)),
815
+ exclude: import_mini5.z.optional(import_mini5.z.array(ToolTargetSchema)),
816
+ verbose: import_mini5.z.optional(import_mini5.z.boolean()),
817
+ delete: import_mini5.z.optional(import_mini5.z.boolean()),
818
+ baseDir: import_mini5.z.optional(import_mini5.z.union([import_mini5.z.string(), import_mini5.z.array(import_mini5.z.string())])),
819
+ watch: import_mini5.z.optional(
820
+ import_mini5.z.object({
821
+ enabled: import_mini5.z.optional(import_mini5.z.boolean()),
822
+ interval: import_mini5.z.optional(import_mini5.z.number()),
823
+ ignore: import_mini5.z.optional(import_mini5.z.array(import_mini5.z.string()))
816
824
  })
817
825
  )
818
826
  });
819
- var MergedConfigSchema = import_mini4.z.object({
820
- aiRulesDir: import_mini4.z.string(),
821
- outputPaths: import_mini4.z.record(ToolTargetSchema, import_mini4.z.string()),
822
- watchEnabled: import_mini4.z.boolean(),
827
+ var MergedConfigSchema = import_mini5.z.object({
828
+ aiRulesDir: import_mini5.z.string(),
829
+ outputPaths: import_mini5.z.record(ToolTargetSchema, import_mini5.z.string()),
830
+ watchEnabled: import_mini5.z.boolean(),
823
831
  defaultTargets: ToolTargetsSchema,
824
- targets: import_mini4.z.optional(import_mini4.z.array(ToolTargetSchema)),
825
- exclude: import_mini4.z.optional(import_mini4.z.array(ToolTargetSchema)),
826
- verbose: import_mini4.z.optional(import_mini4.z.boolean()),
827
- delete: import_mini4.z.optional(import_mini4.z.boolean()),
828
- baseDir: import_mini4.z.optional(import_mini4.z.union([import_mini4.z.string(), import_mini4.z.array(import_mini4.z.string())])),
829
- configPath: import_mini4.z.optional(import_mini4.z.string()),
830
- watch: import_mini4.z.optional(
831
- import_mini4.z.object({
832
- enabled: import_mini4.z.optional(import_mini4.z.boolean()),
833
- interval: import_mini4.z.optional(import_mini4.z.number()),
834
- ignore: import_mini4.z.optional(import_mini4.z.array(import_mini4.z.string()))
832
+ targets: import_mini5.z.optional(import_mini5.z.array(ToolTargetSchema)),
833
+ exclude: import_mini5.z.optional(import_mini5.z.array(ToolTargetSchema)),
834
+ verbose: import_mini5.z.optional(import_mini5.z.boolean()),
835
+ delete: import_mini5.z.optional(import_mini5.z.boolean()),
836
+ baseDir: import_mini5.z.optional(import_mini5.z.union([import_mini5.z.string(), import_mini5.z.array(import_mini5.z.string())])),
837
+ configPath: import_mini5.z.optional(import_mini5.z.string()),
838
+ watch: import_mini5.z.optional(
839
+ import_mini5.z.object({
840
+ enabled: import_mini5.z.optional(import_mini5.z.boolean()),
841
+ interval: import_mini5.z.optional(import_mini5.z.number()),
842
+ ignore: import_mini5.z.optional(import_mini5.z.array(import_mini5.z.string()))
835
843
  })
836
844
  )
837
845
  });
838
846
 
839
847
  // src/types/mcp.ts
840
- var import_mini5 = require("zod/mini");
848
+ var import_mini6 = require("zod/mini");
841
849
  init_tool_targets();
842
- var McpTransportTypeSchema = import_mini5.z.enum(["stdio", "sse", "http"]);
843
- var McpServerBaseSchema = import_mini5.z.object({
844
- command: import_mini5.z.optional(import_mini5.z.string()),
845
- args: import_mini5.z.optional(import_mini5.z.array(import_mini5.z.string())),
846
- url: import_mini5.z.optional(import_mini5.z.string()),
847
- httpUrl: import_mini5.z.optional(import_mini5.z.string()),
848
- env: import_mini5.z.optional(import_mini5.z.record(import_mini5.z.string(), import_mini5.z.string())),
849
- disabled: import_mini5.z.optional(import_mini5.z.boolean()),
850
- networkTimeout: import_mini5.z.optional(import_mini5.z.number()),
851
- timeout: import_mini5.z.optional(import_mini5.z.number()),
852
- trust: import_mini5.z.optional(import_mini5.z.boolean()),
853
- cwd: import_mini5.z.optional(import_mini5.z.string()),
854
- transport: import_mini5.z.optional(McpTransportTypeSchema),
855
- type: import_mini5.z.optional(import_mini5.z.enum(["sse", "streamable-http"])),
856
- alwaysAllow: import_mini5.z.optional(import_mini5.z.array(import_mini5.z.string())),
857
- tools: import_mini5.z.optional(import_mini5.z.array(import_mini5.z.string())),
858
- kiroAutoApprove: import_mini5.z.optional(import_mini5.z.array(import_mini5.z.string())),
859
- kiroAutoBlock: import_mini5.z.optional(import_mini5.z.array(import_mini5.z.string())),
860
- headers: import_mini5.z.optional(import_mini5.z.record(import_mini5.z.string(), import_mini5.z.string()))
850
+ var McpTransportTypeSchema = import_mini6.z.enum(["stdio", "sse", "http"]);
851
+ var McpServerBaseSchema = import_mini6.z.object({
852
+ command: import_mini6.z.optional(import_mini6.z.string()),
853
+ args: import_mini6.z.optional(import_mini6.z.array(import_mini6.z.string())),
854
+ url: import_mini6.z.optional(import_mini6.z.string()),
855
+ httpUrl: import_mini6.z.optional(import_mini6.z.string()),
856
+ env: import_mini6.z.optional(import_mini6.z.record(import_mini6.z.string(), import_mini6.z.string())),
857
+ disabled: import_mini6.z.optional(import_mini6.z.boolean()),
858
+ networkTimeout: import_mini6.z.optional(import_mini6.z.number()),
859
+ timeout: import_mini6.z.optional(import_mini6.z.number()),
860
+ trust: import_mini6.z.optional(import_mini6.z.boolean()),
861
+ cwd: import_mini6.z.optional(import_mini6.z.string()),
862
+ transport: import_mini6.z.optional(McpTransportTypeSchema),
863
+ type: import_mini6.z.optional(import_mini6.z.enum(["sse", "streamable-http"])),
864
+ alwaysAllow: import_mini6.z.optional(import_mini6.z.array(import_mini6.z.string())),
865
+ tools: import_mini6.z.optional(import_mini6.z.array(import_mini6.z.string())),
866
+ kiroAutoApprove: import_mini6.z.optional(import_mini6.z.array(import_mini6.z.string())),
867
+ kiroAutoBlock: import_mini6.z.optional(import_mini6.z.array(import_mini6.z.string())),
868
+ headers: import_mini6.z.optional(import_mini6.z.record(import_mini6.z.string(), import_mini6.z.string()))
861
869
  });
862
- var RulesyncMcpServerSchema = import_mini5.z.extend(McpServerBaseSchema, {
863
- targets: import_mini5.z.optional(RulesyncTargetsSchema)
870
+ var RulesyncMcpServerSchema = import_mini6.z.extend(McpServerBaseSchema, {
871
+ targets: import_mini6.z.optional(RulesyncTargetsSchema)
864
872
  });
865
- var McpConfigSchema = import_mini5.z.object({
866
- mcpServers: import_mini5.z.record(import_mini5.z.string(), McpServerBaseSchema)
873
+ var McpConfigSchema = import_mini6.z.object({
874
+ mcpServers: import_mini6.z.record(import_mini6.z.string(), McpServerBaseSchema)
867
875
  });
868
- var RulesyncMcpConfigSchema = import_mini5.z.object({
869
- mcpServers: import_mini5.z.record(import_mini5.z.string(), RulesyncMcpServerSchema)
876
+ var RulesyncMcpConfigSchema = import_mini6.z.object({
877
+ mcpServers: import_mini6.z.record(import_mini6.z.string(), RulesyncMcpServerSchema)
870
878
  });
871
879
 
872
880
  // src/types/rules.ts
873
- var import_mini6 = require("zod/mini");
881
+ var import_mini7 = require("zod/mini");
874
882
  init_tool_targets();
875
- var RuleFrontmatterSchema = import_mini6.z.object({
876
- root: import_mini6.z.boolean(),
877
- targets: RulesyncTargetsSchema,
878
- description: import_mini6.z.string(),
879
- globs: import_mini6.z.array(import_mini6.z.string()),
880
- cursorRuleType: import_mini6.z.optional(import_mini6.z.enum(["always", "manual", "specificFiles", "intelligently"])),
881
- windsurfActivationMode: import_mini6.z.optional(import_mini6.z.enum(["always", "manual", "model-decision", "glob"])),
882
- windsurfOutputFormat: import_mini6.z.optional(import_mini6.z.enum(["single-file", "directory"])),
883
- tags: import_mini6.z.optional(import_mini6.z.array(import_mini6.z.string()))
883
+ var RuleFrontmatterSchema = import_mini7.z.object({
884
+ root: import_mini7.z.optional(import_mini7.z.boolean()),
885
+ targets: import_mini7.z.optional(RulesyncTargetsSchema),
886
+ description: import_mini7.z.optional(import_mini7.z.string()),
887
+ globs: import_mini7.z.optional(import_mini7.z.array(import_mini7.z.string())),
888
+ cursorRuleType: import_mini7.z.optional(import_mini7.z.enum(["always", "manual", "specificFiles", "intelligently"])),
889
+ windsurfActivationMode: import_mini7.z.optional(import_mini7.z.enum(["always", "manual", "model-decision", "glob"])),
890
+ windsurfOutputFormat: import_mini7.z.optional(import_mini7.z.enum(["single-file", "directory"])),
891
+ tags: import_mini7.z.optional(import_mini7.z.array(import_mini7.z.string()))
884
892
  });
885
- var ParsedRuleSchema = import_mini6.z.object({
893
+ var ParsedRuleSchema = import_mini7.z.object({
886
894
  frontmatter: RuleFrontmatterSchema,
887
- content: import_mini6.z.string(),
888
- filename: import_mini6.z.string(),
889
- filepath: import_mini6.z.string()
895
+ content: import_mini7.z.string(),
896
+ filename: import_mini7.z.string(),
897
+ filepath: import_mini7.z.string(),
898
+ type: import_mini7.z.optional(import_mini7.z.enum(["rule", "command"]))
890
899
  });
891
- var GeneratedOutputSchema = import_mini6.z.object({
900
+ var GeneratedOutputSchema = import_mini7.z.object({
892
901
  tool: ToolTargetSchema,
893
- filepath: import_mini6.z.string(),
894
- content: import_mini6.z.string()
902
+ filepath: import_mini7.z.string(),
903
+ content: import_mini7.z.string()
895
904
  });
896
- var GenerateOptionsSchema = import_mini6.z.object({
897
- targetTools: import_mini6.z.optional(ToolTargetsSchema),
898
- outputDir: import_mini6.z.optional(import_mini6.z.string()),
899
- watch: import_mini6.z.optional(import_mini6.z.boolean())
905
+ var GenerateOptionsSchema = import_mini7.z.object({
906
+ targetTools: import_mini7.z.optional(ToolTargetsSchema),
907
+ outputDir: import_mini7.z.optional(import_mini7.z.string()),
908
+ watch: import_mini7.z.optional(import_mini7.z.boolean())
900
909
  });
901
910
 
902
911
  // src/types/index.ts
@@ -1191,28 +1200,6 @@ async function removeClaudeGeneratedFiles() {
1191
1200
  }
1192
1201
  }
1193
1202
 
1194
- // src/utils/rules.ts
1195
- function isToolSpecificRule(rule, targetTool) {
1196
- const filename = rule.filename;
1197
- const toolPatterns = {
1198
- "augmentcode-legacy": /^specification-augmentcode-legacy-/i,
1199
- augmentcode: /^specification-augmentcode-/i,
1200
- copilot: /^specification-copilot-/i,
1201
- cursor: /^specification-cursor-/i,
1202
- cline: /^specification-cline-/i,
1203
- claudecode: /^specification-claudecode-/i,
1204
- roo: /^specification-roo-/i,
1205
- geminicli: /^specification-geminicli-/i,
1206
- kiro: /^specification-kiro-/i
1207
- };
1208
- for (const [tool, pattern] of Object.entries(toolPatterns)) {
1209
- if (pattern.test(filename)) {
1210
- return tool === targetTool;
1211
- }
1212
- }
1213
- return true;
1214
- }
1215
-
1216
1203
  // src/cli/commands/config.ts
1217
1204
  async function configCommand(options = {}) {
1218
1205
  if (options.init) {
@@ -1424,10 +1411,165 @@ export default config;
1424
1411
  }
1425
1412
 
1426
1413
  // src/cli/commands/generate.ts
1427
- var import_node_path10 = require("path");
1414
+ var import_node_path14 = require("path");
1428
1415
 
1429
- // src/generators/ignore/shared-factory.ts
1416
+ // src/core/command-generator.ts
1417
+ var import_node_path6 = require("path");
1418
+
1419
+ // src/generators/commands/claudecode.ts
1430
1420
  var import_node_path3 = require("path");
1421
+ var ClaudeCodeCommandGenerator = class {
1422
+ generate(command, outputDir) {
1423
+ const filepath = this.getOutputPath(command.filename, outputDir);
1424
+ const frontmatter = ["---"];
1425
+ if (command.frontmatter.description) {
1426
+ frontmatter.push(`description: ${command.frontmatter.description}`);
1427
+ }
1428
+ frontmatter.push("---");
1429
+ const content = `${frontmatter.join("\n")}
1430
+
1431
+ ${command.content.trim()}
1432
+ `;
1433
+ return {
1434
+ tool: "claudecode",
1435
+ filepath,
1436
+ content
1437
+ };
1438
+ }
1439
+ getOutputPath(filename, baseDir) {
1440
+ const flattenedName = filename.replace(/\//g, "-");
1441
+ return (0, import_node_path3.join)(baseDir, ".claude", "commands", `${flattenedName}.md`);
1442
+ }
1443
+ };
1444
+
1445
+ // src/generators/commands/geminicli.ts
1446
+ var import_node_path4 = require("path");
1447
+ var GeminiCliCommandGenerator = class {
1448
+ generate(command, outputDir) {
1449
+ const filepath = this.getOutputPath(command.filename, outputDir);
1450
+ const convertedContent = this.convertSyntax(command.content);
1451
+ const tomlLines = [];
1452
+ if (command.frontmatter.description) {
1453
+ tomlLines.push(`description = "${this.escapeTomlString(command.frontmatter.description)}"`);
1454
+ tomlLines.push("");
1455
+ }
1456
+ tomlLines.push(`prompt = """${convertedContent}"""`);
1457
+ const content = tomlLines.join("\n") + "\n";
1458
+ return {
1459
+ tool: "geminicli",
1460
+ filepath,
1461
+ content
1462
+ };
1463
+ }
1464
+ getOutputPath(filename, baseDir) {
1465
+ const tomlFilename = filename.replace(/\.md$/, ".toml");
1466
+ const filenameWithExt = tomlFilename.endsWith(".toml") ? tomlFilename : `${tomlFilename}.toml`;
1467
+ return (0, import_node_path4.join)(baseDir, ".gemini", "commands", filenameWithExt);
1468
+ }
1469
+ convertSyntax(content) {
1470
+ let converted = content;
1471
+ converted = converted.replace(/\$ARGUMENTS/g, "{{args}}");
1472
+ converted = converted.replace(/!`([^`]+)`/g, "!{$1}");
1473
+ const atSyntaxMatches = converted.match(/@[^\s]+/g);
1474
+ if (atSyntaxMatches) {
1475
+ console.warn(
1476
+ `\u26A0\uFE0F Warning: @ syntax found (${atSyntaxMatches.join(", ")}). Gemini CLI does not support file content injection. Consider using shell commands or remove these references.`
1477
+ );
1478
+ }
1479
+ return converted.trim();
1480
+ }
1481
+ escapeTomlString(str) {
1482
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
1483
+ }
1484
+ };
1485
+
1486
+ // src/generators/commands/index.ts
1487
+ var commandGenerators = {
1488
+ claudecode: new ClaudeCodeCommandGenerator(),
1489
+ geminicli: new GeminiCliCommandGenerator()
1490
+ };
1491
+ function getCommandGenerator(tool) {
1492
+ return commandGenerators[tool];
1493
+ }
1494
+
1495
+ // src/core/command-parser.ts
1496
+ var import_node_path5 = require("path");
1497
+ var import_gray_matter = __toESM(require("gray-matter"), 1);
1498
+ async function parseCommandsFromDirectory(commandsDir) {
1499
+ const commandFiles = await findFiles(commandsDir, ".md");
1500
+ const commands = [];
1501
+ const errors = [];
1502
+ for (const filepath of commandFiles) {
1503
+ try {
1504
+ const command = await parseCommandFile(filepath);
1505
+ commands.push(command);
1506
+ } catch (error) {
1507
+ const errorMessage = error instanceof Error ? error.message : String(error);
1508
+ errors.push(`Failed to parse command file ${filepath}: ${errorMessage}`);
1509
+ }
1510
+ }
1511
+ if (errors.length > 0) {
1512
+ console.warn(`\u26A0\uFE0F Command parsing errors:
1513
+ ${errors.join("\n")}`);
1514
+ }
1515
+ return commands;
1516
+ }
1517
+ async function parseCommandFile(filepath) {
1518
+ const content = await readFileContent(filepath);
1519
+ const parsed = (0, import_gray_matter.default)(content);
1520
+ try {
1521
+ const validatedData = CommandFrontmatterSchema.parse(parsed.data);
1522
+ const filename = (0, import_node_path5.basename)(filepath, ".md");
1523
+ return {
1524
+ frontmatter: {
1525
+ description: validatedData.description
1526
+ },
1527
+ content: parsed.content,
1528
+ filename,
1529
+ filepath
1530
+ };
1531
+ } catch (error) {
1532
+ throw new Error(
1533
+ `Invalid frontmatter in ${filepath}: ${error instanceof Error ? error.message : String(error)}`
1534
+ );
1535
+ }
1536
+ }
1537
+
1538
+ // src/core/command-generator.ts
1539
+ async function generateCommands(projectRoot, baseDir, targets) {
1540
+ const commandsDir = (0, import_node_path6.join)(projectRoot, ".rulesync", "commands");
1541
+ if (!await fileExists(commandsDir)) {
1542
+ return [];
1543
+ }
1544
+ const commands = await parseCommandsFromDirectory(commandsDir);
1545
+ if (commands.length === 0) {
1546
+ return [];
1547
+ }
1548
+ const outputs = [];
1549
+ const outputDir = baseDir || projectRoot;
1550
+ const supportedTargets = targets.filter((target) => ["claudecode", "geminicli"].includes(target));
1551
+ for (const target of supportedTargets) {
1552
+ const generator = getCommandGenerator(target);
1553
+ if (!generator) {
1554
+ continue;
1555
+ }
1556
+ for (const command of commands) {
1557
+ try {
1558
+ const output = generator.generate(command, outputDir);
1559
+ outputs.push(output);
1560
+ } catch (error) {
1561
+ const errorMessage = error instanceof Error ? error.message : String(error);
1562
+ console.error(
1563
+ `\u274C Failed to generate ${target} command for ${command.filename}: ${errorMessage}`
1564
+ );
1565
+ }
1566
+ }
1567
+ }
1568
+ return outputs;
1569
+ }
1570
+
1571
+ // src/generators/ignore/shared-factory.ts
1572
+ var import_node_path7 = require("path");
1431
1573
 
1432
1574
  // src/generators/ignore/shared-helpers.ts
1433
1575
  function extractIgnorePatternsFromRules(rules) {
@@ -1545,7 +1687,7 @@ function generateIgnoreFile(rules, config, ignoreConfig, baseDir) {
1545
1687
  const outputs = [];
1546
1688
  const content = generateIgnoreContent(rules, ignoreConfig);
1547
1689
  const outputPath = baseDir || process.cwd();
1548
- const filepath = (0, import_node_path3.join)(outputPath, ignoreConfig.filename);
1690
+ const filepath = (0, import_node_path7.join)(outputPath, ignoreConfig.filename);
1549
1691
  outputs.push({
1550
1692
  tool: ignoreConfig.tool,
1551
1693
  filepath,
@@ -2133,20 +2275,20 @@ function generateWindsurfIgnore(rules, config, baseDir) {
2133
2275
  }
2134
2276
 
2135
2277
  // src/generators/rules/augmentcode.ts
2136
- var import_node_path6 = require("path");
2278
+ var import_node_path10 = require("path");
2137
2279
 
2138
2280
  // src/generators/rules/shared-helpers.ts
2139
- var import_node_path5 = require("path");
2281
+ var import_node_path9 = require("path");
2140
2282
 
2141
2283
  // src/utils/ignore.ts
2142
- var import_node_path4 = require("path");
2284
+ var import_node_path8 = require("path");
2143
2285
  var import_micromatch = __toESM(require("micromatch"), 1);
2144
2286
  var cachedIgnorePatterns = null;
2145
2287
  async function loadIgnorePatterns(baseDir = process.cwd()) {
2146
2288
  if (cachedIgnorePatterns) {
2147
2289
  return cachedIgnorePatterns;
2148
2290
  }
2149
- const ignorePath = (0, import_node_path4.join)(baseDir, ".rulesyncignore");
2291
+ const ignorePath = (0, import_node_path8.join)(baseDir, ".rulesyncignore");
2150
2292
  if (!await fileExists(ignorePath)) {
2151
2293
  cachedIgnorePatterns = { patterns: [] };
2152
2294
  return cachedIgnorePatterns;
@@ -2200,7 +2342,7 @@ function addOutput(outputs, tool, config, baseDir, relativePath, content) {
2200
2342
  const outputDir = resolveOutputDir(config, tool, baseDir);
2201
2343
  outputs.push({
2202
2344
  tool,
2203
- filepath: (0, import_node_path5.join)(outputDir, relativePath),
2345
+ filepath: (0, import_node_path9.join)(outputDir, relativePath),
2204
2346
  content
2205
2347
  });
2206
2348
  }
@@ -2209,7 +2351,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
2209
2351
  for (const rule of rules) {
2210
2352
  const content = generatorConfig.generateContent(rule);
2211
2353
  const outputDir = resolveOutputDir(config, generatorConfig.tool, baseDir);
2212
- const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : (0, import_node_path5.join)(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
2354
+ const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : (0, import_node_path9.join)(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
2213
2355
  outputs.push({
2214
2356
  tool: generatorConfig.tool,
2215
2357
  filepath,
@@ -2217,7 +2359,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
2217
2359
  });
2218
2360
  }
2219
2361
  const ignorePatterns = await loadIgnorePatterns(baseDir);
2220
- if (ignorePatterns.patterns.length > 0) {
2362
+ if (ignorePatterns.patterns.length > 0 && generatorConfig.ignoreFileName) {
2221
2363
  const ignorePath = resolvePath(generatorConfig.ignoreFileName, baseDir);
2222
2364
  const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, generatorConfig.tool);
2223
2365
  outputs.push({
@@ -2237,7 +2379,7 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
2237
2379
  for (const rule of detailRules) {
2238
2380
  const content = generatorConfig.generateDetailContent(rule);
2239
2381
  const filepath = resolvePath(
2240
- (0, import_node_path5.join)(generatorConfig.detailSubDir, `${rule.filename}.md`),
2382
+ (0, import_node_path9.join)(generatorConfig.detailSubDir, `${rule.filename}.md`),
2241
2383
  baseDir
2242
2384
  );
2243
2385
  outputs.push({
@@ -2258,13 +2400,15 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
2258
2400
  }
2259
2401
  const ignorePatterns = await loadIgnorePatterns(baseDir);
2260
2402
  if (ignorePatterns.patterns.length > 0) {
2261
- const ignorePath = resolvePath(generatorConfig.ignoreFileName, baseDir);
2262
- const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, generatorConfig.tool);
2263
- outputs.push({
2264
- tool: generatorConfig.tool,
2265
- filepath: ignorePath,
2266
- content: ignoreContent
2267
- });
2403
+ if (generatorConfig.ignoreFileName) {
2404
+ const ignorePath = resolvePath(generatorConfig.ignoreFileName, baseDir);
2405
+ const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, generatorConfig.tool);
2406
+ outputs.push({
2407
+ tool: generatorConfig.tool,
2408
+ filepath: ignorePath,
2409
+ content: ignoreContent
2410
+ });
2411
+ }
2268
2412
  if (generatorConfig.updateAdditionalConfig) {
2269
2413
  const additionalOutputs = await generatorConfig.updateAdditionalConfig(
2270
2414
  ignorePatterns.patterns,
@@ -2298,7 +2442,7 @@ async function generateAugmentcodeConfig(rules, config, baseDir) {
2298
2442
  "augmentcode",
2299
2443
  config,
2300
2444
  baseDir,
2301
- (0, import_node_path6.join)(".augment", "rules", `${rule.filename}.md`),
2445
+ (0, import_node_path10.join)(".augment", "rules", `${rule.filename}.md`),
2302
2446
  generateRuleFile(rule)
2303
2447
  );
2304
2448
  });
@@ -2351,19 +2495,19 @@ function generateLegacyGuidelinesFile(allRules) {
2351
2495
  }
2352
2496
 
2353
2497
  // src/generators/rules/claudecode.ts
2354
- var import_node_path7 = require("path");
2498
+ var import_node_path11 = require("path");
2355
2499
  async function generateClaudecodeConfig(rules, config, baseDir) {
2356
2500
  const generatorConfig = {
2357
2501
  tool: "claudecode",
2358
2502
  fileExtension: ".md",
2359
- ignoreFileName: ".aiignore",
2503
+ // ignoreFileName omitted - Claude Code uses settings.json permissions.deny instead of ignore files
2360
2504
  generateContent: generateMemoryFile,
2361
2505
  generateRootContent: generateClaudeMarkdown,
2362
2506
  rootFilePath: "CLAUDE.md",
2363
2507
  generateDetailContent: generateMemoryFile,
2364
2508
  detailSubDir: ".claude/memories",
2365
2509
  updateAdditionalConfig: async (ignorePatterns, baseDir2) => {
2366
- const settingsPath = resolvePath((0, import_node_path7.join)(".claude", "settings.json"), baseDir2);
2510
+ const settingsPath = resolvePath((0, import_node_path11.join)(".claude", "settings.json"), baseDir2);
2367
2511
  await updateClaudeSettings(settingsPath, ignorePatterns);
2368
2512
  return [];
2369
2513
  }
@@ -2427,7 +2571,7 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
2427
2571
  }
2428
2572
 
2429
2573
  // src/generators/rules/generator-registry.ts
2430
- var import_node_path8 = require("path");
2574
+ var import_node_path12 = require("path");
2431
2575
  function determineCursorRuleType(frontmatter) {
2432
2576
  if (frontmatter.cursorRuleType) {
2433
2577
  return frontmatter.cursorRuleType;
@@ -2507,7 +2651,7 @@ var GENERATOR_REGISTRY = {
2507
2651
  },
2508
2652
  pathResolver: (rule, outputDir) => {
2509
2653
  const baseFilename = rule.filename.replace(/\.md$/, "");
2510
- return (0, import_node_path8.join)(outputDir, `${baseFilename}.instructions.md`);
2654
+ return (0, import_node_path12.join)(outputDir, `${baseFilename}.instructions.md`);
2511
2655
  }
2512
2656
  },
2513
2657
  cursor: {
@@ -2547,7 +2691,7 @@ var GENERATOR_REGISTRY = {
2547
2691
  return lines.join("\n");
2548
2692
  },
2549
2693
  pathResolver: (rule, outputDir) => {
2550
- return (0, import_node_path8.join)(outputDir, `${rule.filename}.mdc`);
2694
+ return (0, import_node_path12.join)(outputDir, `${rule.filename}.mdc`);
2551
2695
  }
2552
2696
  },
2553
2697
  codexcli: {
@@ -2583,10 +2727,10 @@ var GENERATOR_REGISTRY = {
2583
2727
  pathResolver: (rule, outputDir) => {
2584
2728
  const outputFormat = rule.frontmatter.windsurfOutputFormat || "directory";
2585
2729
  if (outputFormat === "single-file") {
2586
- return (0, import_node_path8.join)(outputDir, ".windsurf-rules");
2730
+ return (0, import_node_path12.join)(outputDir, ".windsurf-rules");
2587
2731
  } else {
2588
- const rulesDir = (0, import_node_path8.join)(outputDir, ".windsurf", "rules");
2589
- return (0, import_node_path8.join)(rulesDir, `${rule.filename}.md`);
2732
+ const rulesDir = (0, import_node_path12.join)(outputDir, ".windsurf", "rules");
2733
+ return (0, import_node_path12.join)(rulesDir, `${rule.filename}.md`);
2590
2734
  }
2591
2735
  }
2592
2736
  },
@@ -2850,7 +2994,7 @@ function filterRulesForTool(rules, tool, config) {
2850
2994
  if (!targets.includes(tool)) {
2851
2995
  return false;
2852
2996
  }
2853
- return isToolSpecificRule(rule, tool);
2997
+ return true;
2854
2998
  });
2855
2999
  }
2856
3000
  async function generateForTool(tool, rules, config, baseDir) {
@@ -2905,8 +3049,8 @@ async function generateForTool(tool, rules, config, baseDir) {
2905
3049
  }
2906
3050
 
2907
3051
  // src/core/parser.ts
2908
- var import_node_path9 = require("path");
2909
- var import_gray_matter = __toESM(require("gray-matter"), 1);
3052
+ var import_node_path13 = require("path");
3053
+ var import_gray_matter2 = __toESM(require("gray-matter"), 1);
2910
3054
  async function parseRulesFromDirectory(aiRulesDir) {
2911
3055
  const ignorePatterns = await loadIgnorePatterns();
2912
3056
  const allRuleFiles = await findFiles(aiRulesDir, ".md");
@@ -2940,10 +3084,26 @@ ${errors.join("\n")}`);
2940
3084
  }
2941
3085
  async function parseRuleFile(filepath) {
2942
3086
  const content = await readFileContent(filepath);
2943
- const parsed = (0, import_gray_matter.default)(content);
3087
+ const parsed = (0, import_gray_matter2.default)(content);
2944
3088
  try {
2945
- const frontmatter = RuleFrontmatterSchema.parse(parsed.data);
2946
- const filename = (0, import_node_path9.basename)(filepath, ".md");
3089
+ const validatedData = RuleFrontmatterSchema.parse(parsed.data);
3090
+ const frontmatter = {
3091
+ root: validatedData.root ?? false,
3092
+ targets: validatedData.targets ?? ["*"],
3093
+ description: validatedData.description ?? "",
3094
+ globs: validatedData.globs ?? [],
3095
+ ...validatedData.cursorRuleType !== void 0 && {
3096
+ cursorRuleType: validatedData.cursorRuleType
3097
+ },
3098
+ ...validatedData.windsurfActivationMode !== void 0 && {
3099
+ windsurfActivationMode: validatedData.windsurfActivationMode
3100
+ },
3101
+ ...validatedData.windsurfOutputFormat !== void 0 && {
3102
+ windsurfOutputFormat: validatedData.windsurfOutputFormat
3103
+ },
3104
+ ...validatedData.tags !== void 0 && { tags: validatedData.tags }
3105
+ };
3106
+ const filename = (0, import_node_path13.basename)(filepath, ".md");
2947
3107
  return {
2948
3108
  frontmatter,
2949
3109
  content: parsed.content,
@@ -3246,12 +3406,12 @@ async function generateCommand(options = {}) {
3246
3406
  for (const tool of targetTools) {
3247
3407
  switch (tool) {
3248
3408
  case "augmentcode":
3249
- deleteTasks.push(removeDirectory((0, import_node_path10.join)(".augment", "rules")));
3250
- deleteTasks.push(removeDirectory((0, import_node_path10.join)(".augment", "ignore")));
3409
+ deleteTasks.push(removeDirectory((0, import_node_path14.join)(".augment", "rules")));
3410
+ deleteTasks.push(removeDirectory((0, import_node_path14.join)(".augment", "ignore")));
3251
3411
  break;
3252
3412
  case "augmentcode-legacy":
3253
3413
  deleteTasks.push(removeClaudeGeneratedFiles());
3254
- deleteTasks.push(removeDirectory((0, import_node_path10.join)(".augment", "ignore")));
3414
+ deleteTasks.push(removeDirectory((0, import_node_path14.join)(".augment", "ignore")));
3255
3415
  break;
3256
3416
  case "copilot":
3257
3417
  deleteTasks.push(removeDirectory(config.outputPaths.copilot));
@@ -3264,12 +3424,14 @@ async function generateCommand(options = {}) {
3264
3424
  break;
3265
3425
  case "claudecode":
3266
3426
  deleteTasks.push(removeClaudeGeneratedFiles());
3427
+ deleteTasks.push(removeDirectory((0, import_node_path14.join)(".claude", "commands")));
3267
3428
  break;
3268
3429
  case "roo":
3269
3430
  deleteTasks.push(removeDirectory(config.outputPaths.roo));
3270
3431
  break;
3271
3432
  case "geminicli":
3272
3433
  deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
3434
+ deleteTasks.push(removeDirectory((0, import_node_path14.join)(".gemini", "commands")));
3273
3435
  break;
3274
3436
  case "kiro":
3275
3437
  deleteTasks.push(removeDirectory(config.outputPaths.kiro));
@@ -3334,11 +3496,37 @@ Generating configurations for base directory: ${baseDir}`);
3334
3496
  }
3335
3497
  }
3336
3498
  }
3337
- const totalGenerated = totalOutputs + totalMcpOutputs;
3499
+ if (config.verbose) {
3500
+ console.log("\nGenerating command files...");
3501
+ }
3502
+ let totalCommandOutputs = 0;
3503
+ for (const baseDir of baseDirs) {
3504
+ const commandResults = await generateCommands(
3505
+ process.cwd(),
3506
+ baseDir === process.cwd() ? void 0 : baseDir,
3507
+ config.defaultTargets
3508
+ );
3509
+ if (commandResults.length === 0) {
3510
+ if (config.verbose) {
3511
+ console.log(`No commands found for ${baseDir}`);
3512
+ }
3513
+ continue;
3514
+ }
3515
+ for (const result of commandResults) {
3516
+ await writeFileContent(result.filepath, result.content);
3517
+ console.log(`\u2705 Generated ${result.tool} command: ${result.filepath}`);
3518
+ totalCommandOutputs++;
3519
+ }
3520
+ }
3521
+ const totalGenerated = totalOutputs + totalMcpOutputs + totalCommandOutputs;
3338
3522
  if (totalGenerated > 0) {
3523
+ const parts = [];
3524
+ if (totalOutputs > 0) parts.push(`${totalOutputs} configurations`);
3525
+ if (totalMcpOutputs > 0) parts.push(`${totalMcpOutputs} MCP configurations`);
3526
+ if (totalCommandOutputs > 0) parts.push(`${totalCommandOutputs} commands`);
3339
3527
  console.log(
3340
3528
  `
3341
- \u{1F389} All done! Generated ${totalGenerated} file(s) total (${totalOutputs} configurations + ${totalMcpOutputs} MCP configurations)`
3529
+ \u{1F389} All done! Generated ${totalGenerated} file(s) total (${parts.join(" + ")})`
3342
3530
  );
3343
3531
  }
3344
3532
  } catch (error) {
@@ -3349,9 +3537,9 @@ Generating configurations for base directory: ${baseDir}`);
3349
3537
 
3350
3538
  // src/cli/commands/gitignore.ts
3351
3539
  var import_node_fs2 = require("fs");
3352
- var import_node_path11 = require("path");
3540
+ var import_node_path15 = require("path");
3353
3541
  var gitignoreCommand = async () => {
3354
- const gitignorePath = (0, import_node_path11.join)(process.cwd(), ".gitignore");
3542
+ const gitignorePath = (0, import_node_path15.join)(process.cwd(), ".gitignore");
3355
3543
  const rulesFilesToIgnore = [
3356
3544
  "# Generated by rulesync - AI tool configuration files",
3357
3545
  "**/.github/copilot-instructions.md",
@@ -3362,6 +3550,7 @@ var gitignoreCommand = async () => {
3362
3550
  "**/.clineignore",
3363
3551
  "**/CLAUDE.md",
3364
3552
  "**/.claude/memories/",
3553
+ "**/.claude/commands/",
3365
3554
  "**/codex.md",
3366
3555
  "**/.codexignore",
3367
3556
  "**/.roo/rules/",
@@ -3369,6 +3558,7 @@ var gitignoreCommand = async () => {
3369
3558
  "**/.copilotignore",
3370
3559
  "**/GEMINI.md",
3371
3560
  "**/.gemini/memories/",
3561
+ "**/.gemini/commands/",
3372
3562
  "**/.aiexclude",
3373
3563
  "**/.aiignore",
3374
3564
  "**/.augmentignore",
@@ -3415,12 +3605,12 @@ ${linesToAdd.join("\n")}
3415
3605
  };
3416
3606
 
3417
3607
  // src/core/importer.ts
3418
- var import_node_path18 = require("path");
3419
- var import_gray_matter6 = __toESM(require("gray-matter"), 1);
3608
+ var import_node_path22 = require("path");
3609
+ var import_gray_matter7 = __toESM(require("gray-matter"), 1);
3420
3610
 
3421
3611
  // src/parsers/augmentcode.ts
3422
- var import_node_path12 = require("path");
3423
- var import_gray_matter2 = __toESM(require("gray-matter"), 1);
3612
+ var import_node_path16 = require("path");
3613
+ var import_gray_matter3 = __toESM(require("gray-matter"), 1);
3424
3614
 
3425
3615
  // src/utils/parser-helpers.ts
3426
3616
  function createParseResult() {
@@ -3468,7 +3658,7 @@ async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
3468
3658
  async function parseUnifiedAugmentcode(baseDir, config) {
3469
3659
  const result = createParseResult();
3470
3660
  if (config.rulesDir) {
3471
- const rulesDir = (0, import_node_path12.join)(baseDir, config.rulesDir);
3661
+ const rulesDir = (0, import_node_path16.join)(baseDir, config.rulesDir);
3472
3662
  if (await fileExists(rulesDir)) {
3473
3663
  const rulesResult = await parseAugmentRules(rulesDir, config);
3474
3664
  addRules(result, rulesResult.rules);
@@ -3481,7 +3671,7 @@ async function parseUnifiedAugmentcode(baseDir, config) {
3481
3671
  }
3482
3672
  }
3483
3673
  if (config.legacyFilePath) {
3484
- const legacyPath = (0, import_node_path12.join)(baseDir, config.legacyFilePath);
3674
+ const legacyPath = (0, import_node_path16.join)(baseDir, config.legacyFilePath);
3485
3675
  if (await fileExists(legacyPath)) {
3486
3676
  const legacyResult = await parseAugmentGuidelines(legacyPath, config);
3487
3677
  if (legacyResult.rule) {
@@ -3505,16 +3695,16 @@ async function parseAugmentRules(rulesDir, config) {
3505
3695
  const files = await readdir2(rulesDir);
3506
3696
  for (const file of files) {
3507
3697
  if (file.endsWith(".md") || file.endsWith(".mdc")) {
3508
- const filePath = (0, import_node_path12.join)(rulesDir, file);
3698
+ const filePath = (0, import_node_path16.join)(rulesDir, file);
3509
3699
  try {
3510
3700
  const rawContent = await readFileContent(filePath);
3511
- const parsed = (0, import_gray_matter2.default)(rawContent);
3701
+ const parsed = (0, import_gray_matter3.default)(rawContent);
3512
3702
  const frontmatterData = parsed.data;
3513
3703
  const ruleType = frontmatterData.type || "manual";
3514
3704
  const description = frontmatterData.description || "";
3515
3705
  const tags = Array.isArray(frontmatterData.tags) ? frontmatterData.tags : void 0;
3516
3706
  const isRoot = ruleType === "always";
3517
- const filename = (0, import_node_path12.basename)(file, file.endsWith(".mdc") ? ".mdc" : ".md");
3707
+ const filename = (0, import_node_path16.basename)(file, file.endsWith(".mdc") ? ".mdc" : ".md");
3518
3708
  const frontmatter = {
3519
3709
  root: isRoot,
3520
3710
  targets: [config.targetName],
@@ -3572,8 +3762,8 @@ async function parseAugmentGuidelines(guidelinesPath, config) {
3572
3762
  }
3573
3763
 
3574
3764
  // src/parsers/shared-helpers.ts
3575
- var import_node_path13 = require("path");
3576
- var import_gray_matter3 = __toESM(require("gray-matter"), 1);
3765
+ var import_node_path17 = require("path");
3766
+ var import_gray_matter4 = __toESM(require("gray-matter"), 1);
3577
3767
  async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3578
3768
  const errors = [];
3579
3769
  const rules = [];
@@ -3586,7 +3776,7 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3586
3776
  let content;
3587
3777
  let frontmatter;
3588
3778
  if (mainFile.useFrontmatter) {
3589
- const parsed = (0, import_gray_matter3.default)(rawContent);
3779
+ const parsed = (0, import_gray_matter4.default)(rawContent);
3590
3780
  content = parsed.content.trim();
3591
3781
  const parsedFrontmatter = parsed.data;
3592
3782
  frontmatter = {
@@ -3628,14 +3818,14 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3628
3818
  const files = await readdir2(dirPath);
3629
3819
  for (const file of files) {
3630
3820
  if (file.endsWith(dirConfig.filePattern)) {
3631
- const filePath = (0, import_node_path13.join)(dirPath, file);
3821
+ const filePath = (0, import_node_path17.join)(dirPath, file);
3632
3822
  const fileResult = await safeAsyncOperation(async () => {
3633
3823
  const rawContent = await readFileContent(filePath);
3634
3824
  let content;
3635
3825
  let frontmatter;
3636
3826
  const filename = file.replace(new RegExp(`\\${dirConfig.filePattern}$`), "");
3637
3827
  if (dirConfig.filePattern === ".instructions.md") {
3638
- const parsed = (0, import_gray_matter3.default)(rawContent);
3828
+ const parsed = (0, import_gray_matter4.default)(rawContent);
3639
3829
  content = parsed.content.trim();
3640
3830
  const parsedFrontmatter = parsed.data;
3641
3831
  frontmatter = {
@@ -3701,6 +3891,13 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
3701
3891
  const memoryRules = await parseMemoryFiles(memoryDir, config);
3702
3892
  rules.push(...memoryRules);
3703
3893
  }
3894
+ if (config.commandsDirPath) {
3895
+ const commandsDir = resolvePath(config.commandsDirPath, baseDir);
3896
+ if (await fileExists(commandsDir)) {
3897
+ const commandsRules = await parseCommandsFiles(commandsDir, config);
3898
+ rules.push(...commandsRules);
3899
+ }
3900
+ }
3704
3901
  const settingsPath = resolvePath(config.settingsPath, baseDir);
3705
3902
  if (await fileExists(settingsPath)) {
3706
3903
  const settingsResult = await parseSettingsFile(settingsPath, config.tool);
@@ -3766,10 +3963,10 @@ async function parseMemoryFiles(memoryDir, config) {
3766
3963
  const files = await readdir2(memoryDir);
3767
3964
  for (const file of files) {
3768
3965
  if (file.endsWith(".md")) {
3769
- const filePath = (0, import_node_path13.join)(memoryDir, file);
3966
+ const filePath = (0, import_node_path17.join)(memoryDir, file);
3770
3967
  const content = await readFileContent(filePath);
3771
3968
  if (content.trim()) {
3772
- const filename = (0, import_node_path13.basename)(file, ".md");
3969
+ const filename = (0, import_node_path17.basename)(file, ".md");
3773
3970
  const frontmatter = {
3774
3971
  root: false,
3775
3972
  targets: [config.tool],
@@ -3789,6 +3986,54 @@ async function parseMemoryFiles(memoryDir, config) {
3789
3986
  }
3790
3987
  return rules;
3791
3988
  }
3989
+ async function parseCommandsFiles(commandsDir, config) {
3990
+ const rules = [];
3991
+ try {
3992
+ const { readdir: readdir2 } = await import("fs/promises");
3993
+ const files = await readdir2(commandsDir);
3994
+ for (const file of files) {
3995
+ if (file.endsWith(".md")) {
3996
+ const filePath = (0, import_node_path17.join)(commandsDir, file);
3997
+ const content = await readFileContent(filePath);
3998
+ if (content.trim()) {
3999
+ const filename = (0, import_node_path17.basename)(file, ".md");
4000
+ let frontmatter;
4001
+ let ruleContent;
4002
+ try {
4003
+ const parsed = (0, import_gray_matter4.default)(content);
4004
+ ruleContent = parsed.content.trim();
4005
+ const parsedFrontmatter = parsed.data;
4006
+ frontmatter = {
4007
+ root: false,
4008
+ targets: [config.tool],
4009
+ description: parsedFrontmatter.description || `Command: ${filename}`,
4010
+ globs: ["**/*"]
4011
+ };
4012
+ } catch {
4013
+ ruleContent = content.trim();
4014
+ frontmatter = {
4015
+ root: false,
4016
+ targets: [config.tool],
4017
+ description: `Command: ${filename}`,
4018
+ globs: ["**/*"]
4019
+ };
4020
+ }
4021
+ if (ruleContent) {
4022
+ rules.push({
4023
+ frontmatter,
4024
+ content: ruleContent,
4025
+ filename,
4026
+ filepath: filePath,
4027
+ type: "command"
4028
+ });
4029
+ }
4030
+ }
4031
+ }
4032
+ }
4033
+ } catch {
4034
+ }
4035
+ return rules;
4036
+ }
3792
4037
  async function parseSettingsFile(settingsPath, tool) {
3793
4038
  const errors = [];
3794
4039
  let ignorePatterns;
@@ -3836,7 +4081,8 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
3836
4081
  settingsPath: ".claude/settings.json",
3837
4082
  mainDescription: "Main Claude Code configuration",
3838
4083
  memoryDescription: "Memory file",
3839
- filenamePrefix: "claude"
4084
+ filenamePrefix: "claude",
4085
+ commandsDirPath: ".claude/commands"
3840
4086
  });
3841
4087
  }
3842
4088
 
@@ -3861,7 +4107,7 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
3861
4107
  }
3862
4108
 
3863
4109
  // src/parsers/codexcli.ts
3864
- var import_node_path14 = require("path");
4110
+ var import_node_path18 = require("path");
3865
4111
 
3866
4112
  // src/parsers/copilot.ts
3867
4113
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
@@ -3884,10 +4130,10 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
3884
4130
  }
3885
4131
 
3886
4132
  // src/parsers/cursor.ts
3887
- var import_node_path15 = require("path");
3888
- var import_gray_matter4 = __toESM(require("gray-matter"), 1);
4133
+ var import_node_path19 = require("path");
4134
+ var import_gray_matter5 = __toESM(require("gray-matter"), 1);
3889
4135
  var import_js_yaml = require("js-yaml");
3890
- var import_mini7 = require("zod/mini");
4136
+ var import_mini8 = require("zod/mini");
3891
4137
  var customMatterOptions = {
3892
4138
  engines: {
3893
4139
  yaml: {
@@ -3915,7 +4161,7 @@ var customMatterOptions = {
3915
4161
  }
3916
4162
  };
3917
4163
  function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
3918
- const FrontmatterSchema = import_mini7.z.record(import_mini7.z.string(), import_mini7.z.unknown());
4164
+ const FrontmatterSchema = import_mini8.z.record(import_mini8.z.string(), import_mini8.z.unknown());
3919
4165
  const parseResult = FrontmatterSchema.safeParse(cursorFrontmatter);
3920
4166
  if (!parseResult.success) {
3921
4167
  return {
@@ -4009,11 +4255,11 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
4009
4255
  const rules = [];
4010
4256
  let ignorePatterns;
4011
4257
  let mcpServers;
4012
- const cursorFilePath = (0, import_node_path15.join)(baseDir, ".cursorrules");
4258
+ const cursorFilePath = (0, import_node_path19.join)(baseDir, ".cursorrules");
4013
4259
  if (await fileExists(cursorFilePath)) {
4014
4260
  try {
4015
4261
  const rawContent = await readFileContent(cursorFilePath);
4016
- const parsed = (0, import_gray_matter4.default)(rawContent, customMatterOptions);
4262
+ const parsed = (0, import_gray_matter5.default)(rawContent, customMatterOptions);
4017
4263
  const content = parsed.content.trim();
4018
4264
  if (content) {
4019
4265
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, "cursorrules");
@@ -4030,20 +4276,20 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
4030
4276
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
4031
4277
  }
4032
4278
  }
4033
- const cursorRulesDir = (0, import_node_path15.join)(baseDir, ".cursor", "rules");
4279
+ const cursorRulesDir = (0, import_node_path19.join)(baseDir, ".cursor", "rules");
4034
4280
  if (await fileExists(cursorRulesDir)) {
4035
4281
  try {
4036
4282
  const { readdir: readdir2 } = await import("fs/promises");
4037
4283
  const files = await readdir2(cursorRulesDir);
4038
4284
  for (const file of files) {
4039
4285
  if (file.endsWith(".mdc")) {
4040
- const filePath = (0, import_node_path15.join)(cursorRulesDir, file);
4286
+ const filePath = (0, import_node_path19.join)(cursorRulesDir, file);
4041
4287
  try {
4042
4288
  const rawContent = await readFileContent(filePath);
4043
- const parsed = (0, import_gray_matter4.default)(rawContent, customMatterOptions);
4289
+ const parsed = (0, import_gray_matter5.default)(rawContent, customMatterOptions);
4044
4290
  const content = parsed.content.trim();
4045
4291
  if (content) {
4046
- const filename = (0, import_node_path15.basename)(file, ".mdc");
4292
+ const filename = (0, import_node_path19.basename)(file, ".mdc");
4047
4293
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, filename);
4048
4294
  rules.push({
4049
4295
  frontmatter,
@@ -4066,7 +4312,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
4066
4312
  if (rules.length === 0) {
4067
4313
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
4068
4314
  }
4069
- const cursorIgnorePath = (0, import_node_path15.join)(baseDir, ".cursorignore");
4315
+ const cursorIgnorePath = (0, import_node_path19.join)(baseDir, ".cursorignore");
4070
4316
  if (await fileExists(cursorIgnorePath)) {
4071
4317
  try {
4072
4318
  const content = await readFileContent(cursorIgnorePath);
@@ -4079,7 +4325,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
4079
4325
  errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
4080
4326
  }
4081
4327
  }
4082
- const cursorMcpPath = (0, import_node_path15.join)(baseDir, ".cursor", "mcp.json");
4328
+ const cursorMcpPath = (0, import_node_path19.join)(baseDir, ".cursor", "mcp.json");
4083
4329
  if (await fileExists(cursorMcpPath)) {
4084
4330
  try {
4085
4331
  const content = await readFileContent(cursorMcpPath);
@@ -4123,16 +4369,17 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
4123
4369
  additionalIgnoreFile: {
4124
4370
  path: ".aiexclude",
4125
4371
  parser: parseAiexclude
4126
- }
4372
+ },
4373
+ commandsDirPath: ".gemini/commands"
4127
4374
  });
4128
4375
  }
4129
4376
 
4130
4377
  // src/parsers/junie.ts
4131
- var import_node_path16 = require("path");
4378
+ var import_node_path20 = require("path");
4132
4379
  async function parseJunieConfiguration(baseDir = process.cwd()) {
4133
4380
  const errors = [];
4134
4381
  const rules = [];
4135
- const guidelinesPath = (0, import_node_path16.join)(baseDir, ".junie", "guidelines.md");
4382
+ const guidelinesPath = (0, import_node_path20.join)(baseDir, ".junie", "guidelines.md");
4136
4383
  if (!await fileExists(guidelinesPath)) {
4137
4384
  errors.push(".junie/guidelines.md file not found");
4138
4385
  return { rules, errors };
@@ -4185,8 +4432,8 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
4185
4432
 
4186
4433
  // src/parsers/windsurf.ts
4187
4434
  var import_promises3 = require("fs/promises");
4188
- var import_node_path17 = require("path");
4189
- var import_gray_matter5 = __toESM(require("gray-matter"), 1);
4435
+ var import_node_path21 = require("path");
4436
+ var import_gray_matter6 = __toESM(require("gray-matter"), 1);
4190
4437
 
4191
4438
  // src/core/importer.ts
4192
4439
  async function importConfiguration(options) {
@@ -4272,7 +4519,7 @@ async function importConfiguration(options) {
4272
4519
  if (rules.length === 0 && !ignorePatterns && !mcpServers) {
4273
4520
  return { success: false, rulesCreated: 0, errors };
4274
4521
  }
4275
- const rulesDirPath = (0, import_node_path18.join)(baseDir, rulesDir);
4522
+ const rulesDirPath = (0, import_node_path22.join)(baseDir, rulesDir);
4276
4523
  try {
4277
4524
  const { mkdir: mkdir3 } = await import("fs/promises");
4278
4525
  await mkdir3(rulesDirPath, { recursive: true });
@@ -4285,8 +4532,13 @@ async function importConfiguration(options) {
4285
4532
  for (const rule of rules) {
4286
4533
  try {
4287
4534
  const baseFilename = rule.filename;
4288
- const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
4289
- const filePath = (0, import_node_path18.join)(rulesDirPath, `${filename}.md`);
4535
+ let targetDir = rulesDirPath;
4536
+ if (rule.type === "command") {
4537
+ targetDir = (0, import_node_path22.join)(rulesDirPath, "commands");
4538
+ const { mkdir: mkdir3 } = await import("fs/promises");
4539
+ await mkdir3(targetDir, { recursive: true });
4540
+ }
4541
+ const filePath = (0, import_node_path22.join)(targetDir, `${baseFilename}.md`);
4290
4542
  const content = generateRuleFileContent(rule);
4291
4543
  await writeFileContent(filePath, content);
4292
4544
  rulesCreated++;
@@ -4301,7 +4553,7 @@ async function importConfiguration(options) {
4301
4553
  let ignoreFileCreated = false;
4302
4554
  if (ignorePatterns && ignorePatterns.length > 0) {
4303
4555
  try {
4304
- const rulesyncignorePath = (0, import_node_path18.join)(baseDir, ".rulesyncignore");
4556
+ const rulesyncignorePath = (0, import_node_path22.join)(baseDir, ".rulesyncignore");
4305
4557
  const ignoreContent = `${ignorePatterns.join("\n")}
4306
4558
  `;
4307
4559
  await writeFileContent(rulesyncignorePath, ignoreContent);
@@ -4317,7 +4569,7 @@ async function importConfiguration(options) {
4317
4569
  let mcpFileCreated = false;
4318
4570
  if (mcpServers && Object.keys(mcpServers).length > 0) {
4319
4571
  try {
4320
- const mcpPath = (0, import_node_path18.join)(baseDir, rulesDir, ".mcp.json");
4572
+ const mcpPath = (0, import_node_path22.join)(baseDir, rulesDir, ".mcp.json");
4321
4573
  const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
4322
4574
  `;
4323
4575
  await writeFileContent(mcpPath, mcpContent);
@@ -4339,17 +4591,16 @@ async function importConfiguration(options) {
4339
4591
  };
4340
4592
  }
4341
4593
  function generateRuleFileContent(rule) {
4342
- const frontmatter = import_gray_matter6.default.stringify("", rule.frontmatter);
4343
- return frontmatter + rule.content;
4344
- }
4345
- async function generateUniqueFilename(rulesDir, baseFilename) {
4346
- let filename = baseFilename;
4347
- let counter = 1;
4348
- while (await fileExists((0, import_node_path18.join)(rulesDir, `${filename}.md`))) {
4349
- filename = `${baseFilename}-${counter}`;
4350
- counter++;
4594
+ if (rule.type === "command") {
4595
+ const simplifiedFrontmatter = {
4596
+ description: rule.frontmatter.description,
4597
+ targets: rule.frontmatter.targets
4598
+ };
4599
+ const frontmatter2 = import_gray_matter7.default.stringify("", simplifiedFrontmatter);
4600
+ return frontmatter2 + rule.content;
4351
4601
  }
4352
- return filename;
4602
+ const frontmatter = import_gray_matter7.default.stringify("", rule.frontmatter);
4603
+ return frontmatter + rule.content;
4353
4604
  }
4354
4605
 
4355
4606
  // src/cli/commands/import.ts
@@ -4412,7 +4663,7 @@ async function importCommand(options = {}) {
4412
4663
  }
4413
4664
 
4414
4665
  // src/cli/commands/init.ts
4415
- var import_node_path19 = require("path");
4666
+ var import_node_path23 = require("path");
4416
4667
  async function initCommand() {
4417
4668
  const aiRulesDir = ".rulesync";
4418
4669
  console.log("Initializing rulesync...");
@@ -4459,7 +4710,7 @@ globs: ["**/*"]
4459
4710
  - Follow single responsibility principle
4460
4711
  `
4461
4712
  };
4462
- const filepath = (0, import_node_path19.join)(aiRulesDir, sampleFile.filename);
4713
+ const filepath = (0, import_node_path23.join)(aiRulesDir, sampleFile.filename);
4463
4714
  if (!await fileExists(filepath)) {
4464
4715
  await writeFileContent(filepath, sampleFile.content);
4465
4716
  console.log(`Created ${filepath}`);
@@ -4603,7 +4854,7 @@ async function watchCommand() {
4603
4854
 
4604
4855
  // src/cli/index.ts
4605
4856
  var program = new import_commander.Command();
4606
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.56.0");
4857
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.58.0");
4607
4858
  program.command("init").description("Initialize rulesync in current directory").action(initCommand);
4608
4859
  program.command("add <filename>").description("Add a new rule file").action(addCommand);
4609
4860
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);